Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSP: form-action and redirects #8

Open
mikewest opened this issue Oct 7, 2015 · 68 comments
Open

CSP: form-action and redirects #8

mikewest opened this issue Oct 7, 2015 · 68 comments
Labels
Milestone

Comments

@mikewest
Copy link
Member

mikewest commented Oct 7, 2015

From @ptoomey3 on September 23, 2015 0:12

I just wanted to open an issue to get your thoughts on form-action with respect to redirects. We have been working on deploying form-action and have run into a few scenarios that all boil down to:

  • Perform some action by doing a POST to self
  • Based on request params/backend state, redirect the user to another site

One big use case of this is OAuth, where we have users submit a form to authorize access to their account. After the POST we redirect the user back to the OAuth application to complete the OAuth dance.

However, for these kinds of scenarios to work with form-action we would either have to:

  • Determine where we plan to redirect the user for each OAuth authorization so that we can add an appropriate value for form-action.
  • Use something like meta refresh to redirect the user after authorizing (since this doesn't count as a form submit)

I can see why one might want to limit method preserving redirection with a 307, but I couldn't think of much risk with allowing 302 style redirects. I guess there is some value in avoiding the redirection, though it feels somewhat orthogonal to the immediate risk associated with accidentally submitting form contents to an untrusted site. Anyway, I was just curious what your thoughts were on the topic.

/cc @mastahyeti @gregose @oreoshake

Copied from original issue: w3c/webappsec#482

@mikewest mikewest added the CSP label Oct 7, 2015
@mikewest
Copy link
Member Author

mikewest commented Oct 7, 2015

Hrm. I think it would be inconsistent with the rest of CSP to differentiate between types of redirection, but you're right to suggest that the risk of a 302 vs 307 is substantially lower for the specific case that form-action is meant to deal with. Certainly something worth talking about for CSP3.

Setting label and milestone appropriately.

@anforowicz
Copy link

FWIW, I think that

A) enforcing form-action on http redirects doesn't offer significant security improvements (protecting against 2.3.3 below is not possible in general; protecting against 2.3.2 and 2.3.1 doesn't seem important to me).

B) enforcing form-action on http redirects means that the CSP-protected page depends on what should be an implementation details of the server handling the form (i.e. a server receiving the POST data should be free to start responding with an extra, intermediate redirect hop without risking breaking some of its clients [some of whom might be using form-action CSP directive]).

I think it is okay for some CSP directives to pay attention to redirects and for some directives (like form-action) to ignore redirects (i.e. only check the origin of the first http request). I think the comparison between frame-src and form-action below might make it clearer why.

frame-src:

1.1. I assume that frame-src directive mainly protects the parent frame against the threat of embedding a subframe from an unintended origin.

1.2. To protect against the threat above, in theory only the final URI could be verified by CSP .

1.3. This is kind of obvious, but let me also point out that frame-src is enforced for the final URI of both server redirects and client redirects (e.g. via meta refresh and/or window.location).

1.4. frame-src doesn't look at URIs of frames opened in another window (e.g. because the anchor in a subframe had a target attribute OR was shift-clicked). Ignoring other windows seems okay, because the parent-subframe relationship is broken when opening a link in a new window (e.g. the new frame cannot try to trick the user into thinking it is a part of the parent frame's UI). OTOH, in some scenarios window.opener is still populated (e.g. when clicking an anchor with a target attribute, but not when shift-clicking an anchor) so the newly opened frame can still reach into the original parent.

form-action:

2.1. I assume that form-action directive mainly protects form data against the threat of being disclosed to an unintended origin (for example when a hidden form data inside the frame contains an origin-bound secret used for authentication).

2.2. To protect against the threat above, it is important to start CSP enforcement from the very first http request (for example - verifying only the origin of the final redirect destination would be wrong, because the form data could be already disclosed to an unintended origin in the first http request).

2.3. It might be desirable to protect form data against disclosure happening after the first http request. We might want to consider the following threats (personally, I see protection against these threats as much lower priority than protection against the threat outlined in 2.2 above):

