We review how attackers can use a browser’s content security policy to trick users and potentially gather personal information, with a Facebook example.
In an article from August 2014, Pascal Landau describes how to deanonymize Facebook users by brute forcing Content Security Policy (CSP). The idea is an attacker tricks a user who is currently logged into Facebook to go to the attacker’s page. The attacker page has an iframe pointing to https://facebook.com/me with the CSP policy listing multiple Facebook profile pages. If the user is currently logged in, the https://facebook.com/me page automatically redirects to the user’s Facebook profile (e.g. https://facebook.com/pascal.landau1); otherwise, it redirects the user to https://www.facebook.com/login.php. If the user’s profile is in CSP source list, the page is be loaded; otherwise, an error message is reported. To catch the error/success messages, Landau is using postMessage API assigned to the “onerror” and “onload” handlers of the iframe. Therefore, if the correct user profile URL is in the CSP policy and in one of the iframes, the attacker site gets a postMessage saying that the iframe with the certain URL loaded successfully.
The reason for this attack’s success is that the policy violation reports sent by the browsers may be exposing too much information. This may result in gathering personal information such as if a victim is currently logged in to Facebook or any other site, the Facebook profile name of the victim is or any other URL that the victim is currently using.
The situation described by Landau is based on the assumption of using Google Chrome as a browser. There are a couple causes that make the attack possible with Google Chrome:
Therefore, Landau needs to use postMessage API to report success of failure for the iframe.
These two causes, on one hand, allowed Landau to differentiate between the two URLs within www.facebook.com when setting a policy, and on the other hand, forced to use the complex and time consuming binary search to identify the user’s Facebook profile URL. Moreover, because the reports do not provide the exact violation URI, Landau had to use cumbersome postMessage API with event handlers to signify if the resource was loaded successfully.
Interestingly enough, the CSP implementation in various browsers is somewhat different.
Firefox differs from Chrome in two ways:
Content-Security-Policy: frame-src https://www.facebook.com/login.php
allows loading iframes from both https://www.facebook.com/me and https://www.facebook.com/pascal.landau1. The origin source is defined at the host name level and the specific pages do not matter for Firefox.
Consequently, even though Firefox provides more detailed information regarding the violation, the same attack will not work here, because the CSP of the browser simply does not differentiate between the two links: https://www.facebook.com/login.php and https://www.facebook.com/pascal.landau1
Opera behaves similar to Google Chrome in the reporting functionality. It only reports the host of the blocked resource and not the full URL. As for the granularity of the source in the policy, Opera behaves like Firefox, defining allowed source at the host level. Opera, similar to Firefox, does not differentiate between the different pages within www.facebook.com. Opera provides the least level of details when configuring CSP and its reports and, arguably, the highest level of security. Although the detailed URI for the violating resource may be valuable information for security specialists protecting the site, this information may be used to monitor the attacks on the site and respond when necessary.
Safari implements CSP similar to Firefox. It does not differentiate between the different pages within the same domain, like https://www.facebook.com/login.php and https://www.facebook.com/pascal.landau1. As for Internet Explorer, it still does not fully support Content Security Policy as of version 11.
What is the “correct” way of implementing CSP? The definition of CSP by W3C says that the source list should only specify the origin of the document, meaning “scheme+host+port”:
host-source = [ scheme "://" ] host [ port ] scheme = host = "*" / [ "*." ] 1*host-char *( "." 1*host-char ) port = ":" ( 1*DIGIT / "*" )
This definition does not include the full path to the page. Consequently, the source list definition on the Mozilla Developer Network is in line with W3C Candidate Recommendation:
A source list is a string specifying one or more Internet hosts by name or IP address, as well as an optional URL scheme and/or port number. The site’s address may include an optional leading wildcard (the asterisk character, “*”), and you may use a wildcard (again, “*”) as the port number, indicating that all legal ports are valid for the source. The hosts are space-delimited.
Therefore, Google Chrome did not implement the CSP exactly according to the W3C definition. The fact that Chrome differentiates different pages within the same origin when setting source lists in CSP makes users of this browser vulnerable to the attack described by Landau. Other browsers implement the CSP closely to the W3C definition and are therefore not vulnerable to the attack. It would be interesting to know what caused Chrome to make these decisions.
As for the different formats of the violation reports, the W3C Candidate Recommendation says that “this section is non-normative”. Hence, browsers are free to select the format and the level of detail in the report.
A suggestion to the browser maintainers on how to make CSP implementation secure in the case of source lists would be to follow the W3C recommendation more closely. Browsers should only allow specifying the source list at the origin level. As for the granularity of the violation report, both options (specifying the full URL and specifying only the violating origin) are secure. However, the developers and security specialists would prefer to have more information about the attack. So providing a full URL of the violating resource will probably be preferable.