Injection: SQL and Command
Injection happens when user input is mixed into a command or query so that the input's data is reinterpreted as code — the attacker's string becomes part of the SQL, the shell command, or the LDAP filter the server runs. SQL injection against app.meridian.example can dump the entire customer database; command injection can run arbitrary code on the host.
This topic shows a real login flow exploited and then fixed with the one durable defense: never build commands by concatenating input — separate code from data, and the input can never cross into the code.
How Injection Works
A query built by string concatenation puts data and code in the same channel, so input that contains SQL syntax changes the query's meaning. The flaw is structural: when the query is assembled as text, there is no boundary between the developer's SQL and the user's input, so the user can write SQL. Everything about injection follows from that one confusion of data and code.
# VULNERABLE: input is concatenated into the query as text
query = "SELECT * FROM users WHERE email='" + email + "' AND pw_hash='" + h + "'"
# attacker submits email: ' OR '1'='1' --
# query becomes: ... WHERE email='' OR '1'='1' --' AND pw_hash='...'
# the OR '1'='1' is always true and the -- comments out the password check
# FIXED: parameterized query — the driver sends structure and data separately
cur.execute("SELECT * FROM users WHERE email=%s AND pw_hash=%s", (email, h))
# now the input can ONLY be a value, never SQL syntax
In the vulnerable version, the input ' OR '1'='1' -- turns the login check into something always true and comments out the password comparison, logging the attacker in as the first user. The fixed version sends the query structure and the data to the database separately, so the input is treated strictly as a value — it can never become SQL, no matter what it contains. That separation is the whole fix.
The Fix: Parameterized Queries
Prepared statements, or parameter binding, send the query structure and the data on separate channels, so input can never become code. This is the near-total fix for SQL injection, and ORMs help only when they parameterize — the moment you drop to an ORM's raw-query escape hatch with concatenated input, the vulnerability is back. Parameterize every query, with no exceptions for the "simple" ones.
Command and Other Injection
Passing input to a shell reproduces the same flaw as OS command injection, and it recurs in LDAP, NoSQL, XPath, and template engines (server-side template injection). Each is the same data-as-code confusion in a different interpreter, and each is fixed the same way: call a safe, argument-array API instead of building a command string, and avoid the shell entirely where you can.
Defense in Depth Around Injection
Parameterization comes first, but it is worth layering: run the app's database account with least privilege so a successful injection reads a limited slice rather than everything, validate and allow-list input as a second layer, and put a WAF in front as a backstop. The WAF is never the primary fix — it catches patterns and is bypassable — but as one layer over correct code it buys time.
Parameterization — separates code from data structurally; input cannot become SQL regardless of content. The correct, near-complete fix.
Escaping / blocklisting — tries to sanitize dangerous characters; brittle, bypassable by alternate encodings and edge cases, and perpetually behind the attacker. A fragile patch, not the solution.
- Building queries or shell commands by concatenating user input — the single root cause; even one concatenated query is a full-database risk.
- Relying on blocklists or escaping special characters instead of parameterization, which attackers bypass with alternate encodings.
- Trusting an ORM blindly while using its raw-query escape hatch with concatenated input, silently reintroducing SQL injection.
- Running the app's database account as a superuser, so a successful injection reads and writes everything instead of a limited slice.
- Passing user input to a shell instead of a safe argument-array API.
- Use parameterized queries or prepared statements for every database access, with no exceptions for "simple" queries.
- Call safe, argument-array APIs instead of a shell for system commands, avoiding the shell entirely where possible.
- Apply least privilege to the database and OS accounts the app uses, so injection's payoff is bounded.
- Add server-side input validation and a WAF as additional layers, understanding they backstop — not replace — parameterization.
- Test your own endpoints for injection so a concatenated query is caught before an attacker finds it.
Knowledge Check
What is the root cause of injection vulnerabilities?
- Input shares the channel with code, so data runs as commands
- The database uses weak encryption for stored data
- The server runs an outdated, unpatched version of the SQL engine
- Passwords are hashed instead of encrypted
Why are parameterized queries the correct fix rather than escaping dangerous characters?
- They send structure and data apart; input never becomes SQL
- Escaping is slower than parameterization
- Escaping only works on MySQL, not on any other database engine
- Parameterized queries encrypt the input in transit
Why run the app's database account with least privilege even after parameterizing?
- Defense in depth — any injection reads a limited slice
- Least privilege replaces the need for parameterized queries
- A limited account makes queries run faster
- It prevents the app from connecting to the database at all
You got correct