2.3.1. Threat of disclosing the form-data to intermediate server redirect destinations (e.g. disclosure to b.com if the redirect chain looks like a.com -> b.com -> c.com). We might want to consider different behavior depending on the type of a server redirect (e.g. http 307 and 308 responses preserve POST data, while other types of redirects don't).

2.3.2. Threat of disclosing the form-data to destinations reached via client redirect (e.g. redirect triggered via meta refresh and/or window.location).

2.3.3. Threat of disclosing the form-data via means other than redirection (e.g. tricking the http server from the original form action into emailing the form data to an unintended origin).

2.4. form-action needs to enforced even if the form action is opened another window (e.g. because the form element had a target attribute OR because the submit button was shift-clicked). This is because the protected resource is the form data (and the form data will be disclosed to the origin at form action URI regardless of whether the request happens in a new window or not).

@ptoomey3
Copy link

ptoomey3 commented Apr 6, 2017

Just ran into another version of this again and thought I'd post here for some context.

We recently implemented a feature on GitHub.com that allows an organization to require external auth using SAML (in addition to their github auth) to access organization resources. The way this is implemented is that in the event that your external auth session has timed out, we show a modal dialog to let you know you need to do the external auth dance again. This modal is sensibly implemented using a partial that is fetched on demand and shoved into calling page's DOM. This makes sense since this modal might be needed on any page throughout github.com that is associated with a given organization.

Once the the user clicks "auth with external provider" we do a POST to github.com to generate the URL plus params needed to redirect to the external auth provider to do the SAML dance. But, because of CSP, this is kind of quirky to implement. We can't set the additional form-action for the auth provider when the modal is fetched via XHR, since the actual POST occurs from the page that fetched the modal, and hence that calling page's CSP is the one that rules. So, we will likely be forced to do something such as:

  • Fetch modal
  • Have form for modal POST to /initiate-external-auth and NOT attempt a redirect to the external auth provider. Instead, append the auth provider for the organization to form-action and render a github.com response interstitial.
  • Have that interstitial page do an auto-POST back to github.com that then does the the redirect to the external auth provider. Or,have that interstitial directly do a GET to the external auth provider URL.

It is, on average, pretty tricky for a calling page to know that the endpoint it is POST(ing) to might redirect and where it might want to redirect.

@mikewest mikewest added this to the CSP3 CR milestone May 9, 2017
@iquito
Copy link

iquito commented Jul 12, 2017

form-action may be the most obscure source for possible problems with a CSP directive - I never would have thought a 302 redirect would still enforce the form-action domains, because at that point it is no longer part of the form processing (the user is redirected to a different site with different parameters, nothing of the original form submission is still present in the form, except if specifically specified by the server-side application).

To scrutinize 307 redirects with form-action makes sense, but doing it for 302 redirects is confusing for developers and will probably lead to form-action being set to very permissive settings in many cases. If the goal is to make it easy to deploy sensible CSP directives it would be good to specifically state that form-action should not be applied to 302/303 redirects after form submission.

@JurRutten
Copy link

Lost hours on this, indeed a confusing and useless scenario.

@Changaco
Copy link

I've added a warning to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action about the unclear scope and inconsistent browser implementations of this directive.

@ThrawnCA
Copy link

@anforowicz Re 2.1: Don't forget risks like phishing by injecting markup to assemble a fake login form that will post credentials to the attacker. form-action could hinder that by forbidding the fake form from calling home (but that doesn't require any restrictions on redirects).

@csuhta
Copy link

csuhta commented Jul 20, 2018

I agree with others that form-action blocking redirects issued from the server is an extremely surprising behavior, and I recently encountered it when implementing an OAuth provider.

Regardless of the final outcome, one of the behaviors should be explicitly defined in CSP3 (is it already somewhere?)

The current situation where Chrome/Safari block redirects and Firefox does not makes deploying form-action very tricky.

@foolip
Copy link
Member

foolip commented Jul 27, 2018

@mikewest we seem to have run into this problem in the wild with webcompat/webcompat.com#2557, ironically in a project about web compat/interop :)

The error that we saw was: Refused to send form data to 'http://webcompat.com/' because it violates the following Content Security Policy directive: "form-action 'self'".

Is this issue at the core of this?

@annevk
Copy link
Member

annevk commented Aug 2, 2018

No, because in that setup there should not be any redirects as I understand it.

@andypaicu
Copy link
Collaborator

Reviving this bug. Would everyone be happy if form-action applied to to 307/308 redirects but not to other redirects? Does this seem reasonable?

I'm not an expert but I assume if a request goes through a 307 > 302 > 307 redirect chain, it will not send POST the form data to the second 307 redirect (or am I wrong?). But if the redirect chain is 307 > 307 > 307 it will send the POST form data (right?).

Assuming the previous paragraph is correct, form-action should apply to all requests that only have 307/308 redirects in their redirect chain.

@iquito
Copy link

iquito commented Oct 24, 2018

