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.