VNX-1336: Server-side template injection: template source built from request data
Reports Python and Node.js code that compiles a template whose source comes from request data (render_template_string, Jinja2 from_string/Template, Django and Mako Template, Handlebars.compile, ejs/pug render and compile, nunjucks renderString, lodash _.template), directly or through one or two assignments. Literal or file templates with request data passed as context are not reported.
| Field | Value |
|---|---|
| Rule ID | VNX-1336 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | JavaScript / TypeScript, Python |
| CWE | CWE-1336 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | template-injection ssti rce cwe-1336 |
| Source | internal/sast/rules/vnx-1336.rego |
Overview
VNX-1336 maps to CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine. When request data becomes part of the template source, and not just the values the template renders, an attacker can write template expressions ({{ config }}, {{ ''.__class__.__mro__ }}, #{7*7}) that the engine evaluates on the server.
Why This Matters
Server-side template injection in Jinja2, Mako, Pug, EJS, Handlebars and lodash templates regularly escalates to remote code execution, because those engines give templates access to the host language’s objects.
What Gets Flagged
name = request.args.get("name", "guest")
template = f"<h1>Hello {name}</h1>"
return render_template_string(template) # FLAGGED
tpl = env.from_string(request.get_json()["body"]) # FLAGGED
t = Template(request.GET["q"]) # FLAGGED (django.template)
const src = req.body.template
const fn = Handlebars.compile(src) // FLAGGED
res.send(pug.render(`p Hello ${req.query.name}`)) // FLAGGED
res.send(nunjucks.renderString('Hello ' + req.query.name, {})) // FLAGGED
What Is Not Flagged
- A literal template with request data passed only as render context:
render_template_string("<h1>{{ name }}</h1>", name=request.args["name"]),ejs.render('<%= name %>', { name: req.query.name }). - Templates loaded from files:
render_template,res.render,env.get_template, a template read from disk. - Python’s
string.Template, which substitutes values and evaluates no expressions. - Comments and docstrings.
- Templates built from stored data (a value read from the database) are not followed.
Remediation
- Keep template source constant or load it from files under your control.
- Pass user input only as context variables, which the engine escapes.
- If users must author templates, use a sandboxed engine (
jinja2.sandbox.SandboxedEnvironment) or a logic-less format, and never expose host objects to it.
References
- CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine
- OWASP: Testing for Server-side Template Injection
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-1336
// nosec vnx-1336
Or record an ignore rule: vulnetix ignore add --rule vnx-1336 --reason "...". See Suppressing findings.