Restricting it to only 307/308 would completely solve it, from my perspective, as 99.9% of current redirects in applications will be 301/302 (with no CSP risk whatsoever), and whenever you do a 307/308 it will most likely be a conscious decision with known security implications - it should not be a surprise there.

@ptoomey3
Copy link

I think this would resolve the issue for us. We are now up to roughly 4-5 places in our codebase where we do a meta-redirect/JS redirect instead of a 302 just to avoid the current form-action behavior. So, a change like this would be much appreciated 😄

@Changaco
Copy link

Changaco commented Oct 24, 2018

Considering that form-action seems to apply regardless of the submission method (GET or POST), it wouldn't make much sense for it to react differently to different response codes because they change or preserve that HTTP method.

If a server has an open redirect that preserves the query string, then a 302 is as dangerous as a 307, because in that situation they both allow leaking data to third parties. So, if form-action is supposed to protect against open redirects, then it needs to block everything.

@iquito
Copy link

iquito commented Oct 24, 2018

@Changaco: If you do a GET from a form to a valid form-action (like the same domain usually), and then do a 301 or 302 redirect to a different website, that redirect has nothing to do with the form data - no matter if it was GET or POST, because the GET data is not passed along in a redirect, and the POST data isn't either. form-action should only apply to where the form is directly going with the form data (so the action in the form element, or a 307/308 redirect where POST data is passed along), not redirects after the form submission has been fully processed.

@Changaco
Copy link

@iquito If a server has an open redirect vulnerability then a 302 response to a form submission can result in form data being transmitted to a third party.

