Latte Is Synonymous with Safety

Latte is the only PHP templating system with effective protection against the critical Cross-site Scripting (XSS) vulnerability. This is thanks to the so-called context-sensitive escaping. Let's talk,

  • what is the principle of the XSS vulnerability and why is it so dangerous
  • what makes Latte so effective in defending against XSS
  • why Twig, Blade and other templates can be easily compromised

Cross-Site Scripting (XSS)

Cross-site Scripting (XSS for short) is one of the most common vulnerabilities in websites and a very dangerous one at that. It allows an attacker to insert a malicious script (called malware) into a foreign site that executes in the browser of an unsuspecting user.

What can such a script do? For example, it can send arbitrary content from the compromised site to the attacker, including sensitive data displayed after login. It can modify the page or make other requests on behalf of the user. For example, if it were webmail, it could read sensitive messages, modify the displayed content, or change settings, e.g., turn on forwarding copies of all messages to the attacker's address to gain access to future emails.

This is also why XSS tops the list of the most dangerous vulnerabilities. If a vulnerability is discovered on a website, it should be removed as soon as possible to prevent exploitation.

How Does the Vulnerability Arise?

The error occurs in the place where the web page is generated and the variables are printed. Imagine that you are creating a search page, and at the beginning there will be a paragraph with the search term in the form:

echo '<p>Search results for <em>' . $search . '</em></p>';

An attacker can write any string, including HTML code like <script>alert("Hacked!")</script>, into the search field and thus into the $search variable. Since the output is not sanitized in any way, it becomes part of the displayed page:

<p>Search results for <em><script>alert("Hacked!")</script></em></p>

Instead of outputting the search string, the browser executes JavaScript. And thus the attacker takes over the page.

You might argue that putting code into a variable will indeed execute JavaScript, but only in the attacker's browser. How does it get to the victim? From this perspective, we can distinguish several types of XSS. In our search page example, we are talking about reflected XSS. In this case, the victim needs to be tricked into clicking on a link that contains malicious code in the parameter:

https://example.com/?search=<script>alert("Hacked!")</script>

Although it requires some social engineering to make the user to access the link, it's not difficult. Users click on links, whether in emails or on social media, without much thought. And the fact that there's something suspicious in the address can be masked by URL shortener, so the user only sees bit.ly/xxx.

However, there is a second and much more dangerous form of attack known as stored XSS or persistent XSS, where an attacker manages to store malicious code on the server so that it is automatically inserted into certain pages.

An example of this is websites where users post comments. An attacker sends a post containing code and it is saved on the server. If the site is not secure enough, it will then run in every visitor's browser.

It would seem that the point of the attack is to get the <script> string into the page. In fact, there are many ways to embed JavaScript. Let's take an example of embedding using an HTML attribute. Let's have a photo gallery where you can insert a caption to the images, which is printed in the alt attribute:

echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';

An attacker just needs to insert a cleverly constructed string " onload="alert('Hacked!') as a label, and if the output is not sanitized, the resulting code will look like this:

<img src="photo0145.webp" alt="" onload="alert('Hacked!')">

The fake onload attribute now becomes part of the page. The browser will execute the code it contains as soon as the image is downloaded. Hacked!

How to Defend Against XSS?

Any attempts to detect an attack using a blacklist, such as blocking the <script> string, etc. are insufficient. The basis of a workable defense is consistent sanitization of all data printed inside the page.

First of all, this involves replacing all characters with special meaning with other matching sequences, which is called escaping in slang (the first character of the sequence is called the escape character, hence the name). For example, in HTML text, the character < has a special meaning, which, if it is not to be interpreted as the beginning of a tag, must be replaced by a visually corresponding sequence, the so-called HTML entity &lt;. And the browser prints a character.

It is very important to distinguish the context in which the data is output. Because different contexts sanitize strings differently. Different characters have special meaning in different contexts. For example, escaping in HTML text, in HTML attributes, inside some special elements, etc. is different. We'll discuss this in detail in a moment.

It's best to perform escaping directly when the string is written out in the page, ensuring that it is actually done, and done just once. It is best if the processing is handled automatically directly by the templating system. Because if the treatment is not done automatically, the programmer may forget about it. And one omission means the site is vulnerable.

However, XSS doesn't just affect the output of data in templates, but also other parts of the application that must properly handle untrusted data. For example, JavaScript in your application must not use innerHTML in conjunction with them, but only innerText or textContent. Special care should be taken with functions that evaluate strings like JavaScript, which is eval(), but also setTimeout(), or using setAttribute() with event attributes like onload, etc. But this goes beyond the scope covered by templates.

The ideal 3-point defense:

  1. Recognize the context in which the data is being output
  2. sanitizes the data according to the rules of that context (i.e. “context-aware”)
  3. does this automatically

Context-Aware Escaping

