HTB CWEE study materials
HTB CWEE study resources

Boolean-Blind SQLi Behind a Keyword WAF

HTB CWEE · Put your knowledge to work in context.

All case studies

Boolean-Blind SQLi Behind a Keyword WAF

A regional bookstore runs MySQL 8.0 behind PHP 8.1. The product detail endpoint GET /product?id= concatenates the id parameter unquoted into WHERE id = $id. A WAF blocks the literal strings UNION, SLEEP, BENCHMARK, INFORMATION_SCHEMA, and --, and it collapses repeated whitespace. The endpoint returns HTTP 200 with a product page when the row exists and HTTP 404 when it does not. You have a low-privilege account and need the admin password hash from users(username, password_hash). The WAF also rate-limits 20 requests per minute per source IP, and your egress IP is shared with other testers. Error messages are suppressed. You must avoid disrupting the storefront and must not use time-based payloads because shared infrastructure makes delays unreliable. The database user has read access only to the storefront schema.

1. Identify the viable injection class and give the exact confirmation requests. Explain why UNION, error-based, and time-based approaches are not appropriate here.

Read the worked answer

The parameter is unquoted numeric, so boolean-based blind SQLi is the reliable class. UNION is blocked as a literal and would also require matching column counts; error-based is out because errors are suppressed; time-based is excluded by the rules and unreliable on shared infrastructure.

Confirm with a pair that changes only the predicate and observe 200 vs 404:

GET /product?id=1/**/AND/**/1=1 HTTP/1.1
GET /product?id=1/**/AND/**/1=2 HTTP/1.1

The /**/ comments replace the whitespace the WAF collapses. A 200 on the first and 404 on the second is the differential. Add a negative control (id=1/**/AND/**/2=2) and a baseline (id=1) so you are not mistaking an ordinary missing product for injection. If the WAF blocks AND, try && or %26%26; if that is blocked, use OR with a false first predicate. Do not proceed until the differential is stable across at least three requests.

2. Write the extraction query for the first character of the admin hash and explain how to reduce the request count under the 20/min limit.

Read the worked answer

Use a subquery in a boolean predicate:

GET /product?id=1/**/AND/**/(SELECT/**/SUBSTRING(password_hash,1,1)/**/FROM/**/users/**/WHERE/**/username='admin')='a' HTTP/1.1

A 200 means the first character is a; 404 means it is not. Replace 'a' with each candidate character. To avoid dozens of requests per character, use ordered comparison on the ASCII code:

GET /product?id=1/**/AND/**/ASCII(SUBSTRING(password_hash,1,1))>77 HTTP/1.1

This is a binary search over 0-127, about seven requests per character. For a 60-character hash that is roughly 420 requests, or about 21 minutes at 20/min if you do not rotate IPs. Because the egress IP is shared, throttle to 15/min to leave headroom and extract in chunks. Verify the first four characters against a known hash prefix if the algorithm is known. If the WAF starts returning 403, back off for 60 seconds rather than increasing concurrency.