Code Injection vulnerabilities are among the most severe application security flaws because they collapse the boundary between data and program logic. When untrusted input is interpreted as code by an application runtime, template engine, interpreter, or dynamic execution feature, the attacker gains the ability to execute arbitrary logic within the application’s trust boundary.
CWE-94 occurs when software constructs code using externally influenced input and executes it without properly neutralizing that input.
In practical terms:
The attacker turns application input into executable program logic.
This article breaks down how code injection works, why developers still introduce it, modern exploitation techniques, framework-specific mitigations, and secure coding patterns.
What Is Code Injection?
Code Injection happens when an application dynamically evaluates input as source code, script, expression, or template logic.
Unsafe example:
user_expr = request.args["expr"]
result = eval(user_expr)
If the attacker supplies:
__import__('os').system('id')
The application executes attacker-controlled code.
How Code Injection Actually Works
The root problem is that dynamic execution features treat attacker-controlled input as executable logic instead of inert data.
Common Dangerous Sinks
eval()exec()- Dynamic template rendering
- Expression language evaluation
- Runtime compilation / scripting engines
- Deserialization into executable object graphs
Attack Flow
- Application accepts user-controlled input
- Input is embedded into executable context
- Runtime/interpreter parses input as code
- Attacker logic executes
- Application/system compromise occurs
Visual: Code Injection Data Flow
Why Developers Still Get Code Injection Wrong
Convenience Over Safety
Dynamic execution is tempting for:
- Formula engines
- Search/filter builders
- Rule engines
- Admin scripting features
- Dynamic templates
“Only Trusted Users Can Reach It”
Administrative or internal-only execution features still get abused after account compromise.
Template Engine Misunderstanding
Developers assume templates are data-only when many engines support:
- Logic statements
- Function calls
- Object traversal
- Reflection/introspection
Sandbox Overconfidence
Interpreter sandboxes are notoriously difficult to secure.
Modern Exploitation Techniques
Direct Runtime Execution
Classic eval() / exec() abuse.
Server-Side Template Injection (SSTI)
Template payloads execute application/runtime logic:
{{ config.__class__.__init__.__globals__['os'].system('id') }}
Expression Language Injection
Abuse dynamic expression parsers:
- Spring Expression Language (SpEL)
- OGNL
- MVEL
- JEXL
Sandbox Escape
Break out of restricted runtimes via reflection/introspection.
Chained Deserialization / Eval Abuse
Unsafe object construction eventually triggers execution.
Visual: Code Injection Exploitation Chain
Framework-Specific Mitigations
Eliminate Dynamic Evaluation
Unsafe:
eval(user_input)
Safer:
allowed_ops = {"add": add, "sub": sub}
allowed_ops[user_choice](a, b)
Replace evaluation with explicit dispatch/allowlists.
Safe Template Rendering
Never treat user input as template source.
Unsafe:
render_template_string(user_template)
Safer:
render_template("page.html", content=user_data)
Restrict Expression Engines
Disable dangerous features where configurable:
- Reflection
- Method invocation
- Class access
- Imports
Secure Coding Examples
Unsafe
eval(userScript)
Safer Alternative
const operations = {
add: (a,b) => a+b
}
Unsafe Dynamic Import
module = __import__(user_input)
Safer
ALLOWED_MODULES = {"math": math}
module = ALLOWED_MODULES[user_input]
Defense in Depth
Run Untrusted Logic in Isolation
If dynamic execution is unavoidable:
- Separate process/container
- Restricted privileges
- Network isolation
- Resource/time limits
Log Dynamic Execution Events
Alert on:
- Unexpected template compilation
- Eval/exec invocation
- Expression parser failures
- Sandbox violations
Review Admin Features Carefully
“Power user” functionality often creates injection risk.
Final Thoughts
Code Injection is dangerous because it erases the distinction between input and execution.
It persists because:
- Dynamic execution is convenient
- Sandboxing is difficult
- Developers underestimate template/expression power
- “Internal-only” assumptions fail repeatedly
The core lesson is simple:
If users can influence code that gets interpreted, they can often influence what your application does.
Treat dynamic execution as a last resort, not a convenience.

2 thoughts on “CWE-94: Code Injection — When User Input Becomes Executable Logic”