What exactly is meant by the word context? It is a place in the document with its own rules for treating the data to be output. It depends on the type of document (HTML, XML, CSS, JavaScript, plain text, …) and may vary in specific parts of the document. For example, in an HTML document, there are many such places (contexts) where very different rules apply. You might be surprised how many there are. Here are the first four:

<p>#text</p>
<img src="#attribute">
<textarea>#rawtext</textarea>
<!-- #comment -->

The initial and basic context of an HTML page is HTML text. What are the rules here? The special meaning characters < and & represent the beginning of a tag or entity, so we have to escape them by replacing them with the HTML entity (< with &lt;, & with &amp).

The second most common context is the value of an HTML attribute. It differs from text in that the special meaning here goes to the quotation mark " or ' that delimits the attribute. This needs to be written as an entity so that it is not seen as the end of the attribute. On the other hand, the < character can be safely used in an attribute because it has no special meaning here; it cannot be understood as the beginning of a tag or comment. But beware, in HTML you can write attribute values without quotes, in which case a whole range of characters have special meaning, so it is another separate context.

It may surprise you, but special rules apply inside the <textarea> and <title> elements, where the < character need not (but can) be escaped unless followed by /. But that's more of a curiosity.

It's interesting inside the HTML comments. Here, HTML entities are not used for escaping. There is no specification even stating how to escaping in comments. You just have to follow the somewhat curious rules and avoid certain character combinations in them.

Contexts can also be layered, which happens when we embed JavaScript or CSS into HTML. This can be done in two different ways, by element or attribute:

<script>#js-element</script>
<img onclick="#js-attribute">

<style>#css-element</style>
<p style="#css-attribute"></p>

Two ways and two different kinds of escaping data. Within the <script> and <style> elements, as in the case of HTML comments, escaping using HTML entities is not performed. When escaping data inside these elements, there is only one rule: the text must not contain the sequence </script and </style respectively.

On the other hand, the style and on*** attributes are escaped using HTML entities.

And, of course, inside embedded JavaScript or CSS, the escaping rules of those languages apply. So a string in an attribute such as onload is escaped first according to JS rules and then according to HTML attribute rules.

Ugh… As you can see, HTML is a very complex document with layers of contexts, and without knowing exactly where I'm outputting the data (i.e. in what context), there's no telling how to do it right.

Do You Want an Example?

Let's have a string Rock'n'Roll.

If you output it in HTML text, you don't need to make any substitutions in this case, because the string doesn't contain any character with special meaning. The situation is different if you write it inside an HTML attribute enclosed in single quotes. In this case, you need to escaping the quotes to HTML entities:

<div title='Rock&apos;n&apos;Roll'></div>

This was easy. A much more interesting situation occurs when the context is layered, for example if the string is part of JavaScript.

So first we write it out into the JavaScript itself. That is, we wrap it in quotes and at the same time escaping the quotes contained in it using the \ character:

'Rock\'n\'Roll'

We can add a function call to make the code do something:

alert('Rock\'n\'Roll');

If we insert this code into an HTML document using <script>, we don't need to modify anything else, because the forbidden </script sequence is not present:

<script> alert('Rock\'n\'Roll'); </script>

However, if we want to insert it into an HTML attribute, we still need to escaping quotes to HTML entities:

<div onclick='alert(&apos;Rock\&apos;n\&apos;Roll&apos;)'></div>

However, nested context doesn't have to be just JS or CSS. It is also commonly a URL. Parameters in URLs are escaped by converting special characters to sequences starting with %. Example:

https://example.org/?a=Jazz&b=Rock%27n%27Roll

And when we output this string in an attribute, we still apply escaping according to this context and replace & with &amp:

<a href="https://example.org/?a=Jazz&amp;b=Rock%27n%27Roll">

If you've read this far, congratulations, it's been exhausting. Now you have a good idea of what contexts and escaping are. And you don't have to worry about it being complicated. Latte does that for you automatically.

Latte vs Naive Systems

We've shown how to properly escaping in an HTML document and how crucial it is to know the context, i.e., where you're outputting the data. In other words, how context sensitive escaping works. While this is a prerequisite for functional XSS defense, Latte is the only templating system for PHP that does this.

How is this possible when all systems today claim to have automatic escaping? Automatic escaping without knowing the context is a bit of bullshit that creates a false sense of security.

Templating systems like Twig, Laravel Blade and others don't see any HTML structure in the template. Therefore, they don't see contexts either. Compared to Latte, they are blind and naive. They only handle their own markup, everything else is an irrelevant character stream to them:

░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░
░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░{{ text }}░░░░
- in text: <span>{{ text }}</span>
- in tag: <span {{ text }} ></span>
- in attribute: <span title='{{ text }}'></span>
- in unquoted attribute: <span title={{ text }}></span>
- in attribute containing URL: <a href="{{ text }}"></a>
- in attribute containing JavaScript: <img onload="{{ text }}">
- in attribute containing CSS: <span style="{{ text }}"></span>
- in JavaScriptu: <script>var = {{ text }}</script>
- in CSS: <style>body { content: {{ text }}; }</style>
- in comment: <!-- {{ text }} -->

Naive systems just mechanically convert < > & ' " characters to HTML entities, which is a valid way of escaping in most uses, but far from always. Thus, they cannot detect or prevent various security holes, as we will show below.

Latte sees the template the same way you do. It understands HTML, XML, recognizes tags, attributes, etc. And because of this, it distinguishes between contexts and treats data accordingly. So it offers really effective protection against the critical Cross-site Scripting vulnerability.

Live Demonstration

On the left you can see the template in Latte, on the right is the generated HTML code. The $text variable is output several times, each time in a slightly different context. And therefore escaped a bit differently. You can edit the template code yourself, for example change the content of the variable etc. Try it:

{* TRY TO EDIT THIS TEMPLATE *}
{var $text = "Rock'n'Roll"}
- <span>{$text}</span>
- <span title='{$text}'></span>
- <span title={$text}></span>
- <img onload="{$text}">
- <script>var = {$text}</script>
- <!-- {$text} -->
- <span>Rock'n'Roll</span>
- <span title='Rock&apos;n&apos;Roll'></span>
- <span title="Rock&apos;n&apos;Roll"></span>
- <img onload="&quot;Rock&apos;n&apos;Roll&quot;">
- <script>var = "Rock'n'Roll"</script>
- <!-- Rock'n'Roll -->

Isn't that great! Latte does context-sensitive escaping automatically, so the programmer:

  • doesn't have to think or know how to escape data
  • can't be wrong
  • can't forget about it

These aren't even all the contexts that Latte distinguishes when outputting and for which it customizes data treatment. We'll go through more interesting cases now.

How to Hack Naive Systems

We will use a few practical examples to show how important context differentiation is and why naive templating systems do not provide sufficient protection against XSS, unlike Latte. We will use Twig as a representative of a naive system in the examples, but the same applies to other systems.

Attribute Vulnerability

Let's try to inject malicious code into the page using the HTML attribute as we showed above. Let's have a template in Twig displaying an image:

<img src={{ imageFile }} alt={{ imageAlt }}>

Note that there are no quotes around the attribute values. The coder may have forgotten them, which just happens. For example, in React, the code is written like this, without quotes, and a coder who is switching languages can easily forget about the quotes.

The attacker inserts a cleverly constructed string foo onload=alert('Hacked!') as the image caption. We already know that Twig can't tell if a variable is being printed in a stream of HTML text, inside an attribute, inside an HTML comment, etc.; in short, it doesn't distinguish between contexts. And it just mechanically converts < > & ' " characters to HTML entities. So the resulting code will look like this:

<img src=photo0145.webp alt=foo onload=alert(&#039;Hacked!&#039;)>

A security hole has been created!

A fake onload attribute has become part of the page and the browser executes it immediately after downloading the image.

Now let's see how Latte handles the same template:

<img src={$imageFile} alt={$imageAlt}>

Latte sees the template the same way you do. Unlike Twig, it understands HTML and knows that a variable is printed as an attribute value that is not in quotes. That's why it adds them. When an attacker inserts the same caption, the resulting code will look like this:

<img src="photo0145.webp" alt="foo onload=alert(&apos;Hacked!&apos;)">

Latte successfully prevented XSS.

Printing a Variable in JavaScript

Thanks to context-sensitive escaping, it is possible to use PHP variables natively inside JavaScript.

<p onclick="alert({$movie})">{$movie}</p>

<script>var movie = {$movie};</script>

If $movie variable stores 'Amarcord & 8 1/2' string it generates the following output. Notice different escaping used in HTML and JavaScript and also in onclick attribute:

<p onclick="alert(&quot;Amarcord &amp; 8 1\/2&quot;)">Amarcord &amp; 8 1/2</p>

<script>var movie = "Amarcord & 8 1\/2";</script>

Latte automatically checks whether the variable used in the src or href attributes contains a web URL (ie protocol HTTP) and prevents the writing of links that may pose a security risk.

{var $link = 'javascript:attack()'}

<a href="{$link}">click here</a>

Writes:

<a href="">click here</a>

The check can be turned off using a filter nocheck.

Limits of Latte

Latte is not a complete XSS protection for the entire application. We would be unhappy if you stopped to think about security when using Latte. The goal of Latte is to ensure that an attacker cannot alter the structure of a page, tamper with HTML elements or attributes. But it does not check the content correctness of the data being output. Or the correctness of JavaScript behavior. That's beyond the scope of the templating system. Verifying the correctness of data, especially those entered by the user and thus untrusted, is an important task for the programmer.

version: 3.0