A Bypass of the Firefox POST Redirection Bug

I’m happy to report that we have found a way of bypassing the Firefox POST redirection bug discussed in the previous post, obviating the need for code changes to cope with the redirection replay by Firefox when the user clicks the back button. While waiting for the bug to be fixed, this will simplify the implementation of web apps that rely on POST redirection, including apps that use cryptographic authencation or federated login. We have revised again the sample web app demoed at the last IIW, this time to simplify it by taking advantage of the bug bypass.

Recap of the bug and its impact on cryptographic authentication

In a multipage web application that uses cryptographic authentication, JavaScript POST redirection can be used to convey a challenge from the server to the browser as follows. The server responds to the submission of a registration form by downloading a JavaScript script that contains a registration challenge, embedded in a web page with no displayable content. The script generates and stores a key pair, signs the challenge with the private key, and sends the public key and the signature to the server in an HTTP POST request, by constructing and submitting a form. Subsequently the server responds to the submission of a login form by downloading a script that signs a login challenge with the stored private key and sends the signature to the server in a POST request, again by submitting a form.

Chrome, Edge, IE and Safari treat the JavaScript form submission as a JavaScript POST redirection. This means that if the user clicks the browser back button in a welcome page downloaded in response to the JavaScript form submission or to a subsequent HTTP 303 redirection (HTTP redirection being a best practice after any POST submission), the browser goes back to the page containing the registration or login form. In Firefox, by contrast, a back button click in the welcome page causes a replay of the script. Although we encountered the bug in the context of cryptographic authentication, it could impact any kind of application that uses JavaScript POST redirection. It could have dire consequences in a financial application if the replay caused a payment transaction to be executed twice.

Potential impact on federated login

We found the bug bypass as we were trying to see if the Firefox bug also impacts federated login protocols such as SAML, OpenID, OAuth or OpenID Connect, which make extensive use of redirection.

In those protocols the relying party (RP) redirects the browser to the identity provider (IdP), which authenticates the user and redirects the browser back to the RP. OAuth 2.0, as stated in Section 1.7 of the specification, does not specify which kind of redirection must be used, and uses HTTP 302 in the examples. OpenID Connect is a profile and extension of OAuth 2.0, and also uses HTTP 302 in examples of redirection. But it also specifies an OAuth 2.0 Form Post Response Mode, where parameters are “encoded as HTML form values that are auto-submitted in the User Agent, and thus are transmitted via the HTTP POST method”, without prescribing how the form is to be submitted. Implementations of OAuth 2.0 and OpenID Connect may be impacted by the Firefox bug if they use JavaScript POST Redirection.

The older OpenID 2.0 (2007) and SAML 2.0 (2005) describe a JavaScript POST redirection method more explicitly. The POST request is sent by submitting an HTML form that is downloaded from the server. SAML provides an example (page 26) where the form is submitted by the onload attribute of the body tag:

<body onload="document.forms[0].submit()">

This is an old-fashioned method of using JavaScript to send an HTTP POST request by submitting a form. These days, the advice found in developer forums is instead to use JavaScript or jQuery to construct and submit the form:

The bug bypass

We tried the old-fashioned method of the SAML example: instead of constructing the form using JavaScript, we downloaded the form and used JavaScript to assign values to the inputs; and instead of submitting the form after assigning the values, we used the onload attribute of the body tag to do it. To our surprise, this bypassed the Firefox bug: clicking the back button after the redirection took us to the page containing the registration or login form, in Firefox as in the other browsers.

But what was it exactly that bypassed the bug? After more experimentation, it turned out that what mattered was that the form was submitted by the event handler of the load event of the window. The bug could be bypassed simply by replacing

form.submit();

with

window.onload = function () { form.submit(); };

after constructing the form using JavaScript.

A DOM shortcoming

We have not looked at the source code to see what exactly causes Firefox to replay the JavaScript redirection, but the root cause is clear. The DOM allows a script embedded in the current page to navigate to a target URL by means of a GET request in two different ways: location.href = URL; and location.replace = URL. Using location.replace causes the URL of the current page to be replaced with the target URL in the URL history of the browser tab, which amounts to a redirection. Using location.href adds the target URL to the history after the current URL, as if the user had clicked a link.

The DOM also allows a script to navigate to a target URL by means of a POST request. This is accomplished by submitting a form with “post” as the value of the “method” attribute, and the target URL as the value of the “action” attribute. But there is no way of specifying if the target URL should replace the current URL in the history or be added to the history after the current URL. This forces browsers to make a guess as to what should be done in different circumstances. It seems that browsers other than Firefox replace the current URL if the form is submitted before the page has finished loading, while Firefox does not. (An alternative explanation for the difference in behavior could be that the other browsers do not add the URL of the current page to the history until the page has loaded, but we have verified that this is not the case by throwing an exception in a script that runs before the page has loaded and observing that the URL of the current page is already in the history.) The bug bypass shows that Firefox replaces the current URL if the form is submitted by the event handler of the load event, as the other browsers also seem to do. On the other hand, it seems clear that no browsers replace the current URL when a form is submitted after the page has loaded and any event handler registered for the load event has run.

Leave a Reply

Your email address will not be published.