Merge pull request #3394 from monkey-junkie/master

JS SSTI CWE-094
This commit is contained in:
Asger F
2020-05-11 15:06:17 +01:00
committed by GitHub
4 changed files with 189 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Server-Side Template Injection vulnerabilities occur when user input is embedded
in a template in an unsafe manner allowing attackers to access the template context and
run arbitrary code on the application server.
</p>
</overview>
<recommendation>
<p>
Avoid including user input in any expression or template which may be dynamically rendered.
If user input must be included, use context-specific escaping before including it or run
the rendering engine with sandbox options.
</p>
</recommendation>
<example>
<p>
The following example shows a page being rendered with user input allowing attackers to access the
template context and run arbitrary code on the application server.
The Pug template engine (and other template engines) provides an interpolation feature - insertion of variable values into a string of some kind.
For example, <code>Hello #{user.username}!</code>, could be used for printing a username from a scoped variable user,
but the <code>user.username</code> expression will be executed as JavaScript.
Unsafe injection of user input in a template therefore allows an attacker to inject arbitrary JavaScript code.
For example, a payload of <code>#{global.process.exit(1)}</code> will cause the below server to crash.
</p>
<sample src="examples/ServerSideTemplateInjection.js" />
</example>
<example>
<p>
The example below provides an example of how to use a template engine without any risk of Server-Side Template Injection.
Instead of concatenating user input onto the template, the template uses a placeholder and safely inserts
the user input.
</p>
<sample src="examples/ServerSideTemplateInjectionSafe.js" />
</example>
<references>
<li>
OWASP:
<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>.
</li>
<li>
PortSwigger Research Blog:
<a href="https://portswigger.net/research/server-side-template-injection">Server-Side Template Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,64 @@
/**
* @name Server Side Template Injection
* @description Rendering templates with unsanitized user input allows a malicious user arbitrary
* code execution.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/server-side-template-injection
* @tags security
* external/cwe/cwe-094
*/
import javascript
import DataFlow
import DataFlow::PathGraph
class ServerSideTemplateInjectionConfiguration extends TaintTracking::Configuration {
ServerSideTemplateInjectionConfiguration() { this = "ServerSideTemplateInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof ServerSideTemplateInjectionSink }
}
abstract class ServerSideTemplateInjectionSink extends DataFlow::Node { }
class SSTIPugSink extends ServerSideTemplateInjectionSink {
SSTIPugSink() {
exists(CallNode compile, ModuleImportNode renderImport |
renderImport = moduleImport(["pug", "jade"]) and
(
compile = renderImport.getAMemberCall("compile")
or
compile = renderImport.getAMemberCall("render")
) and
this = compile.getArgument(0)
)
}
}
class SSTIDotSink extends ServerSideTemplateInjectionSink {
SSTIDotSink() {
exists(CallNode compile |
compile = moduleImport("dot").getAMemberCall("template") and
this = compile.getArgument(0)
)
}
}
class SSTIEjsSink extends ServerSideTemplateInjectionSink {
SSTIEjsSink() { this = moduleImport("ejs").getAMemberCall("render").getArgument(0) }
}
class SSTINunjucksSink extends ServerSideTemplateInjectionSink {
SSTINunjucksSink() {
this = moduleImport("nunjucks").getAMemberCall("renderString").getArgument(0)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, ServerSideTemplateInjectionConfiguration c
where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"$@ flows to here and unsafely used as part of rendered template", source.getNode(),
"User-provided value"

View File

@@ -0,0 +1,35 @@
const express = require('express')
var bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
//Dependent of Templating engine
var jade = require('pug');
const port = 5061
function getHTML(input) {
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
label(for='name') Name:
input#name.form-control(type='text', placeholder='' name='name')
button.btn.btn-primary(type='submit') Submit
p Hello `+ input
var fn = jade.compile(template);
var html = fn();
console.log(html);
return html;
}
app.post('/', (request, response) => {
var input = request.param('name', "")
var html = getHTML(input)
response.send(html);
})
app.listen(port, () => { console.log(`server is listening on ${port}`) })

View File

@@ -0,0 +1,34 @@
const express = require('express')
var bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.urlencoded({ extended: true }));
//Dependent of Templating engine
var jade = require('pug');
const port = 5061
function getHTML(input) {
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
label(for='name') Name:
input#name.form-control(type='text', placeholder='' name='name')
button.btn.btn-primary(type='submit') Submit
p Hello #{username}`
var fn = jade.compile(template);
var html = fn({username: input});
console.log(html);
return html;
}
app.post('/', (request, response) => {
var input = request.param('name', "")
var html = getHTML(input)
response.send(html);
})
app.listen(port, () => { console.log(`server is listening on ${port}`) })