Injecting a JavaScript Attack Vector using CSS Custom Properties

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

This one’s pretty nice I must say: as the syntax for CSS Custom Properties is overly permissive (see here) you can use Custom Properties to store your JavaScript attack vector in. If you then use window.getComputedStyle to extract the contents of the Custom Property (see here) and combine it with a function constructor and an IIFE, it’s possible to execute it.

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

Let this underline the importance of a Content Security Policy to prevent remote script loading script evaluation.

Update: Blocking this “hack” with a proper CSP

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 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:

See the Pen
Injecting a JavaScript attack vector using CSS Custom Properties (with CSP)
by Bramus (@bramus)
on CodePen.

The CSP used is this one:

    content="script-src 'unsafe-inline';"

It works as follows:

  • and are there for the CodePen demo to work
  • 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

That calls for confetti! 🤪

Did this help you out? Like what you see?
Thank me with a coffee.

I don\'t do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

BuymeaCoffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation


  1. Hey Bramus!

    Came to your site after the recent checking on the your recent update. Anyways, great site I’ve been here before as I regularly browse quality sites to get ideas and learn new topics.

    I learned about this a week ago or so myself (I think from a js newsletter) but after seeing your clear demo decided to give it a try as I can just copy/paste the code to a temp site and test.

    Your original and linked CSP article is correct that the following will work to block this attack:

    Content-Security-Policy: script-src 'self'; img-src 'none'

    However that will also block all images. A modified version:

    Content-Security-Policy: script-src 'self';

    Would block just the script. If you try that and view in Chrome it shows: "Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".”

    The most restrictive CSP:

    Content-Security-Policy: default-src 'self'

    blocks this as well

    1. Thanks for your kind words and input Conrad 🙂

      The CSP post I linked to contained an exemplary CSP rule. What I’m more curious about is a CSP that would both allow external scripts (we might still need a legitmate jQuery from for example) and also block the passing of custom property data from the CSS to JS.

      I’m not too sure if an unsafe-inline — which is a bit more restrictive than unsafe-eval — will do in that case, as there’s no call to eval happening…

      Will also try and take a stab at it … perhaps I can find a working combination.

      PS: Fixed that formatting for you there 😉

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.