Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions python/ql/src/experimental/CWE-074/JinjaBad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.urls import path
from django.http import HttpResponse
from jinja2 import Template as Jinja2_Template
from jinja2 import Environment, DictLoader, escape


def a(request):
# Load the template
template = request.GET['template']
t = Jinja2_Template(template)
name = request.GET['name']
# Render the template with the context data
html = t.render(name=escape(name))
return HttpResponse(html)


urlpatterns = [
path('a', a),
]
20 changes: 20 additions & 0 deletions python/ql/src/experimental/CWE-074/JinjaGood.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.urls import path
from django.http import HttpResponse
from jinja2 import Template as Jinja2_Template
from jinja2 import Environment, DictLoader, escape


def a(request):
# Load the template
template = request.GET['template']
env = SandboxedEnvironment(undefined=StrictUndefined)
t = env.from_string(template)
name = request.GET['name']
# Render the template with the context data
html = t.render(name=escape(name))
return HttpResponse(html)


urlpatterns = [
path('a', a),
]
24 changes: 24 additions & 0 deletions python/ql/src/experimental/CWE-074/TemplateInjection.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>
Template Injection occurs when user input is embedded in a template in an unsafe manner.
When an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side is results in Server Side Template Injection.
</p>
</overview>
<recommendation>
<p>
To fix this, ensure that an untrusted value is not used as a template. If the application requirements do not alow this, use a sandboxed environment where access to unsafe attributes and methods is prohibited.
</p>
</recommendation>
<example>
<p>Consider the example given below, an untrusted HTTP parameter `template` is used to generate a Jinja2 template string. This can lead to remote code execution. </p>
<sample src="JinjaBad.py" />

<p>Here we have fixed the problem by using the Jinja sandbox environment for evaluating untrusted code.</p>
<sample src="JinjaGood.py" />
</example>
<references>
<li>Portswigger : [Server Side Template Injection](https://portswigger.net/web-security/server-side-template-injection)</li>
</references>
</qhelp>
34 changes: 34 additions & 0 deletions python/ql/src/experimental/CWE-074/TemplateInjection.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @name Server Side Template Injection
* @description Using user-controlled data to create a template can cause security issues.
* @kind path-problem
* @problem.severity error
* @precision high
* @id py/template-injection
* @tags security
* external/cwe/cwe-074
*/

import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import experimental.semmle.python.templates.Ssti
/* Flow */
import semmle.python.security.strings.Untrusted

class TemplateInjectionConfiguration extends TaintTracking::Configuration {
TemplateInjectionConfiguration() { this = "Template injection configuration" }

override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}

override predicate isSink(TaintTracking::Sink sink) { sink instanceof SSTISink }
}

from TemplateInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This Template depends on $@.", src.getSource(),
"a user-provided value"
27 changes: 27 additions & 0 deletions python/ql/src/experimental/semmle/python/templates/Airspeed.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/** Provides classes which model the `airspeed` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

/** returns the ClassValue representing `airspeed.Template` */
ClassValue theAirspeedTemplateClass() { result = Value::named("airspeed.Template") }

