THN Interview Prep

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 ClassBoundaryPrimary Control (Safe Sink)
SQL Injection (SQLi)String interpolation -> DB Query parserPrepared statements / Parameterized queries
Command InjectionRaw command string -> Operating system shellAvoid shell invocation; pass arguments as arrays to structured APIs
Path TraversalUnvalidated file path -> File system read/writePath canonicalization, sandboxing, and ID-to-path mappings
Unsafe DeserializationUnsigned byte stream -> Memory object graphUse 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. Using SELECT * 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/sh which parses meta-characters like ;, &, or |. Running execFile("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

Loading diagram…

See also

Mark this page when you finish learning it.

Spotted something unclear or wrong on this page?

On this page