
Les vulnérabilités de second ordre ne constituent pas un type de vulnérabilité à part entière, comme peut l’être une injection de commande. Elles désignent plutôt une manière particulière d’exploiter des vulnérabilités existantes.
Elles apparaissent lorsqu’une entrée utilisateur est enregistrée par une application, puis réutilisée ultérieurement dans un autre contexte sans être correctement validée ou traitée.
Un exemple classique est celui de la XSS stockée, qui correspond dans de nombreux cas à une exploitation de second ordre. Une entrée fournie par un utilisateur est d’abord enregistrée dans l’application sans être correctement nettoyée. Elle est ensuite réutilisée ou affichée dans une autre partie de l’application, voire dans une autre application, comme un back-office, sans être correctement échappée en sortie. C’est à ce moment-là que la vulnérabilité se manifeste.
De nombreux types de vulnérabilités peuvent ainsi être exploités en second ordre, notamment les XSS, les injections SQL, les LFI/RFI ou encore les RCE. Nous détaillerons ci-dessous deux scénarios concrets, portant respectivement sur une SSRF et une injection SQL. Le principe reste toutefois transposable à ces autres types de vulnérabilités.
La principale différence entre une attaque classique et une attaque de second ordre réside dans le moment et l’endroit où l’exploitation se déclenche.
Dans le cas d’une attaque classique, l’injection provoque immédiatement un comportement inattendu, directement côté serveur ou dans la réponse de l’application. La présence de la vulnérabilité peut donc être observée et mesurée au moment où la charge malveillante est injectée.
À l’inverse, dans une attaque de second ordre, l’injection et l’exécution interviennent à deux moments distincts. La charge est d’abord enregistrée ou transmise sans provoquer nécessairement de comportement visible, puis elle est exécutée ultérieurement lorsqu’elle est réutilisée dans un autre contexte.
C’est notamment le cas d’une Blind XSS.
Les attaques de second ordre sont particulièrement difficiles à identifier, et l’approche de détection varie fortement selon que l’on travaille en boîte noire, en boîte grise ou en boîte blanche.
En boîte noire ou grise, les outils automatisés sont souvent peu efficaces, car ils se basent principalement sur la réponse immédiate à une requête. Or, dans le cas d’une attaque de second ordre, cette réponse ne présente généralement aucun signe visible de la vulnérabilité.
Une méthode consiste alors à injecter des marqueurs uniques dans chacun des champs dont les données sont enregistrées par l’application, puis à parcourir les différentes fonctionnalités afin d’identifier les endroits où ces marqueurs réapparaissent. Cela permet de repérer les contextes dans lesquels une donnée contrôlée par l’utilisateur est réutilisée.
Lorsque le déclenchement de la vulnérabilité se produit à l’aveugle, il est nécessaire de s’appuyer sur un serveur externe afin de détecter une éventuelle interaction. Des outils comme Burp Collaborator permettent par exemple d’identifier ce type de déclenchement.
En boîte blanche, la revue de code reste l’approche la plus fiable. Elle nécessite toutefois d’avoir une vision globale des flux de données au sein de l’application, plutôt que d’analyser chaque fonction de manière isolée. Il faut dans un premier temps identifier les différentes sources à partir desquelles une entrée utilisateur peut être introduite dans l’application, puis suivre le cheminement de ces données jusqu’aux endroits où elles sont stockées et réutilisées. L’objectif est notamment d’identifier les cas où une donnée contrôlée par un utilisateur atteint par la suite un contexte sensible, comme une requête SQL.
Prenons l’exemple de l’application BookVault, sur laquelle n’importe quel utilisateur peut créer un compte.

Une fois son compte créé, l’utilisateur peut ajouter un livre à sa bibliothèque à partir d’une URL. Ce type de fonctionnalité est régulièrement rencontré au cours de nos audits et constitue souvent une source de vulnérabilités.
En analysant le code source de l’application, on peut ici identifier un problème de configuration.

En plus des protocoles http et https, le développeur a également autorisé le protocole file. Il devient ainsi possible d’accéder directement à des fichiers présents sur le serveur, ce qui constitue une vulnérabilité.
Pour le vérifier, nous pouvons utiliser l’entrée file:///etc/passwd. Ce fichier est présent sur les systèmes UNIX et peut généralement être lu par tous les utilisateurs.

