Skip to content

Disallow cross-origin requests for webhooks#65928

Closed
esev wants to merge 2 commits intohome-assistant:devfrom
esev:webhook_non_local_headers
Closed

Disallow cross-origin requests for webhooks#65928
esev wants to merge 2 commits intohome-assistant:devfrom
esev:webhook_non_local_headers

Conversation

@esev
Copy link
Copy Markdown
Contributor

@esev esev commented Feb 6, 2022

Breaking change

Webhooks have been adjusted to not allow access from web browsers by default. Trusted web pages can still be allowed by adjusting the cors_allowed_origins configuration for the http integration. If you see a log message similar to Received likely CSRF request from non-trusted origin for webhook, then add the origin to the cors_allowed_origins configuration.

Proposed change

Webhooks can be triggered by devices and services outside of Home Assistant. But they can also be triggered by cross-origin requests in the browser. A web browser could accidentally allow a non-local website to activate a webhook.

(non-local website) <-> (local browser) -> (webhook)

This PR aims to make it harder for a non-local website to activate/trigger a webhook via a browser. It does so by disabling the cors_allowed setting, preventing a non-trusted website from sending non-simple requests. HEAD & POST methods are considered simple requests and do not trigger a CORS preflight check. This PR adds a "Referer" header check to verify these requests come from a trusted site.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • The code has been formatted using Black (black --fast homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
  • Untested files have been added to .coveragerc.

The integration reached or maintains the following Integration Quality Scale:

  • No score or internal
  • 🥈 Silver
  • 🥇 Gold
  • 🏆 Platinum

To help with the load of incoming pull requests:

@esev
Copy link
Copy Markdown
Contributor Author

esev commented Feb 6, 2022

Why does the WebhookView set cors_allowed = True? Was that intentional? It was added in this PR: #27428

It doesn't matter for this PR, as a normal <form> can still send cross-origin requests without any CORS checks. But I'm just curious if this was required for some reason. Could we remove cors_allowed = True if it was not required?

_LOGGER.warning("Received remote request for local webhook %s", webhook_id)
return Response(status=HTTPStatus.OK)

# The presence of NON_LOCAL_HEADERS could indicate a CSRF request from a
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this misses the use case in which a user hosts a local website to provide guests with a basic webhook powered UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(cross-origin is required for that too)

Copy link
Copy Markdown
Contributor Author

@esev esev Feb 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! WDYT about checking the referer/origin URLs against the cors_allowed_origins configuration option from the http component? If the URLs match, then consider the request as allowed for a local_only webhook. That would permit this use case without allowing all of the internet to access the webhook. I think that effectively makes local_only webhooks behave as-if cors_allowed = False.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can't remember why CORS was allowed to True, but I agree that since it can opt-in origins via the http config, it's fine to disable it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Thank you. I've done that, and also added a referer header check for HEAD/POST requests. I also updated the description to consider this PR a breaking change, just in the off chance that someone was relying on this behavior.

Can you add the breaking change label to this PR so that it'll show-up in the release notes?

@esev esev requested a review from a team as a code owner February 8, 2022 03:09
@esev esev changed the title Disallow cross-origin requests for local webhooks Disallow cross-origin requests for webhooks Feb 8, 2022
_LOGGER.debug("%s", content)
return Response(status=HTTPStatus.OK)

# POST & HEAD are considered simple requests. They do not trigger a CORS preflight
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, I forgot about the simple requests. I don't feel like rolling our own CROS is a good way to go.

Do we really need it given the frontend change enforcing that we push for long random strings.

Copy link
Copy Markdown
Contributor Author

@esev esev Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually feel this PR isn't needed if everyone used long random strings from the start. But there are tutorials online that recommend short names, and those names are likely going to be the same for everyone that followed the tutorial.

If you have a few moments, could you review the comments in #56446? That's how I got started down this path. GET is somewhat more dangerous because it's trivial to avoid sending the referer header too. IMHO, PUT (with CORS disabled) is the safest method for webhooks, but I definitely see the utility in allowing more methods.

My day job focuses on keeping users safe online. So it's easy for me to fall into the trap of preferring security over usability. If you could review the comments in #56446, and provide a bit of direction, that would be helpful for me. I'm happy to make the necessary changes to keep users safe. But I'd also rather not go down a path that makes things harder for users or that tries to fix a problem that isn't very wide-spread.

You have a much better perspective on this. I appreciate any feedback you can provide.

Copy link
Copy Markdown
Member

@balloob balloob Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we close this PR and instead go for the method picker that you showed a screenshot off. Although I would even move that into the overflow menu so that we don't have to confuse the user always with it. (and GET disabled by default)

And for local network, that too can be put in overflow menu but we limit to local network by default.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closing this. Thank you for taking a look and the feedback. I appreciate it!

I'll work on polishing-up the method/local options and try to have something ready early next week.

@esev esev closed this Feb 10, 2022
@github-actions github-actions bot locked and limited conversation to collaborators Feb 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants