Problem/Motivation

When jsonapi is enabled, the whole webform entity including the handlers (email addresses, messages etc.) is exposed to anonymous users. I believe this is because webform considers the "view" permission from a HTML front-end perspective to be simply viewing the built form and creating a webform submission. In the REST world, view includes viewing the full field structure and data of an entity.

Proposed resolution

I think it probably makes sense to rejig the "view" permission on the whole entity or to implement field access to deny access to all individual fields of a webform that aren't already public information.

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Sam152 created an issue. See original summary.

rickmanelius’s picture

Priority: Normal » Critical
Issue tags: +Security

Thank you, @Sam152, for generating this issue. It should be worth noting that this would otherwise be a security issue managed/addressed in private. However, since this module is still in RC, it can be handled in public. That said, this should be addressed before an official stable release goes out.

jrockowitz’s picture

I don't feel that this is a security issue but a side-effect of exposing any config entity to Drupal's REST API. I agree most people would expect only the webform elements to be available. Keep in mind that webform elements could also include sensitive information like API keys, roles, usernames, default values, admin only elements, etc...

At the same time, I could see a mobile app needing the entire webform's configuration including the sensitive information to build a headless webform.

Here are the steps required replicate the issue...

  • Enable HAL, REST UI, RESTful Web Services, Serialization (/admin/modules)
  • Enable GET for Webform by checking off all options.(/admin/config/services/rest/resource/entity%3Awebform/edit)
  • Open the Contact form (/form/contact?_format=hal_json)
  • Confirm the entire forms configuration is visible.

Here is a screenshot of the exposed data...

I am little stumped on the best/long term solution. We might need to decide on a MVP solution with a long term plan. An MVP might be applying some basic permissions to the exported configuration. I think the long term plan has to address headless webform instances.

Wim Leers’s picture

This is essentially the same problem as #2820315: Blocks' 'view' operation is interpreted as "render on page" instead of "read this config entity", but worse, because webform config entities tend to contain more sensitive data than block config entities.

I see in https://cgit.drupalcode.org/webform/tree/src/WebformEntityAccessControlH... that webform already defined two custom operations: test and duplicate. IMHO this also needs a render operation, and that's the operation that should be checked when rendering a webform config entity into an HTML form.

jrockowitz’s picture

@Wim Leers Thanks for pointing us in the right direction.

The viewing and rendering of a webform is controlled by whether a user can create a webform submission. We could add 'view own webform' (configuration) and 'view any webform' (configuration) permissions which would solely be for allowing applications to access a webform's configuration via the REST API.

BTW, The Webform REST module is providing individual endpoints for different aspects of a webform. Right now only elements are supported. I think the Webform REST module could be the long term approach.

jrockowitz’s picture

The attached patch just returns AccessResult::forbidden(); for the webform 'view' operation and should confirm that the 'view' operation in the WebformEntityAccessControlHandler has actually never been used, which means we could make it applicable to the Webform REST API endpoint.

jrockowitz’s picture

