10.2 Input Forms, File Uploads, and Simple Injection Ideas
Untrusted Input Is a Language Problem
Injection is not exotic malware; it is a syntax accident. Every field a user can type -- search boxes, profile bio text, headers, filename parameters, JSON values -- arrives as bytes that the server later pastes into some interpreter: SQL, a shell command, a template, an LDAP filter, an HTML page. The program intends those bytes to be data; the interpreter, fooled by structure characters like quotes or semicolons, reads them as instructions. The fix is never to sanitize cleverly, it is to keep data and code on separate channels so structure characters stop mattering.
Concatenation Is the Vulnerability
Watch what happens when a query is built with string concatenation. The developer writes something like SELECT * FROM users WHERE name = ' and appends the submitted value, then closes the quote. Type alice and the statement is boring. Type ' OR '1'='1 instead and the first character closes the developer's quote, the middle becomes a condition that is true for every row, and the tail quote is absorbed as a comment or balanced pair. The attacker did not attack the database -- they finished the developer's sentence. Variants stack: UNION SELECT pulls other tables into the response, stacked statements can write files where the dialect allows it, and blind variants infer data one yes-or-no question at a time through timing or error behavior. The OWASP Top Ten lists injection as a permanent class precisely because concatenation keeps reappearing in new frameworks.
File Uploads and the Extension Lie
Uploads are injection with a filename. A box that accepts "any document" and stores it under its original name inside the web root turns a request into remote execution the moment someone requests shell.php. Content-Type is client-supplied theater; magic bytes can be faked in the first kilobyte; so the durable controls are an extension allowlist, a generated random filename, a stored path outside the document root or on separate storage, no execute bit, and image re-encoding that destroys embedded payloads. Add path traversal on the name -- ../../ segments -- and one unchecked field escapes its own directory.
Closing the Class of Bug
Parameterized queries with bound arguments make SQLi structurally impossible; allowlist validation beats denylist pattern matching; context-aware output encoding neutralizes reflected HTML; shells get invoked with argument arrays rather than concatenated strings. The OWASP Testing Guide and the OWASP Cheat Sheet Series organize exactly these controls by interpreter, because every layer that eats strings needs a boundary.
Architecture Diagram
Key Takeaways
- Injection happens whenever user bytes are pasted into an interpreter, so the field's purpose never determines its risk.
- A single quote in
' OR '1'='1closes the developer's own string literal and turns data into an always-true predicate. - Parameterized queries with bound arguments remove the vulnerability class rather than filtering it.
- Content-Type and magic bytes are attacker-controlled; extension allowlists, random names, and non-executable storage are not.
- Validate with allowlists, encode output per context, and treat every string-building API as a boundary worth reviewing.