Latte est synonyme de sécurité

Latte est le seul système de templates pour PHP avec une protection efficace contre la vulnérabilité critique Cross-site Scripting (XSS). Et ce, grâce à l'échappement contextuel. Nous allons expliquer,

  • quel est le principe de la vulnérabilité XSS et pourquoi elle est si dangereuse
  • pourquoi Latte est si efficace pour se défendre contre XSS
  • comment il est facile de créer une faille de sécurité dans les templates Twig, Blade et autres

Cross-site Scripting (XSS)

Le Cross-site Scripting (abrégé XSS) est l'une des vulnérabilités les plus courantes des sites web et pourtant très dangereuse. Elle permet à un attaquant d'insérer un script malveillant (appelé malware) dans une page étrangère, qui s'exécutera dans le navigateur d'un utilisateur non averti.

Que peut faire un tel script ? Il peut par exemple envoyer à l'attaquant n'importe quel contenu de la page compromise, y compris des données sensibles affichées après connexion. Il peut modifier la page ou effectuer d'autres requêtes au nom de l'utilisateur. S'il s'agissait par exemple d'un webmail, il pourrait lire des messages sensibles, modifier le contenu affiché ou reconfigurer les paramètres, par ex. activer le transfert de copies de tous les messages vers l'adresse de l'attaquant pour accéder également aux futurs e-mails.

C'est pourquoi XSS figure en tête des classements des vulnérabilités les plus dangereuses. Si une vulnérabilité apparaît sur un site web, il est nécessaire de la corriger le plus rapidement possible pour éviter toute exploitation.

Comment naît la vulnérabilité ?

L'erreur se produit à l'endroit où la page web est générée et où les variables sont affichées. Imaginez que vous créez une page de recherche, et au début il y aura un paragraphe avec le terme recherché sous la forme :

echo '<p>Résultats de la recherche pour <em>' . $search . '</em></p>';

Un attaquant peut entrer dans le champ de recherche et donc dans la variable $search n'importe quelle chaîne, y compris du code HTML comme <script>alert("Hacked!")</script>. Comme la sortie n'est pas traitée, elle devient partie intégrante de la page affichée :

<p>Résultats de la recherche pour <em><script>alert("Hacked!")</script></em></p>

Au lieu d'afficher la chaîne recherchée, le navigateur exécute le JavaScript. Et ainsi, l'attaquant prend le contrôle de la page.

Vous pourriez objecter qu'en insérant du code dans la variable, le JavaScript s'exécute, mais seulement dans le navigateur de l'attaquant. Comment atteint-il la victime ? De ce point de vue, nous distinguons plusieurs types de XSS. Dans notre exemple de recherche, nous parlons de reflected XSS. Ici, il faut encore inciter la victime à cliquer sur un lien qui contiendra le code malveillant dans le paramètre :

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

Inciter l'utilisateur à cliquer sur le lien nécessite certes une certaine ingénierie sociale, mais ce n'est pas très compliqué. Les utilisateurs cliquent sur les liens, que ce soit dans les e-mails ou sur les réseaux sociaux, sans trop réfléchir. Et le fait qu'il y ait quelque chose de suspect dans l'adresse peut être masqué à l'aide d'un raccourcisseur d'URL, l'utilisateur ne voit alors que bit.ly/xxx.

Cependant, il existe une deuxième forme d'attaque, beaucoup plus dangereuse, appelée stored XSS ou persistent XSS, où l'attaquant réussit à stocker le code malveillant sur le serveur de manière à ce qu'il soit automatiquement inséré dans certaines pages.

Un exemple sont les pages où les utilisateurs écrivent des commentaires. L'attaquant envoie une contribution contenant du code et celle-ci est stockée sur le serveur. Si les pages ne sont pas suffisamment sécurisées, il s'exécutera alors dans le navigateur de chaque visiteur.

