Lesson 9 / 25

Command, Template and Other Injection

The same pattern in other interpreters.

Avoid interpreters for untrusted input

Injection applies to any interpreter: OS commands (input passed to a shell), server-side templates (user input compiled as a template), LDAP, XPath, NoSQL query operators and even LLM prompts. Prefer library APIs over shelling out; when you must run a program, pass arguments as a list without a shell and validate them against an allow-list. Never render user input as a template, and validate input types strictly (for example reject objects where strings are expected in NoSQL queries).

Shell injection and the safe alternative

Python subprocess.

import subprocess

# VULNERABLE: "file.png; rm -rf /" runs a second command
subprocess.run(f"convert {filename} out.jpg", shell=True)

# SAFER: no shell, arguments as a list, input validated
import re
if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}\.png", filename):
    raise ValueError("invalid file name")
subprocess.run(["convert", filename, "out.jpg"], check=True, timeout=30)

Validate types, not just strings

A JSON body like {"password": {"$ne": null}} can bypass naive NoSQL login checks; require a string.

Quick check: What is the safest way to run an external program with user-supplied input?

  • Concatenate strings carefully
  • Use shell=True with quotes
  • Pass arguments as a list without a shell, after allow-list validation
  • Run it as root
Answer

Pass arguments as a list without a shell, after allow-list validation — Avoid the shell interpreter.