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 <
. 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 :
- reconnaît le contexte dans lequel les données sont affichées
- sanitise les données selon les règles du contexte donné (c'est-à-dire “contextuellement”)
- 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 <
&
par &
).
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'n'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('Rock\'n\'Roll')'></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 &
:
<a href="https://example.org/?a=Jazz&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 :
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('Hacked!')>
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('Hacked!')">
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("Amarcord & 8 1\/2")">Amarcord & 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.