For example if a page in the example.org website has a vulnerability that allows an attacker to manipulate form submission, and the website also has an open redirect vulnerability (e.g. https://example.org/redirect?url=https://malicious.com/$form-data returns a 302 with Location: https://malicious.com/$form-data), then the attacker can get the form data.

If the open redirect vulnerability is really bad and preserves the querystring, then the attacker only needs to be able to manipulate the method and action attributes of a form to obtain the data, by setting them to GET and https://example.org/redirect?url=https://malicious.com/ respectively, which will result in a GET request to https://malicious.com/?$form-data.

It seems to me that the "strict" implementation of form-action in Chrome is meant to provide protection against such scenarios involving open redirects. Client-side protections against server-side vulnerabilities is a big part of what CSP is, so the "strict" interpretation of form-action may be surprising and annoying, but it does make sense.

@iquito
Copy link

iquito commented Oct 24, 2018

@Changaco If you make a GET request to https://example.org/redirect?url=https://malicious.com/ the url parameter needs to be urlencoded for it to work, and any form parameters are added with & and are not part of the url parameter to which the redirect happens. So:

Form submit to https://example.org/redirect?url=https%3A%2F%2Fmalicious.com%2F and add form data key=value&key2=value would lead to https://example.org/redirect?url=https%3A%2F%2Fmalicious.com%2F&key=value&key2=value2 , leading to the following variables being defined in the server-side script:

url = https://malicious.com/
key = value
key2 = value2

Then the script makes a 302 redirect to https://malicious.com/. How do you get it to pass the form data? Is there any documentation of this vulnerability? Because this is not a topic on the open redirect vulnerability page you linked to.

@Changaco
Copy link

@iquito The variant of open redirects in which the querystring parameters are forwarded is indeed not mentioned in the OWASP page I linked to. In that variant the vulnerable server-side script parses the url value (https://malicious.com/URL(protocol='https', host='malicious.com', path='/')), then merges the rest of the querystring into it (key=value&key2=value2URL(..., querystring='?key=value&key2=value2')), and finally redirects to that new URL (Location: https://malicious.com/?key=value&key2=value2). Yes, this kind of open redirect is unlikely, but it is possible.

In most cases involving an open redirect to forward data, the attackers can probably execute JavaScript on the vulnerable page containing the form, so they'll choose to encode the form data directly into the redirect URL instead of relying on the server to do it.

@iquito
Copy link

iquito commented Oct 24, 2018

@Changaco At that point you have multiple super heavy vulnerabilities to an extent that I don't think you will find a real world example (especially none using a CSP at the same time). Parsing the query string yourself is not a thing in most server side languages - for example in PHP you would never parse your own query string, and this kind of vulnerability could never work. You likely also need access to the HTML and possibly JS code on the page, which needs other vulnerabilities. At that point the form-action in CSP is not relevant anymore, because somebody has clearly taken over the website.

On the other hand, the current implementation of form-action as implemented in browsers leads to unexpected bugs in most applications, if form-action is heavily restricted (as it should be, usually to the current domain). This will most likely lead developers to compensate and therefore to a too permissive form-action (including any domains the website could ever possibly redirect to), or to no restriction at all, therefore lessening security for all websites and reducing the impact or importance of form-action.

@ptoomey3
Copy link

On the other hand, the current implementation of form-action as implemented in browsers leads to unexpected bugs in most applications, if form-action is heavily restricted (as it should be, usually to the current domain). This will most likely lead developers to compensate and therefore to a too permissive form-action (including any domains the website could ever possibly redirect to), or to no restriction at all, therefore lessening security for all websites and reducing the impact or importance of form-action.

I tend to agree. CSP was never meant to be a holistic solution to every single content exfiltration avenue available to an attacker. A tightly scoped form-action is handy for multiple reasons that aren't just related to content exfiltration protection. But, as it exists today, many sites are forced through lots of hoops and/or forced to disable the directive.

@Changaco
Copy link

To clarify, I also agree that it could make sense for form-action to apply only to the initial destination URL and not to subsequent ones, in order to make it easier to adopt. However my opinion isn't very relevant, because the future of form-action depends on the browsers who currently implement the "strict" version, and I'm not a browser developer.

I must also point out that even if the CSP spec is updated and browsers are modified accordingly, it could take years before the browser versions that implement the strict version of form-action become negligible, so form-action isn't going to become easy to adopt any time soon.

@iquito
Copy link

iquito commented Oct 24, 2018

All the more reason to change it soon. If the spec says that form-action does not apply to 301/302 redirects, then browsers will most likely adapt fairly quickly.

It might even be possible to add something like strict-dynamic used in script-src to form-action for progressive enhancement - basically disabling form-action for old browsers, like having form-action * 'redirect-possible' 'self', where redirect-possible tells new browsers to disregard the catch-all * while old browsers have no restrictions. That would fix the problem completely if included in CSP 3.

@Changaco
Copy link

having form-action * 'redirect-possible' 'self', where redirect-possible tells new browsers to disregard the catch-all * while old browsers have no restrictions.

That's an interesting idea, but its drawback is that it sacrifices long-term cleanliness for short-term gain.

If the spec says that form-action does not apply to 301/302 redirects

I still think that special treatment for 301/302 isn't a good idea. Instead the spec could say that only the URL of the first form submission request should be checked, not the subsequent redirects, either by default or when form-action contains 'unsafe-redirects' or whatever.

@ThrawnCA
Copy link

I still think that special treatment for 301/302 isn't a good idea.

IMO the "open redirect that preserves the query string" you've raised is an example of the server shooting itself in the foot, and outside the scope of what CSP can protect it from.

A 302 is not meant to preserve the original request contents. If the server deliberately re-adds those contents, then it has sabotaged itself beyond what a client-side policy should try to fix.

@mig5
Copy link

mig5 commented Sep 27, 2022

In addition to that, adding more domains to form-action would make my application less secure compared to just 'self', as my application should never POST directly to these domains, it only redirects to them in very specific circumstances without any of the form data. I definitely would like to prevent my website to POST the login form to any domain except 'self'.

I can add even one more addition, in the case of OAuth/OIDC: having to list all the domains that one might've issued an OAuth2.0 client for, in the form-action, actually also introduces an information disclosure vulnerability, in that examining the CSP at the OP can leak implicit information about what domains may be RPs. Obviously any individual RP gives this information away about itself by invoking the OAuth flow, but maybe we don't want to expose that information about all RPs at the OP side.

Finally, this is a semantic point, but I don't like the idea of having to use any attribute referred to as 'unsafe' given that the OAuth2/OIDC specs say that the redirect URL MUST be validated as being that which is associated with the OAuth2.0 client itself, before redirecting the browser to it. An implementation of the OP that does not do this, has a security vulnerability. Therefore, at least in the case of OAuth2/OIDC, correctly implementing the spec is not at all something that a browser should consider 'unsafe' - it lacks context as to whether other safety measures are in place. I think 'ignore-redirect' is a nicer term.

tsileo pushed a commit to tsileo/microblog.pub that referenced this issue Nov 13, 2022
In Chrome, I get the following when trying to use the remote follow form:

    Refused to send form data to 'https://example.com/remote_follow'
    because it violates the following Content Security Policy directive:
    "form-action 'self'".

It seems some browsers (but notably not Firefox) apply the form-action
policy to the redirect target in addition to the initial form
submission endpoint.  See:

    w3c/webappsec-csp#8

In that thread, this workaround is suggested.
djsime1 pushed a commit to djsime1/dj.je that referenced this issue Dec 2, 2022
In Chrome, I get the following when trying to use the remote follow form:

    Refused to send form data to 'https://example.com/remote_follow'
    because it violates the following Content Security Policy directive:
    "form-action 'self'".

It seems some browsers (but notably not Firefox) apply the form-action
policy to the redirect target in addition to the initial form
submission endpoint.  See:

    w3c/webappsec-csp#8

In that thread, this workaround is suggested.

(cherry picked from commit 9db7bdf)
@roberto-cisternino
Copy link

Is the SSL-offloading interpreted as redirect due to the protocol change ?
My CSP form-action fails for this reason I think...

@marek22k
Copy link

marek22k commented Jul 7, 2023

Does anyone know if there is a workaround available in Chrome to make it accept a redirect after the form? (like Firefox) My web application otherwise only works in Firefox, which would be quite annoying for some.

@Sora2455
Copy link

Sora2455 commented Jul 7, 2023

@marek22k You can use a client-side redirect instead of server-side, but that involves an extra delay in redirecting and more complexity in your application.

@marek22k
Copy link

marek22k commented Jul 7, 2023

Mhh, that's dumb. I thought I could maybe use an additional CSP header or something like allow-redirect 'self';?!

Is there any other way to allow the redirect in the CSP? I also don't quite understand why it is blocked? After all, I have allowed self for the form and also redirect only to self (i.e. /something).

@Sora2455
Copy link

Sora2455 commented Jul 7, 2023

@marek22k Essentially the problem (as I understand it, I do not work for a browser vendor) is that the form-action directive is designed to prevent form submissions from being sent to random websites (e.g. because someone injected a form into your page).

"But hang on, redirects don't cause the form data to be sent to the page we're redirecting to? So a redirect after a form submission can't cause form data to be sent to random websites?"

They can if it's a 307 Temporary Redirect/308 Permanent Redirect status code redirect.

"Well, why don't we just block form redirects to unlisted destinations on a 307/308, but allow them on 301/303 redirects?"

I believe the objection here is that no other CSP directive depends on the exact response code, so this could be a great big "Gotcha!" for developers. (Much like the current behaviour).

"Well... alright then, why not add a 'allow-redirects' keyword to the directive or something?"

This thread has been mostly arguing about the exact syntax of such a keyword and what form it would take. e.g. Should it have the 'unsafe' keyword? If the directive isn't present, should redirects be allowed or denied?

"But this issue was lodged over 6 years ago! Why haven't they agreed on a solution since then?!"

Again, don't work for a browser vendor... but I get the impression that this isn't really a priority for them to solve.

@marek22k
Copy link

@Sora2455 Thank you for the explanation.

What I don't understand now is that if it's just to stop traffic from being redirected to another website, why is the redirection to my own website also blocked? I redirect to /[some page] and have self in the CSP.

@roberto-cisternino
Copy link

roberto-cisternino commented Jul 12, 2023

I believe form-action is not usable successfully cross-browser... report-to can be used to obtain some more info from a browser.
Btw, I noted that once csp is enabled the browser behaviour is strict more then ever... things like Google tag manager are not working anymore without specific settings... and CSP fails to provide more info if there is an error... CSP handbook seems to be not mature enouth for production... we are playing the testing game !

@Sora2455
Copy link

@marek22k That sounds like a browser error - if you can make a minimal test case, and it only happens in some browsers, then you might want to lodge a bug against that browser or browsers.

@marek22k
Copy link

marek22k commented Jul 13, 2023

My webapp could be used as an example:
Goto https://dn42-bgplookup.mk16.de/ and enter 12345678 at ASN goto.
CSP: default-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; style-src 'self'; style-src-elem 'self'; img-src 'self';
Chromium shows Refused to send form data to 'https://dn42-bgplookup.mk16.de/asn?asn=12345678' because it violates the following Content Security Policy directive: "form-action 'self'". in the console, Firefox redirects the request as requested.

Reported on https://bugs.chromium.org/p/chromium/issues/detail?id=1464501.

@fredericDelaporte
Copy link

fredericDelaporte commented Oct 2, 2023

marek22k case has some other cause on its server side (protocol downgrade, so if redirects to other than self are refused, it should be blocked), as this can now be seen on its chromium bug report. So, these comments are actually irrelevant and should be hidden as "Resolved" or "Off-topic" in my opinion, included my own comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests