Skip to main content

04. A03: Injection (SQLi, Command Injection & SSRF)

Injection vulnerabilities occur when untrusted data is sent to an interpreter as part of a command or query. The attacker's hostile data tricks the interpreter into executing unintended commands or accessing data without proper authorization.


[!CAUTION] The Golden Rule of Injection Prevention: Never construct commands or database queries by concatenating, formatting, or interpolating untrusted user input directly into executable code strings. Always separate data from code!


1. SQL Injection (SQLi)

Vulnerability Mechanics

SQL Injection occurs when user input alters the abstract syntax tree (AST) of a database query.

Types of SQL Injection

  1. In-Band (Classic): Results returned directly in HTTP response.
    • Error-Based: Forces database to display internal error stack containing sensitive data.
    • UNION-Based: Appends UNION SELECT to retrieve data from arbitrary tables.
  2. Inferential (Blind): No data returned in HTTP response body.
    • Boolean-Based: Infers data by observing true/false response variations.
    • Time-Based: Infers data by injecting delay commands (e.g. pg_sleep(5) or WAITFOR DELAY '0:0:5').
  3. Out-of-Band (OOB): Data exfiltrated via DNS or HTTP requests triggered by the database (e.g. xp_dirtree in MSSQL).

Multi-Language SQLi Code Fixes

A. Python (psycopg2 / PostgreSQL)

❌ Vulnerable Code (Python)
# VULNERABLE: f-string string interpolation modifies SQL AST
def search_user_bad(cursor, username: str):
query = f"SELECT id, role, email FROM users WHERE username = '{username}'"
cursor.execute(query) # Vulnerable to SQLi!
return cursor.fetchall()
✅ Secure Code (Python)
# SECURE: Parameterized query sends SQL statement and parameters separately
def search_user_secure(cursor, username: str):
query = "SELECT id, role, email FROM users WHERE username = %s"
cursor.execute(query, (username,)) # Safe! Driver handles string escaping.
return cursor.fetchall()

B. Node.js (PostgreSQL / pg)

❌ Vulnerable Code (Node.js)
// VULNERABLE: Direct string concatenation
app.get('/api/users/search', async (req, res) => {
const name = req.query.name;
const sql = "SELECT * FROM users WHERE name = '" + name + "'";
const result = await db.query(sql); // Vulnerable!
res.json(result.rows);
});
✅ Secure Code (Node.js)
// SECURE: Positional parameters (`$1, $`2)
app.get('/api/users/search', async (req, res) => {
const name = req.query.name;
const sql = "SELECT * FROM users WHERE name = $1";
const result = await db.query(sql, [name]); // Safe!
res.json(result.rows);
});

C. Go (database/sql)

❌ Vulnerable Code (Go)
// VULNERABLE: fmt.Sprintf query generation
func GetUserBad(db *sql.DB, name string) (*User, error) {
query := fmt.Sprintf("SELECT id, email FROM users WHERE name = '%s'", name)
row := db.QueryRow(query) // Vulnerable!
// ...
}
✅ Secure Code (Go)
// SECURE: Parameterized placeholder (?)
func GetUserSecure(db *sql.DB, name string) (*User, error) {
query := "SELECT id, email FROM users WHERE name = ?"
row := db.QueryRow(query, name) // Safe!
// ...
}

D. Java (Spring JDBC / JPA)

❌ Vulnerable Code (Java)
// VULNERABLE: String concatenation in Statement
public User getUserBad(String username) throws SQLException {
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql); // Vulnerable!
// ...
}
✅ Secure Code (Java)
// SECURE: PreparedStatement binding
public User getUserSecure(String username) throws SQLException {
String sql = "SELECT id, email, role FROM users WHERE username = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery(); // Safe!
// ...
}

2. Command Injection

Command Injection occurs when application code executes system shell commands (bash, sh, cmd.exe) using untrusted input.

Exploit Mechanics

Attacker input containing shell operators (;, &&, |, `) executes arbitrary OS commands:

  • Payload: 8.8.8.8; cat /etc/passwd
  • Executed Command: ping -c 1 8.8.8.8; cat /etc/passwd

Multi-Language Command Execution Safeguards

❌ Vulnerable Python Implementation

import os

# VULNERABLE: Invokes system shell (/bin/sh) with unsanitized user input
def check_host_bad(target: str):
os.system(f"ping -c 1 {target}")

✅ Secure Python Implementation

import subprocess
import ipaddress

# SECURE: Strict input validation + no shell invocation context
def check_host_secure(target: str) -> str:
# 1. Input format validation
try:
valid_ip = str(ipaddress.ip_address(target))
except ValueError:
raise ValueError("Invalid IP address format")

# 2. Execute process without shell (shell=False by default)
# Pass arguments as a rigid array to prevent shell command parsing
result = subprocess.run(
["ping", "-c", "1", valid_ip],
capture_output=True,
text=True,
timeout=5,
check=True
)
return result.stdout

3. Server-Side Request Forgery (SSRF)

SSRF occurs when a web application fetches a remote resource (e.g. fetching a profile picture from a URL) without validating whether the target IP address resolves to an internal or restricted network.


SSRF Bypasses & Nuances

  1. Cloud IMDS Exploitation: Accessing 169.254.169.254 to steal AWS/GCP/Azure instance metadata credentials.
  2. Alternate IP Formats: Attackers bypass simple string matching using:
    • Hexadecimal: 0x7f000001 (127.0.0.1)
    • Decimal: 2130706433 (127.0.0.1)
    • IPv6 Mapped IPv4: [::ffff:127.0.0.1]
  3. DNS Rebinding: A domain initially resolves to a safe public IP during validation, but shifts to 127.0.0.1 during the actual HTTP fetch.

Production-Grade SSRF Defense (Python)

import socket
import urllib.parse
import ipaddress
import requests

BLOCKED_NETWORKS = [
ipaddress.ip_network('127.0.0.0/8'), # Loopback
ipaddress.ip_network('10.0.0.0/8'), # Private class A
ipaddress.ip_network('172.16.0.0/12'), # Private class B
ipaddress.ip_network('192.168.0.0/16'), # Private class C
ipaddress.ip_network('169.254.0.0/16'), # AWS/Cloud Metadata IMDS
ipaddress.ip_network('0.0.0.0/8'),
]

def validate_and_fetch_url(url: str, timeout: int = 3) -> bytes:
# 1. Scheme Check
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ('http', 'https'):
raise ValueError("Invalid URL scheme")

hostname = parsed.hostname
if not hostname:
raise ValueError("Invalid hostname")

# 2. DNS Resolution & IP Range Verification
try:
ip_addresses = socket.getaddrinfo(hostname, None)
except socket.gaierror:
raise ValueError("Domain resolution failed")

for family, socktype, proto, canonname, sockaddr in ip_addresses:
ip = ipaddress.ip_address(sockaddr[0])
# Check against blocked private/internal networks
for blocked_net in BLOCKED_NETWORKS:
if ip in blocked_net:
raise SecurityError(f"Access to private IP range ({ip}) is forbidden!")

# 3. Safe HTTP Request with Disabled Redirects (Prevents SSRF via 302 Redirect)
response = requests.get(url, allow_redirects=False, timeout=timeout)
return response.content

[!TIP] Cloud Defense Best Practice: Enforce AWS IMDSv2, which requires a session token fetched via PUT header (X-aws-ec2-metadata-token), preventing simple GET-based SSRF attacks from accessing instance metadata!


Next Chapter: 05. A04 & A05: Insecure Design & Misconfiguration →

Share this guide