I recently discovered a method for obtaining the web proxy credentials of visitors to my website. It abuses Mozilla’s implementation of a technology called “Content Security Policy“, which is currently enabled in Firefox 4 and up. CSP was designed to allow website authors to describe which origins are valid locations for certain types of content on their pages, placing that information in an HTTP response header.
For example, I might write a policy for this website which states that images are only valid when fetched from grepular.com, or google.com. If one of my pages then contains an tag which points at facebook.com, Firefox will simply refuse to fetch that image. The point of this technology is to help defend against XSS, CSRF and similar attacks.
One of the features of CSP is that you can specify a report URI in the policy. If the browser detects that the page is attempting to violate its CSP policy, it will perform a HTTP request in the background to the URI specified in the policy header, containing information regarding the violation. Here is an example of a CSP HTTP response header which contains a report URI, and only allows images from my domain, and google.com:
X-Content-Security-Policy: report-uri /csp-report.cgi; allow 'self'; img-src 'self' *.google.com
The HTTP request to the report URI is a POST request. The POSTed data contains a chunk of JSON. That JSON contains a number of items including the URL of the request which was blocked, the particular part of the policy which was violated, and most importantly, the full set of HTTP headers that would have been sent if the request wasn’t blocked. (These have been removed in Firefox 6)
At this point, some of you might have guessed where I’m going with this. When you configure your browser to use a web proxy which requires authentication, they generally tend to authenticate by adding a “Proxy-Authorization” HTTP header to all requests. The web proxy software strips this header before forwarding on the request, so as not to leak the credentials to the destination website. However, when a CSP report HTTP request is generated, the Proxy-Authorization header is stuffed inside the JSON in the POST request, along with the rest of the headers! Web proxies don’t expect the proxy authentication details to be in the POST data, so don’t strip it.
To test my theory, I modified my website to purposefully violate my CSP policy so Firefox 4 and 5 would always generate a CSP report. It’s completely transparent, so nobody would notice. I then wrote a CGI script which would parse the CSP report, look for the Proxy-Authorization data, and log it. It captured three headers of the following format:
Proxy-Authorization: Basic BASE64_ENCODED_PLAIN_TEXT_USERNAME_AND_PASSWORD==
The login details that the user uses to connect to their web proxy are trivially decodable from this data. I instantly deleted this data once I confirmed it had worked, and have now disabled the logging of it.
It also captured several headers which look like this:
Proxy-Authorization: NTLM BASE64_ENCODED_SECURITY_TOKEN_WHICH_I_DONT_THINK_I_CAN_DO_ANYTHING_WITH==
I also noticed that certain requests contained Cookie headers (not set by my website), which were subsequently stripped by the web proxy. I could still access these stripped headers as they were packaged up in the JSON with the CSP report. I’m not sure if they contained anything useful though. Several of them had the name BCSI-CS-SOME_16_CHAR_HEX_STRING which looks to be to do with some sort of functionality of one of the https://www.bluecoat.com/ proxy products.
So what’s the solution to this problem? The CSP reports should only send HTTP headers from a trusted list. It’s not safe or necessary to send the Proxy-Authorization header, nor the Cookie headers. There are probably other headers which will cause problems like this as well. Especially for people using anonymising proxies. I’m not actually convinced that sending any of the headers along with the report is necessary, although I’m sure the people who wrote the spec must have had a reason. The only thing that is needed is the URL which failed, and the specific part of the policy which was broken.
If you’re stuck on an old version of Firefox 4/5, are using an authenticated web proxy and are worried about this issue, you can disable Content-Security-Policy, by setting “security.csp.enable” to “false” in “about:config”