Code Injection

Eval InjectionDynamic Code ExecutionExpression InjectionTemplate Execution Injection

Code Injection at a glance

What it is: User input is treated like code and executed by the application, for example eval, SpEL, C# scripting, or ERB.
Why it happens: Lets attackers run instructions with your app's privileges
How to fix: Remove dynamic code execution on user reachable paths; Replace with explicit, small allow listed operations; Parse to real types, validate format and size, and fail with 4xx

Overview

Code Injection occurs when untrusted data flows into an execution sink like eval, a scripting engine, a powerful expression language, or a template engine that allows code. The input is executed in the application context. This is different from Command Injection which invokes OS commands.

Common sources are query parameters, JSON bodies, headers, uploaded templates, and admin preview tools wired to production.

sequenceDiagram participant Browser participant App as App Server participant Runtime Browser->>App: GET /calc?expr=<user_string> App->>Runtime: eval(<user_string>) Runtime-->>App: executes code in process App-->>Browser: 200 OK with result
A potential flow for a Code Injection exploit

Where it occurs

It happens when developers use dynamic features to be flexible or to prototype quickly. Loose validation and broad execution capabilities let data cross the line into code.

Impact

Attackers run arbitrary code, read secrets, alter data, and cause heavy work that hurts availability. Since it runs as your app, audit trails get noisy and incident response is harder.

Prevention

Remove dynamic execution on routes that accept user input. Replace with explicit behavior through small allow listed operations. Parse inputs to real types, bound sizes, and enforce strict formats. If you must keep an expression or template engine, disable execution features and restrict to declarative use, with auto escaping and timeouts.

Examples

Switch tabs to view language/framework variants.

Express, evaluates a query string with eval

The route reads ?expr= and passes it to eval, which runs inside the server process.

Vulnerable
JavaScript • Express — Bad
const express = require('express');
const app = express();
app.get('/calc', (req, res) => {
  const expr = req.query.expr;
  res.send(String(eval(expr))); // BUG: executes attacker input
});
  • Line 5: eval executes untrusted input in process context

eval runs attacker controlled code with application privileges. That exposes secrets, data, and availability.

Secure
JavaScript • Express — Good
const express = require('express');
const app = express();
const OPS = { add:(a,b)=>a+b, sub:(a,b)=>a-b };
app.get('/calc', (req, res) => {
  const { op, a, b } = req.query;
  if (!Object.prototype.hasOwnProperty.call(OPS, op)) return res.status(400).send('unsupported');
  const A = Number(a), B = Number(b);
  if (!Number.isFinite(A) || !Number.isFinite(B)) return res.status(400).send('bad args');
  res.send(String(OPS[op](A,B)));
});
  • Line 3: Fixed, small allow listed operation set
  • Line 6: Reject unknown operations with 400
  • Line 7: Parse and validate types

Explicit behavior with typed inputs removes dynamic execution and reduces attack surface.

Engineer Checklist

  • No eval, scripting, or expression engines on request paths

  • Keep templates declarative, execution features off, auto escaping on

  • Parse to real types and enforce strict formats and bounds

  • Return 400 for unknown operations or invalid inputs

  • Add lints or CI checks that block dynamic execution APIs

End-to-End Example

A small calculator at GET /calc evaluates a query parameter using a dynamic execution feature. An attacker confirms evaluation with harmless math, then queries process data to learn environment details and degrade performance.

Vulnerable
JAVASCRIPT
// Node.js/Express - Vulnerable code injection endpoint

app.get('/calc', (req, res) => {
  try {
    // VULNERABLE: Directly evaluating user input with eval()
    // Attacker can send: ?expr=process.env.DATABASE_URL
    // or: ?expr=require('fs').readFileSync('/etc/passwd','utf8')
    // or: ?expr=require('child_process').execSync('whoami').toString()
    const expr = req.query.expr;
    const result = eval(expr);  // DANGEROUS!
    res.send(String(result));
  } catch (err) {
    // Even the error message can leak sensitive info
    res.status(500).send(err.toString());
  }
});