The attached patch removes all calls to $webform->access('view) and now the 'view' operation can be reserved for only accessing a webform's configuration via the REST API. I think there should be dedicated 'access own webform configuration' and 'access any webform configuration' permission which would be checked via the webform 'view' operation.

The EntityResource::get() method relies on the 'view' operation so right now I don't think there are are other immediate solutions.

The long-term approach might be to only use the 'view' operation for a rendering and for Drupal core to support a dedicated 'access_configuration' operation.

  • jrockowitz committed c914937 on 2956771-sensitive-information
    Issue #2956771 by jrockowitz: Sensitive information is disclosed via...
jrockowitz’s picture

Status: Needs review » Needs work

The last submitted patch, 9: 2956771-9.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • jrockowitz committed 1f2a15f on 2956771-sensitive-information
    Issue #2956771 by jrockowitz: Sensitive information is disclosed via...

  • jrockowitz committed b51d837 on 2956771-sensitive-information
    Issue #2956771 by jrockowitz: Sensitive information is disclosed via...
jrockowitz’s picture

Status: Needs work » Needs review
FileSize
10.13 KB
jrockowitz’s picture

Even though the patch from #13 limits the 'view' operation to 'administer', 'access own webform configuration' or 'access any webform configuration' permission. I think other modules that call the 'view' operation for a webform could have a major problem. For example, I think entity references which call the 'view' operation would be broken via the approach in #13.

I think we have to create a dedicated operation for accessing a webform's configuration and then override the Webform's EntityResource::get() method to use this new operation.

What should the new operation be called?

  • configuration
  • access configuration
  • view configuration

Are two word operation allowed/recommended?

Some questions/thoughts:

  • Is using new operation the best approach? It might be the only approach
  • Is using a new permission the best approach? The only alternative would be use the update operation to access webform configuration.

Some implementation notes:

  • Need to use hook_rest_resource_alter() to override a Webform's EntityResource
  • Can use contact_rest_resource_alter() as an example
Sam152’s picture

For example, I think entity references which call the 'view' operation would be broken via the approach in #13.

I think this isn't too bad because at least we're explicitly granting access when fixing these 3rd party integrations.

I think we have to create a dedicated operation for accessing a webform's configuration and then override the Webform's EntityResource::get() method to use this new operation.

Hm, not sure if this is sufficient. Does jsonapi use this same mechanism for example?

jrockowitz’s picture

Yes, it does seem that JSON API does also rely on the 'view' operation.
@see \Drupal\jsonapi\Controller\EntityResource::getIndividual

I am stumped because if we fix REST and JSON API by tweaking the 'view' operation to use the 'access webform configuration' permission, we will break entity references that point to webforms.

Sam152’s picture

Could we start with support for the custom operation in WebformEntityReferenceItem? Are any plain entity reference fields used to reference webform config entities in the base functionality of the module?

jrockowitz’s picture

The patch switches from using the 'view' operation to the 'submission_create' operation for WebformEntityReferenceItem::checkAccess. I might have to look into this tweak because it could prevent the disabled/blocked webform message from being displayed to end-users.

Wim Leers’s picture

I would suggest adding a custom operation, such as render.

jrockowitz’s picture

  • view => access the webform configuration which requires the 'access own/any webform configuration' permission.
  • render => access the generated/rendered webform

The current patch uses the 'webform.submission_create' operation to grant users access to the rendered webform and the ability to create a new submissions. I would like to keep the 'webform.submission_create' operation and add support for the 'render' operation, which would do almost the same thing.

Does this approach seem reasonable?

jrockowitz’s picture

Attached is the re-rolled patch with the 'render' operation being handled as a 'submission_create' operation without any change to the RELEASE-NOTES.md

  • jrockowitz committed 6e9fd25 on 2956771-sensitive-information
    Issue #2956771 by jrockowitz: Sensitive information is disclosed via...
jrockowitz’s picture

I reviewed the patch using the steps from #3 and it does protect the webform's configuration and only allows authenticated users to access any or own webform configuration.

Attached is the final patch which I am going to be committing.

  • jrockowitz committed bbe36d9 on 8.x-5.x
    Issue #2956771 by jrockowitz, Sam152, Wim Leers, rickmanelius: Sensitive...
jrockowitz’s picture

Status: Needs review » Fixed
Berdir’s picture

This causes a serious regression for us. We are using regular entity reference fields to reference to webforms and that checks the view operation (We are not using the webform reference field here because we don't need the additional UI options). As a result of this, webforms are no longer displayed.

As pointed out in another context, I still strongly believe that unconditionally exposing any entity type over REST is wrong for modules like json_api/rest :) Over and over again, this approach first causes security issues at first and then fixing them breaks other things :)

This is IMHO an API change, as was suggested in similar cases, one workaround would be to still allow the view operation for the html request format.

I'm reopening this for now, can also create a new issue if you prefer that.

Berdir’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 26: webform-access-2956771-too-locked-down-26.patch, failed testing. View results

jrockowitz’s picture

@berdir your patch makes sense to me. If no one objects and and we fix the broken tests, I will commit the patch.

Berdir’s picture

Status: Needs work » Needs review
FileSize
4.02 KB

Lets fix the broken test then, also added test coverage for this, used the already injected request stack and added the request format cache context (fancy, didn't even know that one existed!)

Status: Needs review » Needs work

The last submitted patch, 30: webform-access-2956771-too-locked-down-30.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

Status: Needs work » Needs review

Hm, to be honest, I'm not sure I understand what you're doing exactly with render/submission_create operations. render doesn't seem to be used at all.

Possibly it's actually the view operation that should be changed to submission_create if the request format is html so that whatever you're doing there is also respected for regular entity reference fields?

Also, the test that is currently failing fails due to a validation error: "Invalid Drupal.org organization name.". Is that really running against the d.o API? that might not be the best idea :)

