mirror of
https://github.com/github/codeql.git
synced 2026-04-29 18:55:14 +02:00
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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}`) })
|
||||
@@ -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}`) })
|
||||
Reference in New Issue
Block a user