On pourrait penser que le cœur de l'attaque consiste à introduire la chaîne <script> dans la page. En réalité, les méthodes d'insertion de JavaScript sont nombreuses. Montrons par exemple une insertion via un attribut HTML. Ayons une galerie de photos où l'on peut ajouter une légende aux images, qui sera affichée dans l'attribut alt:

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

Il suffit à l'attaquant d'insérer comme légende une chaîne habilement construite " onload="alert('Hacked!') et si l'affichage n'est pas traité, le code résultant ressemblera à ceci :

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

L'attribut onload falsifié fait désormais partie de la page. Le navigateur exécute le code qu'il contient dès le téléchargement de l'image. Piraté !

Comment se défendre contre XSS ?

Toute tentative de détection d'attaque à l'aide d'une liste noire, comme bloquer la chaîne <script> etc., est insuffisante. La base d'une défense fonctionnelle est la sanitisation rigoureuse de toutes les données affichées à l'intérieur de la page.

Il s'agit principalement de remplacer tous les caractères ayant une signification spéciale par d'autres séquences correspondantes, ce qu'on appelle familièrement l'échappement (le premier caractère de la séquence est appelé caractère d'échappement, d'où le nom). Par exemple, dans le texte HTML, le caractère < a une signification spéciale, et s'il ne doit pas être interprété comme le début d'une balise, nous devons le remplacer par une séquence visuellement correspondante, appelée entité HTML &lt;. Et le navigateur affichera le signe inférieur.

Il est très important de distinguer le contexte dans lequel nous affichons les données. Car dans différents contextes, les chaînes sont sanitisées différemment. Dans différents contextes, différents caractères ont une signification spéciale. Par exemple, l'échappement diffère dans le texte HTML, dans les attributs HTML, à l'intérieur de certains éléments spéciaux, etc. Nous allons examiner cela en détail dans un instant.

Le traitement est préférable de le faire directement lors de l'affichage de la chaîne dans la page, garantissant ainsi qu'il est réellement effectué et effectué une seule fois. Le mieux est que le traitement soit assuré automatiquement directement par le système de templates. Car si le traitement n'est pas automatique, le programmeur peut l'oublier. Et une seule omission signifie que le site est vulnérable.

Cependant, XSS ne concerne pas seulement l'affichage des données dans les templates, mais aussi d'autres parties de l'application qui doivent traiter correctement les données non fiables. Par exemple, il est nécessaire que le JavaScript de votre application n'utilise pas innerHTML en relation avec elles, mais seulement innerText ou textContent. Une attention particulière doit être portée aux fonctions qui évaluent les chaînes comme du JavaScript, ce qui est eval(), mais aussi setTimeout(), ou l'utilisation de la fonction setAttribute() avec des attributs événementiels comme onload etc. Mais cela dépasse le domaine couvert par les templates.

