At Nextcloud we do employ a pretty strict Content-Security-Policy (CSP). In case you need a quick explanation what CSP is, I’d suggest reading this older blog post of mine.

One of the caveats with the implementation in Nextcloud is that we had to allow 'unsafe-eval' because of our historically grown code base. For example, we use handlebars.js for templating which requires either pre-compiled or 'unsafe-eval'. For us, keeping compatibility with older apps is a high priority and thus we couldn’t just migrate the core templates and break all potential existing apps out there.

So let’s evaluate the risk of 'unsafe-eval' in a Content-Security-Policy. What it effectively means is that your policy won’t be able to protect you against an XSS (Cross-Site-Scripting) vulnerability involving JavaScripts eval() function.

Assuming you have something like the following JavaScript code, CSP with 'unsafe-eval' won’t be able to protect you if an attackers gets the victim to open example.com/?alert(1):

var urlParameter = window.location.search.substr(1);
eval(urlParameter);

XSS with popup as proof of concept

Is this a real world issue at all?

However, passing user-content to eval is arguably a very rare edge case and is better avoided. So can we just stop here and keep 'unsafe-eval' as accepted risk?

Sadly, this would be ill-advised. There’s one major JavaScript framework out there that makes such issues more realistic in the real world: jQuery.

Meet jQuery.globalEval

One of the not so widely known facts of jQuery is that all DOM manipulations through jQuery are basically also passed through jQuery.globalEval which evaluates a script in a global context.

What this means is basically the following: If you do a DOM manipulation using functions such as .html(), then it’s passed through eval(). Thus bypassing the employed CSP.

Let’s take a look at an actual code sample, for a shorter demo I used a CSP nonce to embed the inline JavaScript code. Please note that this approach is entirely insecure and only done for demonstration purposes.

<?php
    header("Content-Security-Policy: default-src 'none'; script-src 'nonce-totally-insecure-nonce-for-demonstration' 'unsafe-eval' https://code.jquery.com")
?>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>
    <script nonce="totally-insecure-nonce-for-demonstration">
        $(document).ready(function() {
            $('#name').html(decodeURIComponent(window.location.search.substr(1)));
        });
    </script>
</head>
<body>
Hello, <span id="name"></span>!
</body>
</html>

Opening that page with ?Lukas would display “Hello, Lukas!” but opening it with ?<script>alert(1)</script> would trigger a popup box:

XSS with popup as proof of concept

A small but useful hardening

To prevent that we can override the jQuery.globalEval. This is something that we have done in the current Nextcloud development branch and will be included in our next release. This is actually rather easy to accomplish, note the jQuery.globalEval = function(){}; in the following code block:

<?php
    header("Content-Security-Policy: default-src 'none'; script-src 'nonce-totally-insecure-nonce-for-demonstration' 'unsafe-eval' https://code.jquery.com")
?>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>
    <script nonce="totally-insecure-nonce-for-demonstration">
        jQuery.globalEval = function(){};
        $(document).ready(function() {
            $('#name').html(decodeURIComponent(window.location.search.substr(1)));
        });
    </script>
</head>
<body>
Hello, <span id="name"></span>!
</body>
</html>

Opening that page now with ?<script>alert(1)</script> would trigger the following CSP warning:

XSS mitigated with CSP

I think that’s a pretty small and easy change that can largely reduce the risk of using 'unsafe-eval' with jQuery applications. Also since the number of developers intentionally using functions such as .html() to execute JavaScript seem marginal at best.