Skip to content

Commit 8129d0c

Browse files
authored
Merge pull request #4762 from asgerf/js/template-sinks-in-code-injection
Approved by erik-krogh, mchammer01
2 parents 354adf3 + f0516dd commit 8129d0c

File tree

12 files changed

+225
-196
lines changed

12 files changed

+225
-196
lines changed

javascript/ql/src/Security/CWE-094/CodeInjection.qhelp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ for example, steal cookies containing session information.
3030
</p>
3131

3232
<sample src="examples/CodeInjection.js" />
33+
34+
<p>
35+
The following example shows a Pug template being constructed from user input, allowing attackers to run
36+
arbitrary code via a payload such as <code>#{global.process.exit(1)}</code>.
37+
</p>
38+
39+
<sample src="examples/ServerSideTemplateInjection.js" />
40+
41+
<p>
42+
Below is an example of how to use a template engine without any risk of template injection.
43+
The user input is included via an interpolation expression <code>#{username}</code> whose value is provided
44+
as an option to the template, instead of being part of the template string itself:
45+
</p>
46+
47+
<sample src="examples/ServerSideTemplateInjectionSafe.js" />
3348
</example>
3449

3550
<references>
@@ -40,5 +55,9 @@ OWASP:
4055
<li>
4156
Wikipedia: <a href="https://en.wikipedia.org/wiki/Code_injection">Code Injection</a>.
4257
</li>
58+
<li>
59+
PortSwigger Research Blog:
60+
<a href="https://portswigger.net/research/server-side-template-injection">Server-Side Template Injection</a>.
61+
</li>
4362
</references>
4463
</qhelp>

javascript/ql/src/Security/CWE-094/CodeInjection.ql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ import DataFlow::PathGraph
1818

1919
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
2020
where cfg.hasFlowPath(source, sink)
21-
select sink.getNode(), source, sink, "$@ flows to here and is interpreted as code.",
22-
source.getNode(), "User-provided value"
21+
select sink.getNode(), source, sink,
22+
"$@ flows to " + sink.getNode().(Sink).getMessageSuffix() + ".", source.getNode(),
23+
"User-provided value"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const express = require('express')
2+
var pug = require('pug');
3+
const app = express()
4+
5+
app.post('/', (req, res) => {
6+
var input = req.query.username;
7+
var template = `
8+
doctype
9+
html
10+
head
11+
title= 'Hello world'
12+
body
13+
form(action='/' method='post')
14+
input#name.form-control(type='text)
15+
button.btn.btn-primary(type='submit') Submit
16+
p Hello `+ input
17+
var fn = pug.compile(template);
18+
var html = fn();
19+
res.send(html);
20+
})
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const express = require('express')
2+
var pug = require('pug');
3+
const app = express()
4+
5+
app.post('/', (req, res) => {
6+
var input = req.query.username;
7+
var template = `
8+
doctype
9+
html
10+
head
11+
title= 'Hello world'
12+
body
13+
form(action='/' method='post')
14+
input#name.form-control(type='text)
15+
button.btn.btn-primary(type='submit') Submit
16+
p Hello #{username}`
17+
var fn = pug.compile(template);
18+
var html = fn({username: input});
19+
res.send(html);
20+
})

javascript/ql/src/experimental/Security/CWE-94/ServerSideTemplateInjection.qhelp

Lines changed: 0 additions & 56 deletions
This file was deleted.

javascript/ql/src/experimental/Security/CWE-94/ServerSideTemplateInjection.ql

Lines changed: 0 additions & 68 deletions
This file was deleted.

javascript/ql/src/experimental/Security/CWE-94/examples/ServerSideTemplateInjection.js

Lines changed: 0 additions & 35 deletions
This file was deleted.

javascript/ql/src/experimental/Security/CWE-94/examples/ServerSideTemplateInjectionSafe.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

javascript/ql/src/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ module CodeInjection {
1515
/**
1616
* A data flow sink for code injection vulnerabilities.
1717
*/
18-
abstract class Sink extends DataFlow::Node { }
18+
abstract class Sink extends DataFlow::Node {
19+
/**
20+
* Gets the substitute for `X` in the message `User-provided value flows to X`.
21+
*/
22+
string getMessageSuffix() { result = "here and is interpreted as code" }
23+
}
1924

2025
/**
2126
* A sanitizer for code injection vulnerabilities.
@@ -138,4 +143,57 @@ module CodeInjection {
138143
API::moduleImport("module").getInstance().getMember("_compile").getACall().getArgument(0)
139144
}
140145
}
146+
147+
/** A sink for code injection via template injection. */
148+
abstract private class TemplateSink extends Sink {
149+
override string getMessageSuffix() {
150+
result = "here and is interpreted as a template, which may contain code"
151+
}
152+
}
153+
154+
/**
155+
* A value interpreted as as template by the `pug` library.
156+
*/
157+
class PugTemplateSink extends TemplateSink {
158+
PugTemplateSink() {
159+
this =
160+
DataFlow::moduleImport(["pug", "jade"]).getAMemberCall(["compile", "render"]).getArgument(0)
161+
}
162+
}
163+
164+
/**
165+
* A value interpreted as a tempalte by the `dot` library.
166+
*/
167+
class DotTemplateSink extends TemplateSink {
168+
DotTemplateSink() {
169+
this = DataFlow::moduleImport("dot").getAMemberCall("template").getArgument(0)
170+
}
171+
}
172+
173+
/**
174+
* A value interpreted as a template by the `ejs` library.
175+
*/
176+
class EjsTemplateSink extends TemplateSink {
177+
EjsTemplateSink() {
178+
this = DataFlow::moduleImport("ejs").getAMemberCall("render").getArgument(0)
179+
}
180+
}
181+
182+
/**
183+
* A value interpreted as a template by the `nunjucks` library.
184+
*/
185+
class NunjucksTemplateSink extends TemplateSink {
186+
NunjucksTemplateSink() {
187+
this = DataFlow::moduleImport("nunjucks").getAMemberCall("renderString").getArgument(0)
188+
}
189+
}
190+
191+
/**
192+
* A value interpreted as a template by `lodash` or `underscore`.
193+
*/
194+
class LodashUnderscoreTemplateSink extends TemplateSink {
195+
LodashUnderscoreTemplateSink() {
196+
this = LodashUnderscore::member("template").getACall().getArgument(0)
197+
}
198+
}
141199
}

0 commit comments

Comments
 (0)