Injection and XSS in depth
SQL injection, command injection and the three flavours of cross-site scripting: how they work and the one idea that prevents them all.
~4 min read
The one idea behind all injection
Every injection vulnerability is the same mistake: untrusted input is mixed into something that gets interpreted, so data crosses the line into code. A query, a shell command, an HTML page: if attacker-controlled text becomes part of the instructions rather than staying data, you have injection. Hold that idea and every variant below is a special case, and so is every fix: keep code and data separate.
SQL injection
The classic. An application builds a database query by concatenating user input:
-- The developer wrote:
SELECT * FROM users WHERE username = '$input' AND password = '$pw';
-- Attacker enters username: admin'--
SELECT * FROM users WHERE username = 'admin'--' AND password = '...';
The ' closes the string early, and -- comments out the rest, so the password check vanishes and the attacker logs in as admin. Other payloads use OR 1=1 to match all rows, UNION SELECT to pull data from other tables, or stacked queries to modify data.
Variants by how the attacker sees results:
- In-band: results come straight back (error messages or the page content).
- Blind: no direct output, so the attacker infers data one bit at a time from boolean responses (does the page change?) or time-based delays (
SLEEP(5)if a condition is true).
Impact ranges from authentication bypass to dumping the entire database to, in some configurations, command execution on the host.
The fix: parameterised queries (prepared statements). The query structure is sent to the database separately from the values, so input is always treated as data and can never become SQL:
SELECT * FROM users WHERE username = ? AND password = ?;
-- values bound separately; quotes in input are now just characters
Use the database library's parameter binding, or a well-configured ORM. Input validation and least-privilege database accounts are useful defence in depth, but parameterisation is the actual fix; escaping by hand is error-prone and not sufficient.
Command injection
The same flaw against the operating system shell. An app that builds a shell command from input:
# App runs: ping -c1 $userinput
# Attacker enters: 8.8.8.8; cat /etc/passwd
ping -c1 8.8.8.8; cat /etc/passwd
The ; chains a second command (|, &&, backticks and $() do similar). Because it runs with the application's privileges, the impact is often full system compromise. The fix: avoid shelling out at all where a native library call exists; if you must, use APIs that pass arguments as a list (not a single string the shell re-parses) and validate strictly against an allowlist.
Cross-site scripting (XSS)
XSS is injection into a web page: the attacker gets their JavaScript to run in another user's browser, in the context of the vulnerable site. Because it runs as that site, it can read the DOM, steal cookies (unless HttpOnly), make authenticated requests as the victim, log keystrokes, or deface the page. OWASP classifies XSS under Injection (A05).
Three types, a guaranteed exam question:
- Stored (persistent) XSS: the payload is saved on the server (a forum post, a profile field, a comment) and served to everyone who views it. The most dangerous, because it hits every visitor without any per-victim action.
- Reflected XSS: the payload is in the request (e.g. a search term echoed back in the results page) and only affects the person who follows the crafted link. Delivered via phishing.
- DOM-based XSS: the vulnerability is entirely in client-side JavaScript that writes untrusted input into the page (e.g. via
innerHTML) without the server ever being involved.
The fix: context-aware output encoding. When you put data into a page, encode it for the context so the browser treats it as text, not markup: < becomes < in HTML, with different rules inside attributes, JavaScript and URLs. Modern frameworks (React, Angular) auto-encode by default, which is why XSS is rarer in their code, until a developer reaches for a raw-HTML escape hatch like dangerouslySetInnerHTML. Layer on:
- Content Security Policy (CSP): a header restricting which scripts may run, so even an injected
<script>is blocked. A strong second line of defence. HttpOnlycookies: so a successful XSS still can't read the session cookie.- Input validation: reject obviously malformed input, but never rely on it alone for XSS; encoding on output is the real control.
Why these dominate
Injection flaws are pervasive because the unsafe way (string concatenation) is the natural, intuitive way to write the code, and it works perfectly in testing with friendly input. The safe way requires knowing the parameterised/encoded pattern and using it every single time. That asymmetry (easy-but-wrong vs deliberate-but-right) is the recurring theme of application security.
Quick recall
- All injection = untrusted input crossing from data into code. Fix = keep them separate.
- SQL injection: closes out the query (
'--,OR 1=1,UNION); fix with parameterised queries, not manual escaping. Blind variants use boolean/time inference. - Command injection chains shell commands (
;,|,$()); avoid the shell, pass args as a list. - XSS runs attacker JS in a victim's browser. Stored (saved, hits all), Reflected (in the link, one victim), DOM-based (client-side only).
- XSS fix = context-aware output encoding; reinforce with CSP and HttpOnly cookies.