L’application renvoie cependant une erreur indiquant qu’aucune balise <title> n’a été trouvée dans le fichier. Pour en comprendre la raison, il faut examiner la fonction extract_title().

Une expression régulière (regex) est appliquée au contenu récupéré afin d’extraire tout ce qui se trouve entre les balises <title></title>.
Même s’il est possible de lire des fichiers présents sur le serveur, peu d’entre eux sont susceptibles de contenir de telles balises. Il est encore moins probable que les données situées entre celles-ci présentent un intérêt particulier pour un attaquant.
Une analyse plus approfondie du code source permet toutefois d’identifier deux comportements intéressants :
/app/app.log.À partir de ces deux éléments, il devient possible d’imaginer un scénario permettant de récupérer la session de l’administrateur :
<title> comme nom d’utilisateur. Cette valeur est alors enregistrée dans le fichier de logs et correspond au début de l’expression régulière utilisée par l’application.</title> comme nom d’utilisateur. Cette valeur correspond à la fin de l’expression régulière. L’ensemble des données enregistrées entre nos deux tentatives se retrouve donc encadré par les balises <title> et </title>.file:///app/app.log. La fonction extract_title() extrait alors tout le contenu situé entre les deux balises que nous avons injectées, dont le cookie de session de l’administrateur. Celui-ci peut alors être utilisé pour accéder à son compte.
Ce scénario illustre bien le principe d’une attaque de second ordre. Les différentes actions effectuées séparément ne permettent pas, à elles seules, d’exploiter directement la vulnérabilité. C’est leur enchaînement, ainsi que la réutilisation ultérieure des données injectées, qui rendent l’exploitation possible.
Ce scénario est inspiré d’une vulnérabilité réelle que nous avons identifiée au cours d’un audit. Elle avait permis de récupérer plusieurs mégaoctets de logs générés par l’application sur une période donnée.
Dans ce second scénario, nous allons exploiter une injection SQL à travers le nom d’utilisateur.
L’application SupportDesk permet à un utilisateur de créer un compte, de modifier les informations de son profil et de contacter le support. Il peut également consulter l’historique de ses tickets depuis la page /support.

Depuis la section dédiée au profil, l’utilisateur peut modifier son nom d’utilisateur, son adresse email et son mot de passe. Il s’agit donc de fonctionnalités classiques, présentes dans de nombreuses applications web.
En analysant le code source, on constate que la création d’un compte et la modification du profil reposent toutes deux sur des requêtes paramétrées, empêchant ainsi une injection SQL directe à ce niveau.

Le code présenté ci-dessus n’est donc pas vulnérable en l’état. On remarque toutefois que les trois champs sont enregistrés tels quels dans la base de données, sans nettoyage particulier.
En poursuivant l’analyse du code source, on observe en revanche qu’à la ligne 208, le nom d’utilisateur est directement concaténé dans une requête SQL.

Un point important est que cette valeur n’est pas directement récupérée depuis une entrée utilisateur au moment de l’exécution de la requête : elle est lue depuis la base de données.
Or, comme nous venons de le voir, l’utilisateur peut auparavant modifier son nom d’utilisateur et y enregistrer des caractères arbitraires. Il est donc possible d’y insérer une charge utile SQL, qui sera stockée dans la base de données sans déclencher immédiatement de comportement particulier.
La vulnérabilité apparaît lorsque cette valeur est réutilisée par la route /support. Par exemple, si une quote est enregistrée dans le nom d’utilisateur, puis qu’une requête GET ou POST est effectuée sur cette route, la valeur stockée est concaténée dans la requête SQL. Celle-ci devient alors invalide et provoque une erreur renvoyée au client.

