lillesvin.net

XSS From Mixed Rendering

Most popular website frameworks — be they frontend or backend — have ways to safely handle user input in order to prevent injection attacks like SQLi or XSS. However, they (obviously) don’t have ways to handle it safely for other frameworks.

So when you’re both rendering templates on the server side and the client side (mixed rendering or double rendering), then each needs to be aware of what’s going on on the other side. Take the following templates:

<!-- layout.html -->
<html>
    <head>
        <title>Server Side Rendering</title>
    </head>
    <body>
        <!-- PAGE CONTENT GOES HERE -->
    </body>
</html>

And for the contents of a search results page we have the following raw PHP template:

<!-- search_results.php -->
<h1>Search: <?=htmlspecialchars($_GET['q']);?></h1>
...

This is usually considered safe and secure from XSS when there’s no additional rendering going on on the client side. This is because htmlspecialchars() will entity-encode special characters like <, > and & along with single and double quotes eliminating the possibility of inserting malicious code through the GET parameter q.

So let’s imagine that the frontend team is reworking the UI and introduces Angular into the mix, so now layout.html looks like this:

<html ng-app>
    <head>
        <title>Mixed Rendering</title>
        <script src="https://code.angularjs.org/1.8.0/angular.min.js"></script>
    </head>
    <body>
        <!-- PAGE CONTENT GOES HERE -->
    </body>
</html>

What happens now when someone uses Angular Template Expressions in the search—e.g. {{3*9}}. Or even better, {{constructor.constructor('alert("XSS!")')()}}?

You’ve guessed it. The inclusion of Angular has had the unfortunate side-effect of making a previously safe application vulnerable to XSS.

This is obviously not exclusive to the mix of PHP and Angular. I’ve seen the same issue in a .NET/Razor + Vue application and it can arise in any situation where input is inserted into a template on the server side before being passed on to a client side renderer.

Solution

The very best solution is obviously to not use mixed rendering at all (clear separation of concerns and all that), especially because frontend and backend development doesn’t always happen in the same team—possibly not even the same company. Mixed rendering requires both development teams to be highly aware of what the other team is doing in order to keep the website/application secure.

If entirely foregoing mixed rendering isn’t an option, then the frontend and backend teams need to be extremely aware of what’s going on both server and client side. The above issue could be mitigated on the server side by setting ng-non-bindable on the vulnerable DOM element. This instructs Angular to not interpret template expressions in the contents of that element.

<!-- search_results.php -->
<h1 ng-non-bindable>Search: <?=htmlspecialchars($_GET['q']);?></h1>

But would you expect the frontend team to fully understand what the server side code is doing and make sure every server side interpolation of user input is taken into account? Or is it the responsibility of the backenders to know that a new client side technology has been introduced and how to safely handle user input with it?

There’s really no good solution here—even if frontend and backend is done by the same team. The fact that XSS is introduced retroactively (because the addition of the frontend framework makes previously secure code insecure) makes it all the more difficult. Some people use a global JSON object to pass server variables on to the client side. This doesn’t alleviate the need for communication between the two teams but it makes it a lot more secure when it fails because it would either result in an attempt to access an undefined variable (when backend hasn’t made the variable available) or an unused variable would be defined (when backend has made the variable available but frontend isn’t using it yet). Both outcomes are indisputably preferable to XSS.