Second-Order SQL Injection: How to Find Delayed SQLi Bugs
Second-order SQL injection stores attacker input without executing it, then later plugs that value into an unsafe query. The injection point and the sink are different requests — scanners that only fuzz the first request often miss it.
Classic example: you set your display name to admin'-- and nothing breaks. Later, an admin “export users” job builds SQL with that name and blows up — or worse, runs your payload.
Hunting mindset
- Find fields that persist: profile names, filenames, invoice notes, webhook URLs.
- Plant a unique SQL metacharacter canary owned by your account.
- Trigger every server process that might read it: reports, search indexing, digests, admin tools, cron-style exports.
- Watch for SQL errors, odd data, or a blind callback you control.
Safe proof
Use only your data. Prefer boolean or time-based techniques on staging. For blind cases, a DNS/HTTP canary tied to your plant is enough — do not extract other customers’ rows.
High-value sinks
- Admin search that filters on user-supplied strings
- Reporting / CSV export jobs
- Audit log viewers building dynamic WHERE clauses
- “Login as user” or impersonation tools
Report structure
Document: (1) where the payload was stored, (2) which action triggered the sink, (3) evidence of SQL context, (4) impact on confidentiality or integrity. Recommend parameterized queries at the sink even if the store layer “looked safe.”
Defensive checklist
- Parameterize every query — including batch jobs and ORMs with raw SQL escapes.
- Treat stored user strings as untrusted forever.
- Avoid building SQL from display names or filenames.
- Add integration tests that plant canaries and run exports.
- Give batch jobs least-privilege DB accounts.
Second-order SQLi rewards patience. Plant, then poke every reader of that data.
Original Bugflare guide informed by PortSwigger SQL injection methodology.