8 min read

Intermediate

Injection Remediation

Risk Severity
Critical
Fix Effort
Medium
Est. Time
2-4 hours
Reference
A05:2025 CWE-89, CWE-78

Injection flaws, such as SQL, OS, and LDAP 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.

Why Injection Is Critical

Injection attacks consistently rank in the OWASP Top 10 because they can lead to complete database compromise, data theft, authentication bypass, and even full server takeover. A single vulnerable query can expose your entire database.

Injection Attack Flow & Defense

SQL Injection (SQLi)

SQL injection occurs when user input is concatenated directly into a database query. The primary defense is the use of parameterized queries (also known as prepared statements).

Vulnerable Examples

Python (Vulnerable):

vuln-example.py
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)

Java (Vulnerable):

String query = "SELECT * FROM users WHERE id = " + userId;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);

PHP (Vulnerable):

$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = mysqli_query($conn, $query);

Node.js (Vulnerable):

const query = `SELECT * FROM users WHERE id = ${req.params.id}`;
db.query(query);

Secure Implementation: Parameterized Queries

🔐 How Parameterized Queries Protect You

Parameterized queries (prepared statements) separate the SQL code from the data. The database driver handles escaping and quoting automatically, so user input is always treated as data, never as executable SQL code.

When you use ? or :param placeholders, the database compiles the query structure first, then safely binds the values. Even if an attacker submits ' OR 1=1 --, it's treated as a literal string, not SQL syntax.

Python (sqlite3):

cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))

Python (SQLAlchemy ORM):

user = session.query(User).filter(User.id == user_id).first()

Java (Prepared Statement):

String query = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(query);
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();

PHP (PDO):

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);
$user = $stmt->fetch();

Node.js (mysql2):

const [rows] = await connection.execute(
  'SELECT * FROM users WHERE id = ?',
  [req.params.id]
);

C# (Entity Framework):

var user = context.Users.FirstOrDefault(u => u.Id == userId);

SQL Injection Prevention Checklist

  • ✅ Use parameterized queries / prepared statements for ALL database queries
  • ✅ Use ORM frameworks (SQLAlchemy, Hibernate, Entity Framework, Sequelize)
  • ✅ Implement input validation (whitelist allowed characters)
  • ✅ Apply principle of least privilege to database accounts
  • ✅ Escape special characters if dynamic queries unavoidable
  • ✅ Use stored procedures with parameterized inputs
  • ✅ Enable WAF rules for SQL injection patterns
  • ✅ Regular code review and SAST scanning

Command Injection

Command injection occurs when an application passes unsafe user supplied data (forms, cookies, HTTP headers, etc.) to a system shell.

Vulnerable Examples

Python (Vulnerable):

vuln-cmd.py
import os
os.system(f"ping -c 4 {user_input}")

Node.js (Vulnerable):

const exec = require('child_process').exec;
exec(`ping -c 4 ${userInput}`);

PHP (Vulnerable):

system("ping -c 4 " . $_GET['host']);

Secure Implementation

Python (subprocess with list arguments):

import subprocess
import shlex
import re

def safe_ping(host):
    # Validate input
    if not re.match(r'^[a-zA-Z0-9.-]+$', host):
        raise ValueError("Invalid hostname")
    
    # Use list form (no shell interpretation)
    result = subprocess.run(
        ['ping', '-c', '4', host],
        capture_output=True,
        text=True,
        timeout=30
    )
    return result.stdout

Node.js (execFile with array arguments):

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

function safePing(host, callback) {
    // Validate input
    if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
        return callback(new Error('Invalid hostname'));
    }
    
    execFile('ping', ['-c', '4', host], (error, stdout, stderr) => {
        callback(error, stdout);
    });
}

PHP (escapeshellarg):

$host = escapeshellarg($_GET['host']);
$output = shell_exec("ping -c 4 " . $host);

Best Practice

The best way to prevent command injection is to avoid calling system commands entirely. Use built-in language APIs instead (e.g., use a library to parse XML instead of calling a command-line tool).

Testing Verification

After implementing fixes, verify they work with these test payloads:

SQL Injection Test Payloads

Basic Tests:
' OR '1'='1 ' OR 1=1-- 1; DROP TABLE users--
Expected Result:

Query should fail or return no results

No SQL errors exposed to user

Application logs show bound parameters

Command Injection Test Payloads

Basic Tests:
; ls -la | cat /etc/passwd $(whoami)
Expected Result:

Command not executed

Input rejected or sanitized

Only expected output returned

# Automated testing with SQLMap (verify fix blocks injection)
sqlmap -u "https://example.com/api/user?id=1" --batch --level=3

# If properly fixed, SQLMap should report:
# "all tested parameters do not appear to be injectable"

Common Mistakes

Blacklist Filtering

Trying to block specific keywords like "SELECT", "DROP", "UNION" is easily bypassed with encoding, case variations, or alternative syntax.

SeLeCt, %53%45%4C%45%43%54, /*!SELECT*/

Escaping Instead of Parameterization

Using escape functions like mysql_real_escape_string() or manual quote escaping is error-prone and doesn't protect against all injection vectors. Always use parameterized queries.

Parameterizing Only Some Inputs

Fixing user-facing inputs but leaving admin panels, internal APIs, or batch jobs vulnerable. ALL user-controlled data must use parameterized queries.

Using String Interpolation with ORMs

Even with ORMs, using raw queries with string interpolation reintroduces vulnerabilities.

Injection Remediation Labs

Practice identifying and fixing injection vulnerabilities.

SQLi Fix Challenge PortSwigger easy
Parameterized queriesORM usageInput validation
Command Injection Remediation DVWA medium
subprocess with list argsInput allowlistingAvoiding shell calls

Evidence to Capture

Request and response: The full request with the payload in the parameter, plus the response that differs: the extra row, the database error text, or the 200 where a rejection was expected.

Differential pair: The true and false condition at identical length, AND 1=1 against AND 1=2, with the response length or hash for each, so the difference is attributable to the query rather than to caching.

Timing for blind cases: Baseline and injected response times over at least ten samples each, with the concurrency stated. One slow response is coincidence, not evidence.

Command execution proof: For OS command injection, the output of id or whoami returned by the host, or the out-of-band DNS or HTTP interaction with its source address.

Sink: The file and line where input reaches the interpreter, from review, or the endpoint and parameter owner where source is not available.

Likely False Positives

  • Parameterised query flagged by a scanner: A bound parameter can still change the response when the driver raises a type error or returns a different row count. Confirm the binding in the query log before reporting injection.
  • Payload reflected, not interpreted: The value comes back HTML-escaped or JSON-escaped. Input reached output, not an interpreter.
  • Error from schema drift: If a benign value of the same length also returns 500, the cause is a migration or column mismatch. Check which log produced it: application or database.
  • Timing noise: Cache misses and row locks produce the same single-sample signal as a blind injection. Report only a separated distribution.

Testing Checklist

Discovery

  • ☐ Inventory every parameter that reaches a query, shell, LDAP or XPath call: query string, body, JSON keys, headers, cookies and path segments.
  • ☐ Include admin, internal and batch or import paths. Fixes usually stop at the user-facing form.
  • ☐ Record, per parameter, whether the value is reflected, compared, stored or used to build a statement.

Confirmation

  • ☐ Send the true and false pair at equal length and compare body, length and timing, not just the status code.
  • ☐ For OS commands, confirm with an out-of-band interaction rather than command output the application may swallow.
  • ☐ Re-run the same payload after the fix and capture the rejection together with the generic error shown to the user.

Impact

  • ☐ Establish what the database account can do: other schemas, FILE or xp_cmdshell turns a read into data theft or code execution.
  • ☐ Test whether UNION or stacked queries reach rows belonging to another tenant.
  • ☐ For command injection, record the process user and the reachable filesystem, so the finding states blast radius rather than execution alone.

Remediation

Structural fix

  • • Bind every value: prepared statements or the ORM query builder for all statements, including fragments that come from a sort or column parameter.
  • • Execute commands with an argument array and no shell: subprocess.run([...]), execFile, never a built string through sh -c.

Compensating control

  • • Least-privilege database accounts without DDL or FILE, xp_cmdshell disabled, and a WAF virtual patch only until the code change ships.
  • • Generic error responses with a correlation id, so a failed injection attempt is not itself a schema disclosure.

CWE References: CWE-89, CWE-78