Berdir’s picture

Here's a version of the patch that does that.

For what's its worth, I still disagree with Wim in the referenced core issue in that the view operation is something different on html request format vs others *for any entity type* and that blocks really aren't that special in that regard.

jrockowitz’s picture

@Berdir The Contribute module's integration test does run against Drupal.org. I know it is not a great idea but it did succeed in catching a major issue with Drupal.org REST API that is breaking this functionality. I just disabled the broken test and will re-queue the tests

I added the 'render' operation from #4 because it does make sense. The 'render' operation is handled via a 'submission_create' operation.

I am also not sure what is the right solution.

Status: Needs review » Needs work

The last submitted patch, 33: webform-access-2956771-too-locked-down-32.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jrockowitz’s picture

Status: Needs work » Needs review
Sam152’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/src/WebformEntityAccessControlHandler.php
@@ -127,11 +127,14 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple
+    $extra_contexts = [];
+    if ($operation === 'view' && $this->requestStack->getCurrentRequest()->getRequestFormat() == 'html') {
       $operation = 'submission_create';
+      $extra_contexts = ['request_format'];
     }

I like this a lot, I think it's the most elegant way of expressing the real world scenario, given some of the constraints we are working worth.

It reads to me as:

If we are browsing an HTML response, we don't support or allow "viewing" of webform data, instead check if the user has access to create a submission. A rendered webform is not webform data, it's a webform submission creation interface.

So this is a thumbs up, unless anyone has any totally different approaches which bridge the gap between "view"ing rendered stuff vs "view"ing field data.

One minor nit, the "request_format" cache context should be added to all return values for "view", not only for HTML format ones, since access checks should not be cached universally if another request format is used first.

Berdir’s picture

> One minor nit, the "request_format" cache context should be added to all return values for "view", not only for HTML format ones, since access checks should not be cached universally if another request format is used first.

I believe I'm doing that because if we don't go into this condition, then it will go into the $operation == 'view' condition below and then I'm also adding the request format there.

And for users who have permissions to actually view the webform configuration, the request format also doesn't matter, they always have access.

Due to a large amount of early returns and re-creating access objects instead of adding to them, it's not easy to do that in a different way.

Sam152’s picture

then it will go into the $operation == 'view' condition below and then I'm also adding the request format there

Ah, my mistake, we're indeed covered if later return values of 'view' include the context.

s_leu’s picture

Status: Reviewed & tested by the community » Needs work

I think the patch that got committed in #23 introduced regression beyond what berdir reported. Especially the following code or at least the access result message doesn't make any sense to me:

    // Return forbidden access result with a reason for 'view' operation
    // so that blocked REST API requests can better understand
    // why access was denied.
    if ($access_result->isNeutral() && $operation === 'view') {
      $access_result = AccessResult::neutral("The 'administer webform' or 'access own or any webform configuration' permission is required.");
      $access_result->cachePerPermissions()->cachePerUser();
    }

It could be understood as one should grant 'administer webform', 'access own webform configuration' or 'access or any webform configuration' to anonymous users. Because when the execution reaches this line, the request is coming from an anonymous user and you don't want to grant these permissions to this role.

Even worse, even if you grant 'access own webform configuration' or 'access or any webform configuration' to anonymous users, this still doesn't work and again, nobody wants to grant 'administer webform' to anonymous users.

-enzo-’s picture

Using JSON API and trying to get webform definition in a decouple application using a URL similar to

http://example.com:8080/jsonapi/webform/webform?filter[id][value]=my_custom_webform

The only permissions that allow accessing the webform definition is `Administer webforms` which `Allows administration of global YAML configuration and options.`.

Permission `access any webform configuration` doesn't work for the anonymous user.

Maybe we should include a separate permission that allows reading that configuration but without administration permissions.

jrockowitz’s picture

So I feel that Berdir's comment from #38

Due to a large amount of early returns and re-creating access objects instead of adding to them, it's not easy to do that in a different way.

Hints at a much larger issue which is the Webform module's access controls have become a little overwhelming. This is making it very difficult for us to resolve this issue.

I created and will work on #3005581: [meta] Improve test coverage, refactor, and stabilize webform access controls

