The Challenge
A web application presents a card lookup form that POSTs a card_id parameter. The backend directly interpolates card_id into a SQLite query with no parameterization. The flag is stored in a separate table not reachable through the normal UI.
Approach
I opened the app and tried submitting a card ID of 1. Got a result. Tried 1' — server error. Classic string injection point.
The first thing I wanted was the table schema. I tried a simple UNION SELECT NULL,NULL-- to probe the column count — it errored. Added more NULLs one by one: three didn’t work either. Looking at the HTML response I noticed there was also a CSRF token being checked, which my manual curl requests were missing. I had to script the extraction to also grab and forward the CSRF token with each POST.
Once the CSRF flow was handled in extract_data(), the actual injection worked. The payload structure is nested because the app escapes single quotes in a non-standard way, so I had to use a double-union trick to actually get the inner query to evaluate. I first retrieved the schema of the flag table from sqlite_master, confirmed the column was named flag, then extracted the value.
Solution
|
|
What I Learned
Union-based injection in SQLite is slightly more forgiving than MySQL — you can use double quotes for string literals which sidesteps some quoting escapes. sqlite_master is always the first place to look when you need the database schema, and it’s always accessible to any SQL query that runs.