/**
* Sink representing the `airspeed.Template` class instantiation argument.
*
* import airspeed
* temp = airspeed.Template(`"sink"`)
*/
class AirspeedTemplateSink extends SSTISink {
override string toString() { result = "argument to airspeed.Template()" }

AirspeedTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theAirspeedTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
46 changes: 46 additions & 0 deletions python/ql/src/experimental/semmle/python/templates/Bottle.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/** Provides classes which model the `bottle` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

/** returns the ClassValue representing `bottle.SimpleTemplate` */
ClassValue theBottleSimpleTemplateClass() { result = Value::named("bottle.SimpleTemplate") }

/**
* Sink representing the `bottle.SimpleTemplate` class instantiation argument.
*
* from bottle import SimpleTemplate
* template = SimpleTemplate(`sink`)
*/
class BottleSimpleTemplateSink extends SSTISink {
override string toString() { result = "argument to bottle.SimpleTemplate()" }

BottleSimpleTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theBottleSimpleTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}

/**
* Sink representing the `bottle.template` function call argument.
*
* from bottle import template
* tmp = template(`sink`)
*/
class BottleTemplateSink extends SSTISink {
override string toString() { result = "argument to bottle.template()" }

BottleTemplateSink() {
exists(CallNode call |
call.getFunction() = theBottleModule().attr("template").getAReference() and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
27 changes: 27 additions & 0 deletions python/ql/src/experimental/semmle/python/templates/Chameleon.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/** Provides classes which model the `Chameleon` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

/** returns the ClassValue representing `chameleon.PageTemplate` */
ClassValue theChameleonPageTemplateClass() { result = Value::named("chameleon.PageTemplate") }

/**
* Sink representing the `chameleon.PageTemplate` class instantiation argument.
*
* from chameleon import PageTemplate
* template = PageTemplate(`sink`)
*/
class ChameleonTemplateSink extends SSTISink {
override string toString() { result = "argument to Chameleon.PageTemplate()" }

ChameleonTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theChameleonPageTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
37 changes: 37 additions & 0 deletions python/ql/src/experimental/semmle/python/templates/Cheetah.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** Provides classes which model the `Cheetah3` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

/** returns the ClassValue representing `Cheetah.Template.Template` */
ClassValue theCheetahTemplateClass() { result = Value::named("Cheetah.Template.Template") }

/**
* Sink representing the instantiation argument of any class which derives from
* the `Cheetah.Template.Template` class .
*
* from Cheetah.Template import Template
* class Template3(Template):
* title = 'Hello World Example!'
* contents = 'Hello World!'
* t3 = Template3("sink")
*
* This will also detect cases of the following type :
*
* from Cheetah.Template import Template
* t3 = Template("sink")
*/
class CheetahTemplateInstantiationSink extends SSTISink {
override string toString() { result = "argument to Cheetah.Template.Template()" }

CheetahTemplateInstantiationSink() {
exists(CallNode call, ClassValue cv |
cv.getASuperType() = theCheetahTemplateClass() and
call.getFunction().pointsTo(cv) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
36 changes: 36 additions & 0 deletions python/ql/src/experimental/semmle/python/templates/Chevron.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** Provides classes which model the `chevron` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

/** returns the Value representing `chevron.render` function */
Value theChevronRenderFunc() { result = Value::named("chevron.render") }

/**
* Sink representing the `chevron.render` function call argument.
*
* import chevron
* tmp = chevron.render(`sink`,{ 'key' : 'value' })
*/
class ChevronRenderSink extends SSTISink {
override string toString() { result = "argument to chevron.render()" }

ChevronRenderSink() {
exists(CallNode call |
call.getFunction() = theChevronRenderFunc().getAReference() and
call.getArg(0) = this
)
// TODO: this should also detect :
// import chevron
// args = {
// 'template': 'sink',
// 'data': {
// 'mustache': 'World'
// }
// }
// chevron.render(**args)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/** Provides classes which model the `DjangoTemplate` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

ClassValue theDjangoTemplateClass() { result = Value::named("django.template.Template") }

/**
* Sink representng `django.template.Template` class instantiation argument.
*
* from django.template import Template
* template = Template(`sink`)
*/
class DjangoTemplateTemplateSink extends SSTISink {
override string toString() { result = "argument to Django.template()" }

DjangoTemplateTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theDjangoTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
// TODO (intentionally commented out QLDoc, since qlformat will delete those lines otherwise)
// /**
// * Sinks representng the django.template.Template class instantiation.
// *
// * from django.template import engines
// *
// * django_engine = engines["django"]
// * template = django_engine.from_string(`sink`)
// */
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** Provides classes which model templates in the`flask` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

Value theFlaskRenderTemplateClass() { result = Value::named("flask.render_template_string") }

/**
* Sink representng `flask.render_template_string` function call argument.
*
* from flask import render_template_string
* render_template_string(`sink`)
*/
class FlaskTemplateSink extends SSTISink {
override string toString() { result = "argument to flask.render_template_string()" }

FlaskTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theFlaskRenderTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
51 changes: 51 additions & 0 deletions python/ql/src/experimental/semmle/python/templates/Genshi.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** Provides classes which model the `Genshi` package. */

import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink

/** returns the ClassValue representing `Genshi.template.TextTemplate` */
ClassValue theGenshiTextTemplateClass() { result = Value::named("genshi.template.TextTemplate") }

/** returns the ClassValue representing `Genshi.template.MarkupTemplate` */
ClassValue theGenshiMarkupTemplateClass() {
result = Value::named("genshi.template.MarkupTemplate")
}

/**
* Sink representing the `genshi.template.TextTemplate` class instantiation argument.
*
* from genshi.template import TextTemplate
* tmpl = TextTemplate('sink')
*/
class GenshiTextTemplateSink extends SSTISink {
override string toString() { result = "argument to genshi.template.TextTemplate()" }

GenshiTextTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theGenshiTextTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}

/**
* Sink representing the `genshi.template.MarkupTemplate` class instantiation argument.
*
* from genshi.template import MarkupTemplate
* tmpl = MarkupTemplate('sink')
*/
class GenshiMarkupTemplateSink extends SSTISink {
override string toString() { result = "argument to genshi.template.MarkupTemplate()" }

GenshiMarkupTemplateSink() {
exists(CallNode call |
call.getFunction().pointsTo(theGenshiMarkupTemplateClass()) and
call.getArg(0) = this
)
}

override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
Loading