Skip to content

Commit 86a774d

Browse files
authored
Merge pull request github#3394 from monkey-junkie/master
JS SSTI CWE-094
2 parents cec73e6 + 4594aa4 commit 86a774d

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Server-Side Template Injection vulnerabilities occur when user input is embedded
9+
in a template in an unsafe manner allowing attackers to access the template context and
10+
run arbitrary code on the application server.
11+
</p>
12+
</overview>
13+
14+
<recommendation>
15+
<p>
16+
Avoid including user input in any expression or template which may be dynamically rendered.
17+
If user input must be included, use context-specific escaping before including it or run
18+
the rendering engine with sandbox options.
19+
</p>
20+
</recommendation>
21+
22+
<example>
23+
<p>
24+
The following example shows a page being rendered with user input allowing attackers to access the
25+
template context and run arbitrary code on the application server.
26+
The Pug template engine (and other template engines) provides an interpolation feature - insertion of variable values into a string of some kind.
27+
For example, <code>Hello #{user.username}!</code>, could be used for printing a username from a scoped variable user,
28+
but the <code>user.username</code> expression will be executed as JavaScript.
29+
Unsafe injection of user input in a template therefore allows an attacker to inject arbitrary JavaScript code.
30+
For example, a payload of <code>#{global.process.exit(1)}</code> will cause the below server to crash.
31+
</p>
32+
33+
<sample src="examples/ServerSideTemplateInjection.js" />
34+
</example>
35+
36+
<example>
37+
<p>
38+
The example below provides an example of how to use a template engine without any risk of Server-Side Template Injection.
39+
Instead of concatenating user input onto the template, the template uses a placeholder and safely inserts
40+
the user input.
41+
</p>
42+
43+
<sample src="examples/ServerSideTemplateInjectionSafe.js" />
44+
</example>
45+
46+
<references>
47+
<li>
48+
OWASP:
49+
<a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server_Side_Template_Injection">Server Side Template Injection</a>.
50+
</li>
51+
<li>
52+
PortSwigger Research Blog:
53+
<a href="https://portswigger.net/research/server-side-template-injection">Server-Side Template Injection</a>.
54+
</li>
55+
</references>
56+
</qhelp>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @name Server Side Template Injection
3+
* @description Rendering templates with unsanitized user input allows a malicious user arbitrary
4+
* code execution.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id js/server-side-template-injection
9+
* @tags security
10+
* external/cwe/cwe-094
11+
*/
12+
13+
import javascript
14+
import DataFlow
15+
import DataFlow::PathGraph
16+
17+
class ServerSideTemplateInjectionConfiguration extends TaintTracking::Configuration {
18+
ServerSideTemplateInjectionConfiguration() { this = "ServerSideTemplateInjectionConfiguration" }
19+
20+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
21+
22+
override predicate isSink(DataFlow::Node sink) { sink instanceof ServerSideTemplateInjectionSink }
23+
}
24+
25+
abstract class ServerSideTemplateInjectionSink extends DataFlow::Node { }
26+
27+
class SSTIPugSink extends ServerSideTemplateInjectionSink {
28+
SSTIPugSink() {
29+
exists(CallNode compile, ModuleImportNode renderImport |
30+
renderImport = moduleImport(["pug", "jade"]) and
31+
(
32+
compile = renderImport.getAMemberCall("compile")
33+
or
34+
compile = renderImport.getAMemberCall("render")
35+
) and
36+
this = compile.getArgument(0)
37+
)
38+
}
39+
}
40+
41+
class SSTIDotSink extends ServerSideTemplateInjectionSink {
42+
SSTIDotSink() {
43+
exists(CallNode compile |
44+
compile = moduleImport("dot").getAMemberCall("template") and
45+
this = compile.getArgument(0)
46+
)
47+
}
48+
}
49+
50+
class SSTIEjsSink extends ServerSideTemplateInjectionSink {
51+
SSTIEjsSink() { this = moduleImport("ejs").getAMemberCall("render").getArgument(0) }
52+
}
53+
54+
class SSTINunjucksSink extends ServerSideTemplateInjectionSink {
55+
SSTINunjucksSink() {
56+
this = moduleImport("nunjucks").getAMemberCall("renderString").getArgument(0)
57+
}
58+
}
59+
60+
from DataFlow::PathNode source, DataFlow::PathNode sink, ServerSideTemplateInjectionConfiguration c
61+
where c.hasFlowPath(source, sink)
62+
select sink.getNode(), source, sink,
63+
"$@ flows to here and unsafely used as part of rendered template", source.getNode(),
64+
"User-provided value"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const express = require('express')
2+
var bodyParser = require('body-parser');
3+
const app = express()
4+
app.use(bodyParser.json());
5+
app.use(bodyParser.urlencoded({ extended: true }));
6+
7+
//Dependent of Templating engine
8+
var jade = require('pug');
9+
const port = 5061
10+
11+
function getHTML(input) {
12+
var template = `
13+
doctype
14+
html
15+
head
16+
title= 'Hello world'
17+
body
18+
form(action='/' method='post')
19+
label(for='name') Name:
20+
input#name.form-control(type='text', placeholder='' name='name')
21+
button.btn.btn-primary(type='submit') Submit
22+
p Hello `+ input
23+
var fn = jade.compile(template);
24+
var html = fn();
25+
console.log(html);
26+
return html;
27+
}
28+
29+
app.post('/', (request, response) => {
30+
var input = request.param('name', "")
31+
var html = getHTML(input)
32+
response.send(html);
33+
})
34+
35+
app.listen(port, () => { console.log(`server is listening on ${port}`) })
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const express = require('express')
2+
var bodyParser = require('body-parser');
3+
const app = express()
4+
app.use(bodyParser.urlencoded({ extended: true }));
5+
6+
//Dependent of Templating engine
7+
var jade = require('pug');
8+
const port = 5061
9+
10+
function getHTML(input) {
11+
var template = `
12+
doctype
13+
html
14+
head
15+
title= 'Hello world'
16+
body
17+
form(action='/' method='post')
18+
label(for='name') Name:
19+
input#name.form-control(type='text', placeholder='' name='name')
20+
button.btn.btn-primary(type='submit') Submit
21+
p Hello #{username}`
22+
var fn = jade.compile(template);
23+
var html = fn({username: input});
24+
console.log(html);
25+
return html;
26+
}
27+
28+
app.post('/', (request, response) => {
29+
var input = request.param('name', "")
30+
var html = getHTML(input)
31+
response.send(html);
32+
})
33+
34+
app.listen(port, () => { console.log(`server is listening on ${port}`) })

0 commit comments

Comments
 (0)