Forcing Firefox to Execute XSS Payloads during 302 Redirects

Submitted by quentin on Wed, 09/30/2020 - 14:49

Initial Discovery

During a recent engagement I identified an open redirect where a GET parameter would be reflected as-is in the HTTP response Location header without any kind of sanitization. Something similar to this:

open_redirect

Trying multiple kinds of injections, I discovered that newlines and carriage returns characters could be inserted, leading to header injection:

header_injection

Even more interesting, we can inject arbitrary content in the HTTP response body by inserting two newline characters, leading to reflected cross-site scripting:

body_inject

However, modern browsers (Google Chrome, Internet Explorer, Firefox) do not interpret the HTTP response body if the HTTP response status code is a 302, so our cross-site scripting payload is useless. Time to find a bypass !

Prior Work

By searching for prior bypasses, I stumbled upon this blog post where Fortinet describes how they bypassed the execution block by setting the Location header to a URI starting with 'mailto://'. Bugcrowd forums also provides some insight into bypasses that may have worked in the past. And this excellent HackerOne report on XSS affecting Twitter, where they used a Location header starting with '//x:1/' definitely sent me in the right direction.

Let's Fuzz

Given that none of the already documented bypasses worked, I decided to write a dumb fuzzer that would generate a list of URLs and open them with xdg-open. To do so, I downloaded the IANA URI schemes list and generated a list of URLs following this format: http://acme.corp/?redir=[URI_SCHEME]://gremwell.com%0A%0A[XSS_PAYLOAD]. Google Chrome and Firefox were tested in this way, Internet Explorer was also tested but with a PowerShell script rather than simply calling xdg-open.

I then spent quite some time closing browser tabs, hoping to be greeted with an alert box :)

A Valid Candidate

Two candidates out of the full IANA URI scheme list worked, and only on Firefox:

  • ws:// (WebSocket)
  • wss:// (Secure WebSocket).

It simply looks like this:

valid_bypass

Opening the link in the latest version of Firefox (version 81 at the time of writing) and we see we are executing JavaScript under the right domain, without being redirected:

xss_trigger

Proof-of-Concept

If you want to test this at home, you can download the 302_server script. It will launch a Python3 HTTP server on port 8000, mimicking the behavior I just described.

Update - October 1st 2020

Sergey Bobrov just pointed out that using an empty Location header will work to force Google Chrome to execute the payload. Nice find !

Update - October 2nd 2020

Maxim Rupp just pointed out that using an resource:// URI in the Location header will work to force Firefox 81 to execute the payload. Nice find !

Contacts

+32 (0) 2 215 53 58

Gremwell BVBA
Sint-Katherinastraat 24
1742 Ternat
Belgium
VAT: BE 0821.897.133.