LPF LPQ study materials
LPF LPQ study resources

Proving Blind SQL Injection Without Emptying the Database

LPF LPQ · Put your knowledge to work in context.

All case studies

Proving Blind SQL Injection Without Emptying the Database

Vantage Retail runs a PCI-DSS-scoped e-commerce platform behind a CDN and a WAF. You have written authorisation for a grey-box test of the checkout flow with two hard constraints: no more than 300 requests per minute to any endpoint, and no retrieval of real customer data, with proof of access limited to one row and one column per finding. Testing the coupon endpoint POST /api/v1/coupon/validate, you measure a baseline of about 210 ms. Sending code=SAVE10' AND (SELECT 1 FROM PG_SLEEP(5))-- returns in 5.24 s. Repeats vary between 5.1 s and 5.6 s, but a malformed code with no SQL syntax also sometimes takes 900 ms because the WAF inspects it more deeply. The backend is PostgreSQL 14. Peak traffic is 12:00-14:00 UTC. The client's QA lead asks you to extract the admin password hash 'so the auditor believes us'.

1. How do you establish that this is genuine blind SQL injection rather than WAF-induced latency?

Read the worked answer

A single slow response proves nothing, and the 900 ms outlier shows this endpoint has at least two code paths, one of which is WAF inspection. The test therefore has to control for the WAF rather than hope it stays constant.

Steps:

  1. Baseline distribution. Send 10-15 benign requests and record mean, median and spread. If normal latency already ranges to 900 ms, a 5 s delay is still distinguishable but a 2 s delay might not be.
  2. Parameterised sleep. Use several injected delays, for example PG_SLEEP(2), PG_SLEEP(5) and PG_SLEEP(9). A real injection produces response times that track the injected value monotonically; WAF delay does not scale with a number buried in the payload.
  3. Paired true/false predicate. Compare ' AND (SELECT 1 FROM PG_SLEEP(5))-- against a syntactically similar payload whose predicate is false, so both traverse the same WAF rule path and differ only in database evaluation. Repeat each three times and compare distributions, not single samples.
  4. Arithmetic evaluation. PG_SLEEP(2+3) taking about 5 s demonstrates the database parsed and evaluated an expression, which a proxy timeout cannot mimic.
  5. Negative control. Send a random alphanumeric string of the same length as the payload; it may trigger the WAF but must not produce the delay.

Only after the timing correlates with the injected arithmetic do you record it as confirmed, with the raw request/response timing log attached as evidence.

2. The QA lead wants the admin password hash in the report. How do you respond, and what evidence do you supply instead?

Read the worked answer

Refuse the extraction, and explain the reasoning so it does not look like obstruction.

The SoW limits proof of access to one row and one column per finding. Pulling pg_authid.rolpassword is not one column of one row in any meaningful sense; it is credential material, and the moment it is written into a report the report becomes a credential store that will be emailed, stored in a shared drive and possibly retained for years. That is a new risk created by the assessment, which is exactly what the data-handling clause exists to prevent. The auditor's requirement under PCI DSS is evidence that unauthorised read access to the database is possible, not possession of a working secret.

Evidence that satisfies the requirement:

  • Functions that prove code execution in the database context: version(), current_user, current_database(), inet_server_addr(). These are non-sensitive and unambiguous.
  • One non-sensitive value from one row and one column, such as the first coupon code in the coupons table, to demonstrate table read.
  • If the client insists on proof that the users table specifically is readable, extract a username only, and record a SHA-256 of the extracted string alongside it so authenticity can be verified without exposing the value.
  • Offer a witnessed live reproduction for the auditor, with the client's security lead present, where the query is run and the output is shown on screen but not captured.

Record the request and your rationale in the engagement log. If pressure continues, escalate in writing to the client's security lead rather than complying quietly.

3. You still need several more confirmed requests to finish the proof. How do you schedule and pace them without harming checkout or contaminating your own timing evidence?

Read the worked answer

The 300 requests per minute cap is a ceiling, not a target, and the real constraint is customer experience plus evidence quality.

Scheduling: run between 01:00 and 05:00 UTC, far outside the 12:00-14:00 peak. Ask the on-call engineer and the CDN/WAF owner to expect the traffic and confirm they will not auto-block the source; a WAF block mid-sequence both wastes the window and creates a false negative.

Pacing: concurrency of one, roughly six to ten requests per minute to this endpoint. Blind timing attacks fail when requests interleave, because queueing latency at the WAF or application layer swamps the sleep signal. Slow and serial produces cleaner numbers than fast and parallel.

Canaries and stop conditions, agreed in advance in writing:

  • Monitor checkout success rate and p95 latency; abort if checkout errors exceed 1% or p95 latency doubles against the pre-test baseline.
  • Use a dedicated test account and an isolated test cart so no real order is created.
  • Stop immediately on any 5xx burst, connection-pool exhaustion or database CPU alarm.

Bookkeeping: log each request with a monotonic timestamp, keep the sleep values small where the signal is already clear, and finish the minimal proof (version, current user, one table read) rather than elaborating. Then hand the endpoint back, confirm with the client that monitoring is clean, and write the finding the same night while the timing data is fresh.