Injections & Safe Sinks
Injection vulnerabilities occur when untrusted user input is sent to an interpreter as part of a command or query. The interpreter is tricked into executing unintended commands or accessing unauthorized data.
Core details
| Vulnerability Class | Boundary | Primary Control (Safe Sink) |
|---|---|---|
| SQL Injection (SQLi) | String interpolation -> DB Query parser | Prepared statements / Parameterized queries |
| Command Injection | Raw command string -> Operating system shell | Avoid shell invocation; pass arguments as arrays to structured APIs |
| Path Traversal | Unvalidated file path -> File system read/write | Path canonicalization, sandboxing, and ID-to-path mappings |
| Unsafe Deserialization | Unsigned byte stream -> Memory object graph | Use structured data formats (JSON, Protobuf) with schema validation |
[!IMPORTANT] The primary defense against injection is the strict separation of instructions (code) from arguments (data). Input validation is a defense-in-depth measure, but it is never a substitute for using safe, parameterized sinks.
Understanding
How Sinks Work
A "sink" is a function or method where data is consumed or executed. A dangerous sink interprets the input string as code. A safe sink strictly separates data from the execution context.
- SQLi Example: In SQL,
SELECT * FROM users WHERE name = '+input+'is an unsafe sink because the database compiler parses the resulting string. UsingSELECT * FROM users WHERE name = ?binds the parameter dynamically at the database driver layer, making execution immune to structure manipulation. - Command Injection Example: Running
exec("nslookup " + userHost)invokes a shell/bin/shwhich parses meta-characters like;,&, or|. RunningexecFile("nslookup", [userHost])spawns the binary directly without a shell, treating the argument as a literal string.
Senior understanding
Code Patterns: Safe vs Unsafe Sinks
Node.js Command Execution
Always prefer spawn or execFile over exec to prevent shell command injection:
import { exec, execFile } from 'node:child_process';
// ❌ UNSAFE: Invokes a system shell, allowing attackers to append "; rm -rf /"
function pingHostUnsafe(host: string) {
exec(`ping -c 3 ${host}`, (err, stdout) => {
console.log(stdout);
});
}
// SAFE: Spawns the binary directly. Input is passed as a literal argument
function pingHostSafe(host: string) {
execFile('ping', ['-c', '3', host], (err, stdout) => {
if (err) {
console.error('Execution failed', err);
return;
}
console.log(stdout);
});
}Safe Path Resolution
Preventing path traversal (e.g., ../../etc/passwd):
import path from 'node:path';
import fs from 'node:fs';
const SAFE_ROOT = path.resolve('/var/www/uploads');
function serveFileSafe(userFilename: string): Buffer {
// 1. Resolve to absolute path
const safePath = path.resolve(SAFE_ROOT, userFilename);
// 2. Enforce trust boundary
if (!safePath.startsWith(SAFE_ROOT)) {
throw new Error('Access Denied: Path Traversal Attempt');
}
return fs.readFileSync(safePath);
}Diagram
See also
Mark this page when you finish learning it.
Spotted something unclear or wrong on this page?