jrockowitz’s picture

Remaining issues

  • View operation needs to be correctly supported. Entity Reference field uses the view operation to determine access.
  • Viewing rendered webform (aka HTML) should use expected permissions and access rules
  • View webform configuration (aka NOT HTML) should have dedicated own and any permissions and access rules with users, roles, and permissions should be supported

Tasks

  • Add 'Access Webform configuration' (configuration) to access rules
  • Write update hook.
  • Isolate all view operation access checks in WebformEntityAccessControlHandler and add 'request_format' cache context.
  • Update view access response message.
  • Update tests
  • Write change record documenting webform configuration permission and access rule.

Changes to be reverted…

  • QueryStringWebformSourceEntity should use 'view' operation
  • Route _entity_access should used webform.view
    • entity.webform.assets.javascript
    • entity.webform.assets.css
    • entity.webform.confirmation

  • jrockowitz committed bbe36d9 on 2956771-rest-access
    Issue #2956771 by jrockowitz, Sam152, Wim Leers, rickmanelius: Sensitive...

  • jrockowitz committed bbe36d9 on 2956771-rest-access
    Issue #2956771 by jrockowitz, Sam152, Wim Leers, rickmanelius: Sensitive...

  • jrockowitz committed bbe36d9 on 2956771-rest-access
    Issue #2956771 by jrockowitz, Sam152, Wim Leers, rickmanelius: Sensitive...

  • jrockowitz committed 575b15b on 2956771-rest-access
    Issue #2956771 by jrockowitz, Berdir, Sam152, Wim Leers, rickmanelius:...

  • jrockowitz committed f0dcac6 on 2956771-rest-access
    Issue #2956771 by jrockowitz, Berdir, Sam152, Wim Leers, rickmanelius:...
jrockowitz’s picture

Status: Needs work » Needs review
FileSize
28.8 KB

Attached is a patch that combines #33 and allows individual webform configuration access to be managed using an access rule.

The main change that needs to be reviewed is in \Drupal\webform\WebformEntityAccessControlHandler::checkAccess. I am specifically trying to keep the access checking and access result context inside one understandable code block.

I also created a draft change record.

jrockowitz’s picture

If anyone has time available, please review this patch.

I am hoping to commit this patch before the next week's release.

joum’s picture

Hi jrockowitz!

I can validate that your latest proposed patch has fixed the Webform not being visible for anonymous users after core update to 8.6.2 (even after updb) and it fixed this issue in 2 different sites of mine. Good job!

I propose moving to RTBC after a couple more confirmations.

Cheers!

jrockowitz’s picture

I think we have fixed the REST API issue but this ticket #3010727: Error processing Webform Tokens, sending leaked metadata is indicating that JSON API may still be using the HTML request format.

  • jrockowitz committed 1128300 on 2956771-rest-access
    Issue #2956771 by jrockowitz, Berdir, Sam152, Wim Leers, rickmanelius:...

  • jrockowitz committed f592b8f on 2956771-rest-access
    Issue #2956771 by jrockowitz, Berdir, Sam152, Wim Leers, rickmanelius:...
jrockowitz’s picture

Attached patch includes JSON API test coverage.

Status: Needs review » Needs work

The last submitted patch, 55: 2956771-55.patch, failed testing. View results

AaronBauman’s picture

Status: Needs work » Needs review
FileSize
112.83 KB

re-rolling #55 with a couple of items that were already committed

Status: Needs review » Needs work

The last submitted patch, 57: 2956771-57.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • jrockowitz committed b74e9f1 on 2956771-rest-access
    Issue #2956771 by jrockowitz, Berdir, Sam152, Wim Leers, rickmanelius:...
jrockowitz’s picture

Status: Needs work » Needs review
FileSize
32.62 KB

@aaronbauman Thanks for the re-roll. I forgot to merge 8.x-5.x into my feature branch.

  • jrockowitz committed 3909b57 on 8.x-5.x
    Issue #2956771 by jrockowitz, Berdir, aaronbauman, Sam152, Wim Leers,...
jrockowitz’s picture

Status: Needs review » Fixed

This issue needs to be resolved before I tag a stable release.

The patch includes test coverage and there is change record, so I decided to commit it.

I am going to tag another RC and hopefully, this patch solves the existing regressions and security issues.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.