August 14, 2026
How I Systematically Find SQL Injection Bugs in Bug Bounty Programs (Step-by-Step Method)
SQLi is decades old and still one of the highest-paying bug classes in 2026 — here’s the exact recon-to-report process that actually finds…

By b0dj0x
7 min read
SQLi is decades old and still one of the highest-paying bug classes in 2026 — here's the exact recon-to-report process that actually finds it in modern apps
Everyone assumes SQL injection is basically extinct now that Django, Laravel, Rails, and Spring all use parameterized queries by default. That's true for greenfield code. It's not true for the millions of lines of legacy code, hand-rolled raw queries, and "just this one report endpoint" exceptions sitting in production right now. SQLi still shows up, still pays out at High/Critical severity, and still gets missed by people who only know how to paste ' OR 1=1-- into a login box.
Here's the process that consistently surfaces it.
Scope reminder:_ everything below is for targets you're explicitly authorized to test — a published bug bounty program, a private invite, or your own lab. Unauthorized testing is illegal regardless of intent, and a SQLi PoC that touches real data without permission can turn a bug report into a legal problem._
Step 1: Know what you're hunting for
SQLi isn't one bug, it's a family. Knowing which kind you're looking at determines your entire approach:
- In-band / Error-based — the database error message or query output is returned directly in the response. Fastest to confirm.
- Union-based — you use
UNION SELECTto pull data from other tables into a field the app already displays. - Blind Boolean-based — no errors, no visible output, but the page behaves differently (different content, different length) depending on whether your injected condition is true or false.
- Blind Time-based — same as above, but you infer true/false from response delay (
SLEEP(),WAITFOR DELAY) instead of content difference. Your last resort when nothing else leaks a signal. - Second-order — the payload is stored somewhere and only triggers a query when it's used later, elsewhere in the app. Easy to miss because the injection point and the trigger point are different requests entirely.
Most beginners only test for the first two. Blind and second-order SQLi are where a lot of the real bounty money is now, precisely because they're harder to spot.
Step 2: Map every parameter before you inject anything
You cannot test what you haven't found. This is recon, not exploitation:
- Enumerate subdomains and live hosts.
subfinder/assetfinder/amass, thenhttpxto confirm what's actually reachable. - Pull historical and current URLs.
waybackurlsandgausurface old parameters —?id=,?cat=,?redirect=— that current site crawls miss but that legacy backend code still processes. - Filter for SQLi-shaped parameters. Use
gfwith a SQLi pattern set to cut a huge URL list down to the ones actually worth testing — names likeid,page,category,product,sort,order,filter,search,user,lang. - Don't stop at GET parameters. POST body fields, JSON keys, cookie values, and headers (
User-Agent,X-Forwarded-For,Referer) all reach the database in plenty of apps. So do sort/filter parameters on admin dashboards and reporting endpoints — chronically undertested and often built with raw queries because "it's just internal." - Read the API docs / Swagger if available. Parameters that never appear in the crawled UI still exist in the API and are often less scrutinized.
Step 3: Confirm injection with the smallest possible test
Before firing sqlmap at everything, do a fast manual pass on each candidate parameter:
- Send a single quote ' and see if it breaks the response — a 500 error, a stack trace, or a visibly malformed page is a strong signal.
- Send a harmless true/false pair and compare responses:
id=1 AND 1=1vsid=1 AND 1=2. If the page content or length differs, you likely have a boolean-based injection point even with zero visible errors. - Send a deliberately mistyped SQL fragment and see if the app leaks a database error message (MySQL, MSSQL, PostgreSQL, and Oracle all have distinct, recognizable error signatures) — this alone tells you the backend and narrows your syntax.
This manual triage step is what separates people who find real bugs from people who run a scanner against every parameter and get nothing but noise.
Step 4: Escalate based on what Step 3 told you
- If you got a visible error or reflected data → move to union-based extraction. Determine the column count (
ORDER BYincrementing, orUNION SELECT NULL,NULL...), then map which columns are actually reflected back to you before pulling real data. - If the response differs but nothing is reflected → you're in boolean-blind territory. Confirm with a clean true/false pair, then look at automating the extraction rather than doing it by hand.
- If nothing differs at all → try time-based payloads (
SLEEP(5),WAITFOR DELAY '0:0:5', database-appropriate syntax) and measure response time. This is your fallback when the app gives you zero visible signal — but it's slow, so use it selectively rather than as your default. - If the parameter is stored (comment, profile field, filename) and shows no immediate effect → think second-order. Check every place that stored value later gets used — admin panels, reports, search indexes, exports — since that's where the query actually executes.
Step 5: Automate the extraction, not the thinking
Once you've manually confirmed an injection point, that's when automation earns its keep:
- sqlmap — still the standard for automated exploitation once you know a parameter is vulnerable. Feed it the exact request (via
-rwith a saved raw HTTP request) rather than letting it guess blindly across a whole site. - Ghauri — a faster, more modern alternative to sqlmap for blind and time-based cases, increasingly the go-to when sqlmap is too slow or too noisy against a WAF.
- ffuf — for fuzzing parameter names and values at scale once you have a list of candidate endpoints.
- Burp Suite (Repeater/Intruder) — for the manual confirmation work in Steps 3–4, where you need full control over the exact payload and response comparison.
Run mass automation across your filtered URL list to flag candidates broadly, then go manual and deliberate on anything that lights up. Never submit a report based purely on a scanner's "possible SQLi" flag without confirming it yourself — false positives here are common and burn your credibility with triagers.
Step 6: When a WAF is in the way
Assume one is present on any program worth hunting seriously.
- Test with alternate casing, inline comments (/**/), and whitespace variations to see if the WAF is doing simple pattern matching rather than real parsing.
- Try encoding your payload differently (URL encoding, double encoding) to see what the WAF normalizes before matching versus what the backend normalizes before executing — mismatches between the two are exactly where bypasses live.
- Look for alternate injection points on the same feature — sometimes a JSON field or a header carrying the same value isn't covered by the same WAF rule that protects the main URL parameter.
- Use tamper scripts (sqlmap has a large built-in library) as a starting point, but understand why each one works rather than trying them all blindly — that understanding is what lets you improvise when none of them fit.
The goal is the same as with any filter: understand its logic, don't just brute-force strings at it.
Step 7: Prove real impact
A SLEEP(5) delay alone convinces a lot of triagers of the bug, but severity is scored on what an attacker could actually do:
- Extract database version, current user, and schema/table names as a first proof step — enough to show real access, without pulling sensitive rows.
- Demonstrate you can reach tables containing user data (without dumping real PII into your report — reference the table/column names and row counts instead).
- Check whether the database user has elevated privileges — file read (
LOAD_FILE), file write, or even command execution (xp_cmdshellon misconfigured MSSQL) turns a data-exposure bug into a full server compromise, which is a different severity conversation entirely. - Note whether the vulnerable parameter sits behind authentication or in an admin-only context — that changes the practical blast radius significantly.
Step 8: Write the report that gets triaged fast
- Title — specific: "Blind Time-Based SQL Injection in
/api/reports?sort=Parameter (Authenticated)." - Summary — what it is, where it is, backend DB type if known.
- Steps to reproduce — exact request, exact payload, exact account state needed.
- Proof of concept — for error/union-based, a screenshot of extracted schema info; for blind, a clear before/after timing or content comparison.
- Impact — concretely: what data is exposed, whether write/RCE is possible, whether it's pre- or post-auth.
- Suggested fix — parameterized queries / prepared statements as the real fix; note that WAF rules are a mitigation, not a solution.
Common mistakes that waste your time
- Running sqlmap against every URL on a target instead of confirming manually first — noisy, slow, and often gets you rate-limited or banned from the program.
- Giving up when a quote gets filtered instead of testing boolean- and time-based signals.
- Ignoring second-order injection because the "obvious" request looks clean.
- Dumping real user data into a report as "proof" instead of showing schema/table names and row counts — this can violate program rules and turns a good report into a liability.
- Assuming a WAF means the parameter is safe. It usually just means the obvious payload is blocked.
Turning this into a Medium article that actually gets read
The technical depth is what makes this worth reading — the structure is what gets it found:
- Hook fast. Medium's feed only shows your title, subtitle, and the first couple of lines — open with a specific, slightly contrarian claim ("SQLi isn't dead, it's just hiding better") rather than a textbook definition.
- Length. Technical deep-dives like this tend to land best around 1,800–3,000 words — long enough to be genuinely useful, short enough to finish in one sitting.
- Skimmable structure. Numbered steps, bolded key terms, short paragraphs. Most readers scan the shape of the article before deciding to read it properly.
- Specific, personal titles outperform generic ones. "How I Find SQL Injection in Bug Bounty Programs" beats "A Guide to SQL Injection" — specificity and a first-person angle both help click-through.
- Tag with intent. Mix broad and narrow tags:
Cybersecurity,Bug Bounty,SQL Injection,Penetration Testing,Infosec— five tags covering both the niche and Medium's broader curated topics. - A relevant cover image matters more than people expect — it's a large share of your click-through in-feed and on social shares.
- Close with a real next step. Point readers at somewhere to practice (PortSwigger's Web Security Academy has dedicated SQLi labs) so the article ends with an action, not just a sign-off.
Disclaimer: For use on authorized bug bounty programs, CTFs, and your own lab environments only. Never extract or expose real user data as "proof" — schema names, table names, and row counts are enough to demonstrate impact without creating a data-handling problem of your own.