Injecting a JavaScript Attack Vector using CSS Custom Properties
Tags: css, customproperties, javascript, csp
Published: 2020-12-23 12:08:46

---
title: Injecting a JavaScript Attack Vector using CSS Custom Properties
published: true
description: As the syntax for CSS Custom Properties is overly permissive it's possible to store a JavaScript attack vector in them …
tags: css, customproperties, javascript, csp
cover_image: https://www.bram.us/wordpress/wp-content/uploads/2020/12/javascript-attack-with-custom-properties.png
---

Earlier this week I saw this tweet by @Sansec float by:

{% twitter 1336614850047381506 %}

This one is pretty nice I must say, and a bit hard to find: 

1. As [the syntax for CSS Custom Properties is overly permissive](https://www.bram.us/2020/12/09/what-values-can-you-put-in-css-custom-property/) you can use Custom Properties to store your JavaScript attack vector in.

    ```css
    :root {
      --script: console.log('Attack!');
    }
    ```

2. If you then [use `window.getComputedStyle` to extract the contents out of the Custom Property](https://www.bram.us/2020/03/30/pass-data-from-css-to-javascript-with-css-variables/) and combine it with a function constructor and an IIFE, it’s possible to execute it.

    ```js
    const scriptContents = window.getComputedStyle(document.documentElement)?.getPropertyValue('--script');
    new Function(scriptContents)();
    ```
---

Here’s a pen that loads a remote confetti script using the method described:

{% codepen https://codepen.io/bramus/pen/ZEpJVee default-tab=result %}

---

To prevent this remote evaluation, you'll need a proper [Content Security Policy](https://developers.google.com/web/fundamentals/security/csp) to do so. It took me some time to figure out — as I’m no CSP expert — but turns out the `unsafe-inline` keyword in the CSP’s source list is enough to block the execution of the JS-IN-CSS.

As a reminder, [here](https://developers.google.com/web/fundamentals/security/csp#implementation_details) are the four allowed keywords:

- **`'none'`**, as you might expect, matches nothing.
- **`'self'`** matches the current origin, but not its subdomains.
- **`'unsafe-inline'`** allows inline JavaScript and CSS.
- **`'unsafe-eval'`** allows text-to-JavaScript mechanisms like eval.

I first thought `unsafe-inline` would be insufficient here as the code does not call eval, but apparently a function constructor is _(correctly!)_ considered equally harmful, and therefore also blocked.

Here’s an updated demo that blocks the script evaluation:

{% codepen https://codepen.io/bramus/pen/WNGZdjb default-tab=result %}

The CSP used is this one:

```html
<meta
  http-equiv="Content-Security-Policy"
  content="script-src 
    https://cpwebassets.codepen.io
    https://cdpn.io
    https://cdn.jsdelivr.net
    'unsafe-inline'
  ;"
>
```

It works as follows:

- `https://cpwebassets.codepen.io` and `https://cdpn.io` are there for the CodePen demo to work
- `https://cdn.jsdelivr.net` is there to allow legitimate loading of scripts — such as a jQuery you might need — from that CDN.
- `unsafe-inline` is the one that prevents the execution of the JS-IN-CSS defined script by blocking the call to the function constructor.

Now that calls for confetti! 🤪

---

_💁‍♂️ This post [first appeared on my blog bram.us](https://www.bram.us/2020/12/22/injecting-a-javascript-attack-vector-using-css-custom-properties/). Want to stay in the loop? Here's how to do so:_

- _[Follow @bramus on Twitter](https://twitter.com/bramus)_
- _[Follow @bramusblog on Twitter](https://twitter.com/bramusblog)_
- _[Follow bram.us using RSS](https://bram.us/rss)_