Skip to content

Commit 26055dd

Browse files
author
Porcupiney Hairs
committed
Python : Add query to detect Server Side Template Injection
1 parent 499e349 commit 26055dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+786
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// /**
2+
// * @name Server Side Template Injection
3+
// * @description Using user-controlled data to create a template can cause security issues.
4+
// * @kind path-problem
5+
// * @problem.severity error
6+
// * @precision high
7+
// * @id py/template-injection
8+
// * @tags security
9+
// * external/cwe/cwe-074
10+
// */
11+
12+
import python
13+
import semmle.python.security.Paths
14+
/* Sources */
15+
import semmle.python.web.HttpRequest
16+
/* Sinks */
17+
import experimental.semmle.python.templates.Ssti
18+
19+
class TemplateInjectionConfiguration extends TaintTracking::Configuration {
20+
TemplateInjectionConfiguration() { this = "Template injection configuration" }
21+
22+
override predicate isSource(TaintTracking::Source source) {
23+
source instanceof HttpRequestTaintSource
24+
}
25+
26+
override predicate isSink(TaintTracking::Sink sink) {
27+
sink instanceof SSTISink
28+
}
29+
}
30+
31+
from TemplateInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
32+
where config.hasFlowPath(src, sink)
33+
select sink.getSink(), src, sink, "This Template depends on $@.", src.getSource(),
34+
"a user-provided value"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Provides classes which model the `bottle` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `airspeed.Template` */
8+
ClassValue theAirspeedTemplateClass() { result = Value::named("airspeed.Template") }
9+
10+
/**
11+
* Sink representing the `airspeed.Template` class instantiation argument.
12+
*
13+
* from chameleon import PageTemplate
14+
* template = PageTemplate(`sink`)
15+
*/
16+
class AirspeedTemplateSink extends SSTISink {
17+
override string toString() { result = "argument to airspeed.Template()" }
18+
19+
AirspeedTemplateSink() {
20+
exists(CallNode call |
21+
call.getFunction().pointsTo(theAirspeedTemplateClass()) and
22+
call.getArg(0) = this
23+
)
24+
}
25+
26+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/** Provides classes which model the `bottle` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `bottle.SimpleTemplate` */
8+
ClassValue theBottleSimpleTemplateClass() { result = Value::named("bottle.SimpleTemplate") }
9+
10+
/**
11+
* Sink representing the `bottle.SimpleTemplate` class instantiation argument.
12+
*
13+
* from bottle import SimpleTemplate
14+
* template = SimpleTemplate(`sink`)
15+
*/
16+
class BottleSimpleTemplateSink extends SSTISink {
17+
override string toString() { result = "argument to bottle.SimpleTemplate()" }
18+
19+
BottleSimpleTemplateSink() {
20+
exists(CallNode call |
21+
call.getFunction().pointsTo(theBottleSimpleTemplateClass()) and
22+
call.getArg(0) = this
23+
)
24+
}
25+
26+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
27+
}
28+
29+
/**
30+
* Sink representing the `bottle.template` function call argument.
31+
*
32+
* from bottle import template
33+
* tmp = template(`sink`)
34+
*/
35+
class BottleTemplateSink extends SSTISink {
36+
override string toString() { result = "argument to bottle.template()" }
37+
38+
BottleTemplateSink() {
39+
exists(CallNode call |
40+
call.getFunction() = theBottleModule().attr("template").getAReference() and
41+
call.getArg(0) = this
42+
)
43+
}
44+
45+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Provides classes which model the `Chameleon` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `chameleon.PageTemplate` */
8+
ClassValue theChameleonPageTemplateClass() { result = Value::named("chameleon.PageTemplate") }
9+
10+
/**
11+
* Sink representing the `chameleon.PageTemplate` class instantiation argument.
12+
*
13+
* from chameleon import PageTemplate
14+
* template = PageTemplate(`sink`)
15+
*/
16+
class ChameleonTemplateSink extends SSTISink {
17+
override string toString() { result = "argument to Chameleon.PageTemplate()" }
18+
19+
ChameleonTemplateSink() {
20+
exists(CallNode call |
21+
call.getFunction().pointsTo(theChameleonPageTemplateClass()) and
22+
call.getArg(0) = this
23+
)
24+
}
25+
26+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/** Provides classes which model the `chevron` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the Value representing `chevron.render` function */
8+
Value theChevronRenderFunc() { result = Value::named("chevron.render") }
9+
10+
/**
11+
* Sink representing the `chevron.render` function call argument.
12+
*
13+
* import chevron
14+
* tmp = chevron.render(`sink`,{ 'key' : 'value' })
15+
*/
16+
class ChevronRenderSink extends SSTISink {
17+
override string toString() { result = "argument to chevron.render()" }
18+
19+
ChevronRenderSink() {
20+
exists(CallNode call |
21+
call.getFunction() = theChevronRenderFunc().getAReference() and
22+
call.getArg(0) = this
23+
)
24+
or
25+
// HELP: this should detect :-
26+
// import chevron
27+
// args = {
28+
// 'template': 'sink',
29+
// 'data': {
30+
// 'mustache': 'World'
31+
// }
32+
// }
33+
// chevron.render(**args)
34+
exists(Dict dict, Call call, KeyValuePair kv |
35+
call.getFunc().getAFlowNode() = theChevronRenderFunc().getAReference() and
36+
dict.getAnItem().contains(kv) and
37+
dict = call.getKwargs() and
38+
kv.getKey().toString() = "template" and
39+
kv.getValue().getAFlowNode() = this
40+
)
41+
}
42+
43+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/** Provides classes which model the `DjangoTemplate` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
ClassValue theDjangoTemplateClass() { result = Value::named("django.template.Template") }
8+
9+
/**
10+
* Sink representng `django.template.Template` class instantiation argument.
11+
*
12+
* from django.template import Template
13+
* template = Template(`sink`)
14+
*/
15+
class DjangoTemplateTemplateSink extends SSTISink {
16+
override string toString() { result = "argument to Django.template()" }
17+
18+
DjangoTemplateTemplateSink() {
19+
exists(CallNode call |
20+
call.getFunction().pointsTo(theDjangoTemplateClass()) and
21+
call.getArg(0) = this
22+
)
23+
}
24+
25+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
26+
}
27+
28+
/**
29+
*Sinks representng the django.template.Template class instantiation.
30+
*
31+
* from django.template import engines
32+
*
33+
* django_engine = engines["django"]
34+
* template = django_engine.from_string(`sink`)
35+
*/
36+
class DjangoTemplateEngineSink extends SSTISink {
37+
override string toString() { result = "argument to django.template.Engine.from_string()" }
38+
39+
// HELP : This does resolve `from_string`
40+
DjangoTemplateEngineSink() {
41+
exists(CallNode call, ClassValue c |
42+
c = Value::named("django.template.Engine") and
43+
call.getFunction().pointsTo(c.attr("from_string")) and
44+
call.getArg(0) = this
45+
)
46+
}
47+
48+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/** Provides classes which model the `Genshi` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `Genshi.template.TextTemplate` */
8+
ClassValue theGenshiTextTemplateClass() { result = Value::named("genshi.template.TextTemplate") }
9+
10+
/** returns the ClassValue representing `Genshi.template.MarkupTemplate` */
11+
ClassValue theGenshiMarkupTemplateClass() {
12+
result = Value::named("genshi.template.MarkupTemplate")
13+
}
14+
15+
/**
16+
* Sink representing the `genshi.template.TextTemplate` class instantiation argument.
17+
*
18+
* from genshi.template import TextTemplate
19+
* tmpl = TextTemplate('sink')
20+
*/
21+
class GenshiTextTemplateSink extends SSTISink {
22+
override string toString() { result = "argument to genshi.template.TextTemplate()" }
23+
24+
GenshiTextTemplateSink() {
25+
exists(CallNode call |
26+
call.getFunction().pointsTo(theGenshiTextTemplateClass()) and
27+
call.getArg(0) = this
28+
)
29+
}
30+
31+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
32+
}
33+
34+
/**
35+
* Sink representing the `genshi.template.MarkupTemplate` class instantiation argument.
36+
*
37+
* from genshi.template import MarkupTemplate
38+
* tmpl = MarkupTemplate('sink')
39+
*/
40+
class GenshiMarkupTemplateSink extends SSTISink {
41+
override string toString() { result = "argument to genshi.template.MarkupTemplate()" }
42+
43+
GenshiMarkupTemplateSink() {
44+
exists(CallNode call |
45+
call.getFunction().pointsTo(theGenshiMarkupTemplateClass()) and
46+
call.getArg(0) = this
47+
)
48+
}
49+
50+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Provides classes which model the `Jinja2` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `jinja2.Template` */
8+
ClassValue theJinja2TemplateClass() { result = Value::named("jinja2.Template") }
9+
10+
/**
11+
* Sink representing the `jinja2.Template` class instantiation argument.
12+
*
13+
* from jinja2 import Template
14+
* template = Template(`sink`)
15+
*/
16+
class Jinja2TemplateSink extends SSTISink {
17+
override string toString() { result = "argument to Jinja2.template()" }
18+
19+
Jinja2TemplateSink() {
20+
exists(CallNode call |
21+
call.getFunction().pointsTo(theJinja2TemplateClass()) and
22+
call.getArg(0) = this
23+
)
24+
}
25+
26+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Provides classes which model the `Mako` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `mako.template.Template` */
8+
ClassValue theMakoTemplateClass() { result = Value::named("mako.template.Template") }
9+
10+
/**
11+
* Sink representing the `mako.template.Template` class instantiation argument.
12+
*
13+
* from mako.template import Template
14+
* mytemplate = Template("hello world!")
15+
*/
16+
class MakoTemplateSink extends SSTISink {
17+
override string toString() { result = "argument to mako.template.Template()" }
18+
19+
MakoTemplateSink() {
20+
exists(CallNode call |
21+
call.getFunction().pointsTo(theMakoTemplateClass()) and
22+
call.getArg(0) = this
23+
)
24+
}
25+
26+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import semmle.python.dataflow.TaintTracking
2+
3+
/**
4+
* A generic taint sink that is vulnerable to template inclusions.
5+
* The `temp` in `Jinja2.Template(temp)` and similar.
6+
*/
7+
abstract class SSTISink extends TaintSink { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** Imports all files which model potential SSTI sinks */
2+
3+
import experimental.semmle.python.templates.Chameleon
4+
import experimental.semmle.python.templates.DjangoTemplate
5+
import experimental.semmle.python.templates.Genshi
6+
import experimental.semmle.python.templates.Jinja
7+
import experimental.semmle.python.templates.Mako
8+
import experimental.semmle.python.templates.TRender
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Provides classes which model the `TRender` package. */
2+
3+
import python
4+
import semmle.python.web.HttpRequest
5+
import experimental.semmle.python.templates.SSTISink
6+
7+
/** returns the ClassValue representing `jinja2.Template` */
8+
ClassValue theTRenderTemplateClass() { result = Value::named("trender.TRender") }
9+
10+
/**
11+
* Sink representing the `trender.TRender` class instantiation argument.
12+
*
13+
* from trender import TRender
14+
* template = TRender(`sink`)
15+
*/
16+
class TRenderTemplateSink extends SSTISink {
17+
override string toString() { result = "argument to trender.TRender()" }
18+
19+
TRenderTemplateSink() {
20+
exists(CallNode call |
21+
call.getFunction().pointsTo(theTRenderTemplateClass()) and
22+
call.getArg(0) = this
23+
)
24+
}
25+
26+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
27+
}

0 commit comments

Comments
 (0)