Injection: SQL, XSS, and the Untrusted Input Problem
For over a decade, injection topped the OWASP Top 10. In 2021 it slipped to third — not because it was solved, but because access control edged ahead. The revision also folded cross-site scripting (XSS) into this category, recognising that a script injected into a page and a query injected into a database are the same bug wearing different clothes: untrusted input interpreted as a command.
Injection happens when data supplied by a user crosses into a place where it is executed as code. An interpreter — a SQL engine, a shell, an LDAP directory, a browser's HTML parser — receives a string that mixes the developer's intended instructions with the attacker's, and cannot tell them apart. The interpreter does what it always does: it runs what it was given.
SQL injection
The archetype is SQL injection. A query built by concatenating user input lets an attacker rewrite its logic — bypassing a login, dumping a table, or, with stacked queries, altering data. The fix is not cleverer filtering; it is parameterised queries, which send the query structure and the data on separate channels so the input can never become part of the command.
input = ' OR '1'='1 → returns every row; authentication defeated
Cross-site scripting
XSS is injection into the browser. Untrusted input is reflected into a page without encoding, and the victim's browser executes it as script — stealing session tokens, rewriting the page, or acting as the user. It comes in three shapes: reflected (payload echoed from the request), stored (payload saved and served to every viewer), and DOM-based (client-side JavaScript writes untrusted data into the page). The defence is contextual output encoding, so data rendered into HTML, attributes, or scripts is always treated as text, never as markup.
Command and the rest
The same pattern recurs wherever an interpreter meets untrusted input. OS command injection passes user data to a shell that runs it as a command. LDAP, XPath, NoSQL, and template injection are all variations on the theme. The category is broad precisely because the mistake is general: trusting input at the boundary where it becomes executable.
Preventing it
- Use parameterised queries and prepared statements for all database access. Never build queries by concatenation.
- Encode output for its context. HTML-encode, attribute-encode, or JavaScript-encode data at the point it is rendered, and prefer frameworks that do this by default.
- Avoid calling out to a shell; use safe library APIs. Where a command is unavoidable, pass arguments as a list, never as an interpolated string.
- Validate input positively. Allow-list expected formats server-side; treat validation as defence in depth, not the primary control.
- Deploy a Content Security Policy to blunt the impact of any XSS that slips through.
- Use static analysis and dynamic testing to find injection sinks before attackers do.
Injection has been on this list since the beginning because the fix is well understood and yet the mistake keeps recurring in new code and new interpreters. Every input that reaches an interpreter is a place to get it wrong — which is why knowing where your untrusted data flows is the precondition for keeping it safe.
- In one input, enter a lone single quote
'and submit. A database error or 500 in response is a sign the input is reaching a query unescaped. - In a field that echoes back on screen, submit a harmless marker like
<i>test</i>. If it renders as italics rather than text, output isn't being encoded.
The full, at-scale version — tracing untrusted input into every SQL, command, template and DOM sink — safely, and across the whole app — is what our assessment (and SecStudio agents) runs for you.