CWE-94: Code Injection — When User Input Becomes Executable Logic

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

  1. Application accepts user-controlled input
  2. Input is embedded into executable context
  3. Runtime/interpreter parses input as code
  4. Attacker logic executes
  5. Application/system compromise occurs

Visual: Code Injection Data Flow

1. User Input Malicious Expression 2. Dynamic Eval eval / exec / Template 3. Runtime Parses as Code 4. Result RCE / Logic Abuse

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

Code Injection Logic Manipulation RCE Sandbox Escape System Compromise

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.