SQHell
Lab Info
| Detail | Info |
|---|---|
| Room | SQHell |
| Platform | TryHackMe |
| Difficulty | Medium |
| Type | CTF / Multi-Technique SQL Injection (In-Band, Header-Based, Boolean/UNION, Blind) |
| Knowledge Required | Web, SQL, SQLi Theory (Union/Boolean/Blind/Time-Based), Burp Suite, sqlmap |
Tools Used
Nmap — Port scanning
dirsearch / Gobuster — Directory enumeration
Burp Suite — Capturing and replaying requests (login, registration, header injection)
sqlmap — Automated exploitation for headers, POST bodies, and GET parameters
Manual SQLi payloads (login bypass, nested UNION queries) — Where automation isn't the point of the exercise
First Impressions
This one isn't a boot2root at all — the room description says it outright: five flags, five different SQL injection types, no shell required anywhere. It's less a "story" box and more a structured skills gauntlet: in-band login bypass, header-based blind injection, POST-parameter injection, boolean/UNION-based extraction, and GET-parameter injection — each flag deliberately built to only fall to one specific technique. Genuinely one of the better rooms for building real SQLi range rather than repeating the same ' OR 1=1-- trick five times.
Recon
nmap -sC -sV -p- <ip>
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
dirsearch -u http://<ip>
Turns up a login page, a registration page, a terms-and-conditions page, and a blog/post section with a ?id= parameter — five separate surfaces, which lines up neatly with five separate flags.
Flag 1 — In-Band Authentication Bypass
The login form is the obvious starting point for any SQLi room. A standard authentication-bypass payload in the username field:
' OR 1=1-- -
(with the password field left arbitrary) logs straight in without valid credentials — a classic case of user input being concatenated directly into a WHERE username = '...' AND password = '...' query with no parameterization. First flag displayed immediately post-login.
Flag 2 — Header-Based Blind Injection (X-Forwarded-For)
The Terms and Conditions page contains a deliberate hint: a clause stating the site logs the visitor's IP address "for analytics purposes." That's a strong signal the backend is reading a client-controlled header — most commonly X-Forwarded-For — and doing something with it server-side, likely inserting it straight into a query or log table without sanitization.
sqlmap --dbms=mysql --headers="X-Forwarded-For: 1*" -u http://<ip>/terms-and-conditions --dbs --batch
The * marks the injection point for sqlmap inside the header value itself. Confirms the header is vulnerable to time-based blind injection, and enumerates the available databases:
available databases [2]:
[*] information_schema
[*] sqhell_1
sqlmap --dbms=mysql --headers="X-Forwarded-For: 1*" -u http://<ip>/terms-and-conditions -D sqhell_1 -T flag --dump
Dumps the flag table directly — second flag retrieved without ever touching the visible page content, entirely through a header nobody would think to test without that terms-and-conditions clue.
Flag 3 — POST-Parameter Injection via Registration
The registration form checks whether a chosen username is already taken before allowing signup — and that existence check is itself a query built from unsanitized input, making the username field a second, independent injection point.
Capturing a registration attempt in Burp and saving it as a request file, then handing it to sqlmap directly:
sqlmap -r register.req -p username --dbs --batch
available databases [2]:
[*] information_schema
[*] sqhell_3
sqlmap -r register.req -p username -D sqhell_3 --tables
[2 tables]
flag
users
sqlmap -r register.req -p username -D sqhell_3 -T flag --dump
Third flag out — a different injection point (POST body instead of a header), a different database, same overall approach of letting sqlmap drive once the vulnerable parameter is confirmed.
Flag 4 — Manual UNION-Based Injection on the Users Page
The users listing page renders results from a query with a predictable, guessable shape once you feed it test input and watch how results change. Rather than reaching for sqlmap here, this flag is built around manually constructing a nested UNION query — feeding a UNION injection inside a string that the outer query then also evaluates as SQL, effectively chaining two levels of injection together:
UNION ALL SELECT "1 UNION SELECT 1,flag,3,4 FROM flag-- -",1,2 FROM users#
The outer UNION ALL SELECT supplies a string that — once it lands in a context the application re-parses as SQL — itself contains a second UNION pulling flag out of the flag table, with the trailing -- - and # comment markers cleaning up whatever the original query expected to follow. Working this out took direct experimentation with the page's normal output shape first, rather than jumping straight to a payload — a good reminder that not every flag in this room is meant to be automated.
Flag 5 — GET-Parameter Injection on the Blog
The blog/post section uses a straightforward ?id= parameter to select which post to display — checking it for injection is close to reflexive at this point in the room.
sqlmap -u "http://<ip>/post?id=2" -p id --dbs --batch
available databases [2]:
[*] information_schema
[*] sqhell_5
sqlmap -u "http://<ip>/post?id=2" -p id -D sqhell_5 --tables
[3 tables]
flag
posts
users
sqlmap -u "http://<ip>/post?id=2" -p id -D sqhell_5 -T flag --dump
Fifth and final flag retrieved — notably, several people who've done this room mention finding this particular injection point first by accident while just browsing, since a bare ?id= parameter is one of the most reflexive things to test on any site.
Flag Summary
| Flag | Technique | Injection Point |
|---|---|---|
| Flag 1 | In-band authentication bypass | Login form (username/password) |
| Flag 2 | Header-based blind (time-based) injection | X-Forwarded-For header, surfaced via the Terms & Conditions hint |
| Flag 3 | POST-parameter injection | Registration form's username field (existence check query) |
| Flag 4 | Manual nested UNION-based injection | users listing page |
| Flag 5 | GET-parameter injection | post?id= |
(Actual flag values are per-deploy secrets and are omitted here — retrieve them from your own instance using the methods above.)
Key Takeaways
Not every injection point is visible in the rendered page. The
X-Forwarded-Forheader vulnerability was only discoverable because the Terms and Conditions page explicitly mentioned IP logging — a reminder to actually read incidental page content, not just probe forms.Any field that performs a server-side existence or validation check against user input (like a "username already taken" check) is a separate, independent injection surface from the form's main submission logic — worth testing on its own rather than assuming only the final submit path matters.
sqlmap is the right tool once a vulnerable parameter is confirmed, but confirming it often still takes manual testing first — feeding it a raw guess without understanding the query shape wastes time; watching how the app's output changes with simple test input narrows down the technique before automating.
Nested/second-order injection (an injected string that itself gets re-evaluated as SQL later) is a distinct skill from single-layer injection, and flag 4 in this room is specifically built to force practicing it rather than letting a tool handle it end-to-end.
A room built around "one technique per flag" is a genuinely efficient way to build broad SQLi range — rather than one deep chain, each flag isolates a different context (header, POST body, GET param, nested query) so the underlying pattern for each becomes muscle memory independently.
Day 23 of 30 ✅ — See You Tomorrow