L’injection est donc indirecte et moins évidente à identifier, mais elle est bien présente. La charge utile est injectée lors de la modification du profil, tandis que son exécution intervient seulement plus tard, lorsque le nom d’utilisateur est récupéré depuis la base de données puis réutilisé dans la requête SQL.
Un scanner automatique basique risque de ne pas détecter cette vulnérabilité, puisqu’il s’appuie généralement sur la réponse immédiate à la requête contenant la charge utile. Sqlmap permet toutefois de gérer ce type de scénario en utilisant notamment les options --second-url ou --second-req.
À partir de là, il devient possible de modifier le nom d’utilisateur avec une charge utile adaptée afin, par exemple, de récupérer les informations d’autres utilisateurs. En enregistrant une injection SQL de type UNION-based dans le nom d’utilisateur, puis en revisitant la page /support, la charge utile est exécutée et la requête renvoie la liste des utilisateurs.

Par nature, l’impact d’une attaque de second ordre ne diffère pas de celui de la vulnérabilité sous-jacente : une injection SQL reste une injection SQL, une XSS reste une XSS, etc. Ce qui change en revanche, c’est le contexte dans lequel la charge malveillante est finalement exécutée.
C’est précisément ce qui rend ces attaques particulièrement intéressantes. L’exécution peut avoir lieu dans un contexte plus privilégié que celui du point d’injection initial. Une donnée peut par exemple être enregistrée par un utilisateur standard, puis être réutilisée ultérieurement dans un back-office accessible à un administrateur.
C’est notamment le cas des Blind XSS, mais aussi du scénario BookVault présenté précédemment : l’attaque part d’un simple compte utilisateur, mais permet finalement de récupérer le cookie de session d’un administrateur.
Autre particularité importante : la charge malveillante n’est généralement pas présente dans la requête qui déclenche son exécution. Cette dernière peut être parfaitement légitime et ne contenir aucun élément suspect. Un WAF peut donc difficilement identifier l’attaque au moment du déclenchement, puisque la donnée malveillante a été introduite plus tôt dans l’application.
Ce fonctionnement complique également la réponse à incident. Une charge malveillante peut rester stockée pendant plusieurs heures ou plusieurs jours avant d’être réutilisée et exécutée, ce qui rend plus difficile la corrélation entre le point d’injection initial et l’événement à l’origine de l’incident.
L’impact final dépend donc principalement du contexte dans lequel la donnée est réutilisée. Dans nos exemples précédents, l’exploitation permet respectivement un vol de session administrateur et l’exfiltration d’informations sur les utilisateurs. Selon la vulnérabilité sous-jacente et le contexte d’exécution, une attaque de second ordre pourrait toutefois mener à d’autres conséquences, comme une exécution de code ou une escalade de privilèges.
Il n’existe pas de remédiation unique aux attaques de second ordre. Comme nous l’avons vu, celles-ci ne correspondent pas à une catégorie de vulnérabilité spécifique : la correction dépend donc avant tout de la vulnérabilité sous-jacente.
Le fait qu’une attaque soit de second ordre n’implique pas nécessairement de mesure de protection particulière. C’est avant tout la vulnérabilité présente au point de réutilisation de la donnée qu’il faut corriger : requêtes paramétrées pour prévenir les injections SQL, encodage contextuel en sortie pour les XSS, validation du protocole et de la destination pour les SSRF, etc.
Le principe essentiel à retenir est qu’une donnée considérée comme sûre dans un contexte ne l’est pas nécessairement dans un autre. Dans notre exemple SupportDesk, le nom d’utilisateur est inoffensif tant qu’il est utilisé comme tel, mais devient dangereux dès lors qu’il est concaténé dans une requête SQL.
Une donnée ne doit donc jamais être considérée comme sûre uniquement parce qu’elle provient d’une source jugée de confiance, comme la base de données de l’application. Celle-ci peut simplement contenir une donnée contrôlée par un utilisateur et enregistrée à un moment antérieur.
Enfin, ces vulnérabilités étant particulièrement difficiles à identifier, la revue de code reste l’une des approches les plus efficaces. L’analyse doit être menée en raisonnant en termes de flux de données : il s’agit d’identifier les endroits où une entrée utilisateur est introduite et stockée, puis de suivre les différents points où cette donnée est ensuite réutilisée afin de vérifier que les protections adaptées au contexte sont bien appliquées.
Auteur : Théo ARCHIMBAUD – Pentester @Vaadata