// ALSO VULNERABLE: Dynamic template evaluation
app.post('/preview-template', (req, res) => {
  // User can upload a template for "preview"
  const template = req.body.template;
  
  // VULNERABLE: Evaluating user-supplied template code
  // Attacker sends: <%= require('child_process').execSync('cat /etc/passwd') %>
  const ejs = require('ejs');
  const rendered = ejs.render(template, { user: req.user });
  res.send(rendered);
});

// VULNERABLE: JSON-based formula evaluation
app.post('/api/calculate-discount', (req, res) => {
  // Client sends: { formula: "price * 0.1" }
  // But attacker sends: { formula: "process.exit(1)" }
  const formula = req.body.formula;
  const price = req.body.price;
  
  // VULNERABLE: Using Function constructor with user input
  const calculate = new Function('price', `return ${formula}`);
  const discount = calculate(price);
  
  res.json({ discount });
});
Secure
JAVASCRIPT
// Node.js/Express - SECURE implementation without code injection

// Define allowed operations explicitly (whitelist approach)
const ALLOWED_OPERATIONS = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => b !== 0 ? a / b : null
};

app.get('/calc', (req, res) => {
  try {
    const { op, a, b } = req.query;
    
    // SECURE: Check operation against whitelist
    if (!Object.prototype.hasOwnProperty.call(ALLOWED_OPERATIONS, op)) {
      return res.status(400).json({ error: 'Unsupported operation' });
    }
    
    // SECURE: Parse and validate inputs as numbers
    const numA = Number(a);
    const numB = Number(b);
    
    if (!Number.isFinite(numA) || !Number.isFinite(numB)) {
      return res.status(400).json({ error: 'Invalid numeric arguments' });
    }
    
    // Execute whitelisted operation
    const result = ALLOWED_OPERATIONS[op](numA, numB);
    
    if (result === null) {
      return res.status(400).json({ error: 'Division by zero' });
    }
    
    res.json({ result });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

// SECURE: Static template with no user-controlled code
app.post('/preview-template', (req, res) => {
  // Use a predefined template, only allow data substitution
  const templateData = {
    username: String(req.body.username || ''),
    message: String(req.body.message || '')
  };
  
  // SECURE: Template is static, only data is dynamic
  const staticTemplate = '<h1>Hello <%= username %></h1><p><%= message %></p>';
  const ejs = require('ejs');
  const rendered = ejs.render(staticTemplate, templateData);
  res.send(rendered);
});

// SECURE: Fixed discount logic, no dynamic evaluation
const DISCOUNT_TIERS = {
  bronze: 0.05,
  silver: 0.10,
  gold: 0.15,
  platinum: 0.20
};

app.post('/api/calculate-discount', (req, res) => {
  const tier = String(req.body.tier || 'bronze');
  const price = Number(req.body.price);
  
  // SECURE: Use predefined discount rates
  const discountRate = DISCOUNT_TIERS[tier] || 0;
  const discount = price * discountRate;
  
  res.json({ discount, tier, rate: discountRate });
});

Discovery

This vulnerability is discovered by testing input fields with language-specific expressions or code patterns (like eval, template syntax, or scripting constructs) and observing if the application executes them, revealing dynamic code evaluation vulnerabilities.

  1. 1. Test basic arithmetic

    http

    Action

    Send a simple mathematical expression to check if the endpoint evaluates dynamic code

    Request

    GET https://app.example.com/calc?expr=1+2

    Response

    Status: 200
    Body:
    {
      "result": "3",
      "note": "Server evaluated '1+2' and returned 3, confirming code execution rather than string handling"
    }

    Artifacts

    http_response_body
  2. 2. Confirm code execution capability

    http

    Action

    Test with more complex JavaScript-specific syntax to verify eval() or similar is being used

    Request

    GET https://app.example.com/calc?expr=Math.random()

    Response

    Status: 200
    Body:
    {
      "result": "0.847293651",
      "note": "Server executed Math.random() - proves JavaScript code execution, not just math parsing"
    }

    Artifacts

    http_response_body execution_proof
  3. 3. Test runtime object access

    http

    Action

    Attempt to access Node.js process object to confirm access to runtime internals

    Request

    GET https://app.example.com/calc?expr=typeof process

    Response

    Status: 200
    Body:
    {
      "result": "object",
      "note": "Access to 'process' object confirmed - arbitrary code execution in Node.js runtime!"
    }

    Artifacts

    http_response_body runtime_access_proof
  4. 4. Identify information disclosure vectors

    http

    Action

    Test ability to extract environment variables and sensitive configuration

    Request

    GET https://app.example.com/calc?expr=Object.keys(process.env).length

    Response

    Status: 200
    Body:
    {
      "result": "47",
      "note": "Successfully enumerated 47 environment variables - can extract secrets like DATABASE_URL, API_KEY, JWT_SECRET"
    }

    Artifacts

    http_response_body env_access_proof

Exploit steps

An attacker exploits this by injecting malicious code expressions into user-controlled inputs that are dynamically evaluated, allowing them to execute arbitrary code, access sensitive data, manipulate application state, or achieve remote code execution within the application context.

  1. 1. Exfiltrate environment variables

    Extract secret configuration

    http

    Action

    Use code injection to read environment variables containing API keys, database credentials, and secrets

    Request

    GET https://app.example.com/calc?expr=process.env.DATABASE_URL

    Response

    Status: 200
    Body:
    {
      "result": "postgres://admin:Pr0dP@ssw0rd2024!@db.internal.company.com:5432/production_db",
      "note": "Database credentials exposed via code injection"
    }

    Artifacts

    http_response_body database_credentials
  2. 2. Extract additional secrets

    Enumerate all environment secrets

    http

    Action

    Retrieve API keys and third-party service credentials from environment

    Request

    GET https://app.example.com/calc?expr=JSON.stringify(process.env)

    Response

    Status: 200
    Body:
    {
      "secrets_extracted": {
        "DATABASE_URL": "postgres://admin:Pr0dP@ssw0rd2024!@db.internal:5432/prod",
        "AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE",
        "AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "STRIPE_API_KEY": "sk_live_51HxYz2eZvKYlo2CyNj8Qp3OjPa...",
        "JWT_SECRET": "super-secret-jwt-key-do-not-share-2024",
        "SENDGRID_API_KEY": "SG.xYz123...",
        "REDIS_URL": "redis://:password@redis.internal:6379",
        "total_secrets": 47
      },
      "note": "Full environment dump containing database, AWS, payment gateway, and session secrets"
    }

    Artifacts

    http_response_body secrets_dump
  3. 3. Execute denial of service

    Consume server resources

    http

    Action

    Execute resource-intensive code to degrade application performance and cause timeouts

    Request

    GET https://app.example.com/calc?expr=while(true){}

    Response

    Status: 503
    Body:
    {
      "error": "Service Unavailable",
      "impact": {
        "event_loop_blocked": true,
        "cpu_usage": "100%",
        "affected_users": "all",
        "legitimate_requests_failing": true,
        "response_time": "timeout after 30s"
      },
      "note": "Infinite loop blocks Node.js event loop - entire application unresponsive"
    }

    Artifacts

    error_log monitoring_alert
  4. 4. Demonstrate file system access

    Read application source code

    http

    Action

    Use require() to load file system module and read sensitive files

    Request

    GET https://app.example.com/calc?expr=require('fs').readFileSync('/etc/passwd','utf8')

    Response

    Status: 200
    Body:
    {
      "file_contents": "root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\npostgres:x:999:999:PostgreSQL:/var/lib/postgresql:/bin/sh\nnode:x:1000:1000:Node.js user:/home/node:/bin/bash",
      "next_steps": "Read application.properties, .env, id_rsa, database files",
      "note": "File system read access confirmed - can exfiltrate source code, configs, keys"
    }

    Artifacts

    http_response_body file_contents

Specific Impact

Secrets and internal details leak and can be used for lateral movement or targeted exploitation. Under load, abused evaluation paths hurt availability, leading to errors and timeouts for real users.

Fix

There is no dynamic execution. Inputs are parsed to real types and routed through a fixed behavior set. Unknown operations or bad types return clear 4xx responses.

Detect This Vulnerability in Your Code

Sourcery automatically identifies code injection vulnerabilities and many other security issues in your codebase.

Scan Your Code for Free