La défense idéale en 3 points :

  1. reconnaît le contexte dans lequel les données sont affichées
  2. sanitise les données selon les règles du contexte donné (c'est-à-dire “contextuellement”)
  3. le fait automatiquement

Échappement contextuel

Que signifie exactement le mot contexte ? C'est un endroit dans le document avec ses propres règles pour traiter les données affichées. Il dépend du type de document (HTML, XML, CSS, JavaScript, texte brut, …) et peut varier dans ses parties spécifiques. Par exemple, dans un document HTML, il existe de nombreux endroits (contextes) où des règles très différentes s'appliquent. Vous serez peut-être surpris de leur nombre. Voici les quatre premiers :

<p>#texte</p>
<img src="#attribut">
<textarea>#rawtext</textarea>
<!-- #commentaire -->

Le contexte par défaut et de base d'une page HTML est le texte HTML. Quelles règles s'appliquent ici ? Les caractères < et & ont une signification spéciale, représentant le début d'une balise ou d'une entité, nous devons donc les échapper en les remplaçant par une entité HTML (< par &lt; & par &amp).

Le deuxième contexte le plus courant est la valeur d'un attribut HTML. Il diffère du texte en ce que le guillemet " ou ', qui délimite l'attribut, a ici une signification spéciale. Il doit être écrit comme une entité pour ne pas être compris comme la fin de l'attribut. Inversement, dans l'attribut, le caractère < peut être utilisé en toute sécurité, car il n'a pas de signification spéciale ici, il ne peut pas être compris comme le début d'une balise ou d'un commentaire. Mais attention, en HTML, on peut aussi écrire les valeurs des attributs sans guillemets, auquel cas toute une série de caractères ont une signification spéciale, il s'agit donc d'un autre contexte distinct.

Vous serez peut-être surpris, mais des règles spéciales s'appliquent à l'intérieur des éléments <textarea> et <title>, où le caractère < n'a pas besoin (mais peut) être échappé, s'il n'est pas suivi de /. Mais c'est plutôt une anecdote.

C'est intéressant à l'intérieur des commentaires HTML. Ici, l'échappement n'utilise pas d'entités HTML. Aucune spécification n'indique même comment échapper dans les commentaires. Il faut juste respecter des règles quelque peu curieuses et y éviter certaines combinaisons de caractères.

Les contextes peuvent également être imbriqués, ce qui se produit lorsque nous insérons du JavaScript ou du CSS dans du HTML. Cela peut être fait de deux manières différentes, par élément et par attribut :

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

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

Deux chemins et deux méthodes différentes d'échappement des données. À l'intérieur de l'élément <script> et <style>, comme dans le cas des commentaires HTML, l'échappement à l'aide d'entités HTML n'est pas effectué. Lors de l'affichage de données à l'intérieur de ces éléments, il faut respecter une seule règle : le texte ne doit pas contenir la séquence </script resp. </style.

Inversement, dans les attributs style et on***, on échappe à l'aide d'entités HTML.

Et bien sûr, à l'intérieur du JavaScript ou du CSS imbriqué, les règles d'échappement de ces langages s'appliquent. Ainsi, une chaîne dans un attribut par ex. onload est d'abord échappée selon les règles JS, puis selon les règles de l'attribut HTML.

Ouf… Comme vous pouvez le voir, HTML est un document très complexe où les contextes s'imbriquent, et sans savoir exactement où j'affiche les données (c'est-à-dire dans quel contexte), il est impossible de dire comment le faire correctement.

Voulez-vous un exemple ?

Prenons la chaîne Rock'n'Roll.

Si vous l'affichez dans du texte HTML, dans ce cas précis, il n'est pas nécessaire de faire de remplacements, car la chaîne ne contient aucun caractère ayant une signification spéciale. La situation change si vous l'affichez à l'intérieur d'un attribut HTML entouré de guillemets simples. Dans ce cas, il faut échapper les guillemets en entités HTML :

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

C'était simple. Une situation beaucoup plus intéressante se produit lors de l'imbrication de contextes, par exemple si la chaîne fait partie de JavaScript.

D'abord, affichons-la dans le JavaScript lui-même. C'est-à-dire, entourons-la de guillemets et échappons en même temps les guillemets qu'elle contient à l'aide du caractère \ :

'Rock\'n\'Roll'

Nous pouvons encore ajouter l'appel d'une fonction pour que le code fasse quelque chose :

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

Si nous insérons ce code dans un document HTML à l'aide de <script>, il n'est pas nécessaire de le modifier davantage, car il ne contient pas la séquence interdite </script:

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

Cependant, si nous voulions l'insérer dans un attribut HTML, nous devrions encore échapper les guillemets en entités HTML :

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

Mais le contexte imbriqué ne doit pas nécessairement être uniquement JS ou CSS. Il s'agit couramment aussi d'une URL. Les paramètres dans une URL sont échappés en convertissant les caractères ayant une signification spéciale en séquences commençant par %. Exemple :

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

Et lorsque nous affichons cette chaîne dans un attribut, nous appliquons encore l'échappement selon ce contexte et remplaçons & par &amp:

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

Si vous avez lu jusqu'ici, félicitations, c'était épuisant. Vous avez maintenant une bonne idée de ce que sont les contextes et l'échappement. Et ne vous inquiétez pas, ce n'est pas compliqué. Latte fait tout cela automatiquement pour vous.

Latte vs systèmes naïfs

Nous avons montré comment échapper correctement dans un document HTML et à quel point la connaissance du contexte, c'est-à-dire l'endroit où nous affichons les données, est essentielle. En d'autres termes, comment fonctionne l'échappement contextuel. Bien qu'il s'agisse d'une condition préalable nécessaire à une défense fonctionnelle contre XSS, Latte est le seul système de templates pour PHP qui sait le faire.

Comment est-ce possible, alors que tous les systèmes prétendent aujourd'hui avoir un échappement automatique ? L'échappement automatique sans connaissance du contexte est un peu une connerie, qui crée une fausse impression de sécurité.

Les systèmes de templates, tels que Twig, Laravel Blade et autres, ne voient aucune structure HTML dans le template. Ils ne voient donc pas non plus les contextes. Par rapport à Latte, ils sont aveugles et naïfs. Ils ne traitent que leurs propres balises, tout le reste est pour eux un flux de caractères sans importance :

░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░
░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░
- dans le texte : <span>{{ foo }}</span>
- dans la balise : <span {{ foo }} ></span>
- dans l'attribut : <span title='{{ foo }}'></span>
- dans l'attribut sans guillemets : <span title={{ foo }}></span>
- dans l'attribut contenant une URL : <a href="{{ foo }}"></a>
- dans l'attribut contenant du JavaScript : <img onload="{{ foo }}">
- dans l'attribut contenant du CSS : <span style="{{ foo }}"></span>
- en JavaScript : <script>var = {{ foo }}</script>
- en CSS : <style>body { content: {{ foo }}; }</style>
- dans le commentaire : <!-- {{ foo }} -->

Les systèmes naïfs ne font que convertir mécaniquement les caractères < > & ' " en entités HTML, ce qui est certes une méthode d'échappement valide dans la plupart des cas d'utilisation, mais loin d'être toujours le cas. Ils ne peuvent donc ni détecter ni prévenir la création de diverses failles de sécurité, comme nous le montrerons plus loin.

Latte voit le template de la même manière que vous. Il comprend HTML, XML, reconnaît les balises, les attributs, etc. Et grâce à cela, il distingue les différents contextes et traite les données en conséquence. Il offre ainsi une protection vraiment efficace contre la vulnérabilité critique Cross-site Scripting.

░░░░░░░░░░░<span>{$foo}</span>
░░░░░░░░░░<span {$foo} ></span>
░░░░░░░░░░░░░░<span title='{$foo}'></span>
░░░░░░░░░░░░░░░░░░░░░░░░░░░<span title={$foo}></span>
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<a href="{$foo}"></a>
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<img onload="{$foo}">
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<span style="{$foo}"></span>
░░░░░░░░░░░░░░░░░<script>░░░░░░{$foo}</script>
░░░░░░░░░<style>░░░░░░░░░░░░░░░░{$foo}░░░</style>
░░░░░░░░░░░░░░░<!--░{$foo}░-->
- dans le texte : <span>{$foo}</span>
- dans la balise : <span {$foo} ></span>
- dans l'attribut : <span title='{$foo}'></span>
- dans l'attribut sans guillemets : <span title={$foo}></span>
- dans l'attribut contenant une URL : <a href="{$foo}"></a>
- dans l'attribut contenant du JavaScript : <img onload="{$foo}">
- dans l'attribut contenant du CSS : <span style="{$foo}"></span>
- en JavaScript : <script>var = {$foo}</script>
- en CSS : <style>body { content: {$foo}; }</style>
- dans le commentaire : <!-- {$foo} -->

Démonstration en direct

À gauche, vous voyez le template en Latte, à droite le code HTML généré. La variable $text est affichée plusieurs fois, et à chaque fois dans un contexte légèrement différent. Et donc aussi échappée légèrement différemment. Vous pouvez éditer vous-même le code du template, par exemple changer le contenu de la variable, etc. Essayez :

{* ESSAYEZ DE MODIFIER CE 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 -->

N'est-ce pas génial ! Latte effectue l'échappement contextuel automatiquement, de sorte que le programmeur :

  • n'a pas besoin de réfléchir ni de savoir comment échapper où
  • ne peut pas se tromper
  • ne peut pas oublier d'échapper

Ce ne sont même pas tous les contextes que Latte distingue lors de l'affichage et pour lesquels il adapte le traitement des données. Nous allons maintenant passer en revue d'autres cas intéressants.

Comment pirater les systèmes naïfs

Sur plusieurs exemples pratiques, nous allons montrer à quel point la distinction des contextes est importante et pourquoi les systèmes de templates naïfs n'offrent pas une protection suffisante contre XSS, contrairement à Latte. Comme représentant d'un système naïf, nous utiliserons Twig dans les exemples, mais la même chose s'applique aux autres systèmes.

Vulnérabilité par attribut

Nous allons essayer d'injecter du code malveillant dans la page à l'aide d'un attribut HTML, comme nous l'avons montré ci-dessus. Ayons un template en Twig affichant une image :

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

Notez qu'il n'y a pas de guillemets autour des valeurs des attributs. Le codeur a pu les oublier, ce qui arrive tout simplement. Par exemple, en React, le code s'écrit ainsi, sans guillemets, et un codeur qui alterne les langages peut alors facilement oublier les guillemets.

L'attaquant insère comme légende de l'image une chaîne habilement construite foo onload=alert('Hacked!'). Nous savons déjà que Twig ne peut pas savoir si la variable est affichée dans le flux de texte HTML, à l'intérieur d'un attribut, d'un commentaire HTML, etc., bref, il ne distingue pas les contextes. Et il ne fait que convertir mécaniquement les caractères < > & ' " en entités HTML. Le code résultant ressemblera donc à ceci :

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

Et une faille de sécurité a été créée !

L'attribut onload falsifié fait partie de la page et le navigateur l'exécute immédiatement après le téléchargement de l'image.

Voyons maintenant comment Latte gère le même template :

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

Latte voit le template de la même manière que vous. Contrairement à Twig, il comprend HTML et sait que la variable est affichée comme valeur d'un attribut qui n'est pas entre guillemets. C'est pourquoi il les ajoute. Lorsque l'attaquant insère la même légende, le code résultant ressemblera à ceci :

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

Latte a réussi à empêcher XSS.

Affichage d'une variable en JavaScript

Grâce à l'échappement contextuel, il est tout à fait natif d'utiliser des variables PHP à l'intérieur de JavaScript.

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

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

Si la variable $movie contient la chaîne 'Amarcord & 8 1/2', la sortie suivante sera générée. Notez qu'à l'intérieur du HTML, un échappement différent est utilisé par rapport à l'intérieur du JavaScript, et encore un autre dans l'attribut onclick:

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

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

Vérification des liens

Latte vérifie automatiquement si la variable utilisée dans les attributs src ou href contient une URL web (c'est-à-dire le protocole HTTP) et empêche l'affichage de liens qui pourraient présenter un risque de sécurité.

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

<a href={$link}>cliquez</a>

Affiche :

<a href="">cliquez</a>

La vérification peut être désactivée à l'aide du filtre nocheck.

Limites de Latte

Latte n'est pas une protection totalement complète contre XSS pour l'ensemble de l'application. Nous ne voudrions pas que vous cessiez de penser à la sécurité en utilisant Latte. L'objectif de Latte est de s'assurer qu'un attaquant ne peut pas modifier la structure de la page, falsifier des éléments ou attributs HTML. Mais il ne contrôle pas l'exactitude du contenu des données affichées. Ni l'exactitude du comportement de JavaScript. Cela dépasse les compétences d'un système de templates. La vérification de l'exactitude des données, en particulier celles insérées par l'utilisateur et donc non fiables, est une tâche importante du programmeur.

version: 3.0