Skip to content

Commit c41c2ad

Browse files
Merge pull request github#17922 from joefarebrother/python-promote-template-injection
Python: Promote Template Injection query from experimental
2 parents 0580ad0 + f82fa20 commit c41c2ad

Some content is hidden

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

64 files changed

+501
-523
lines changed

python/ql/lib/semmle/python/Concepts.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,31 @@ class LdapFilterEscaping extends Escaping {
861861
LdapFilterEscaping() { super.getKind() = Escaping::getLdapFilterKind() }
862862
}
863863

864+
/**
865+
* A data-flow node that constructs a template in a templating engine.
866+
*
867+
* Extend this class to refine existing API models. If you want to model new APIs,
868+
* extend `TemplateConstruction::Range` instead.
869+
*/
870+
class TemplateConstruction extends DataFlow::Node instanceof TemplateConstruction::Range {
871+
/** Gets the argument that specifies the template source. */
872+
DataFlow::Node getSourceArg() { result = super.getSourceArg() }
873+
}
874+
875+
/** Provides classes for modeling template construction APIs. */
876+
module TemplateConstruction {
877+
/**
878+
* A data-flow node that constructs a template in a templating engine.
879+
*
880+
* Extend this class to model new APIs. If you want to refine existing API models,
881+
* extend `TemplateConstruction` instead.
882+
*/
883+
abstract class Range extends DataFlow::Node {
884+
/** Gets the argument that specifies the template source. */
885+
abstract DataFlow::Node getSourceArg();
886+
}
887+
}
888+
864889
/** Provides classes for modeling HTTP-related APIs. */
865890
module Http {
866891
/** Gets an HTTP verb, in upper case */

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ private import semmle.python.frameworks.Aiohttp
1111
private import semmle.python.frameworks.Aiomysql
1212
private import semmle.python.frameworks.Aiopg
1313
private import semmle.python.frameworks.Aiosqlite
14+
private import semmle.python.frameworks.Airspeed
1415
private import semmle.python.frameworks.Anyio
1516
private import semmle.python.frameworks.Asyncpg
1617
private import semmle.python.frameworks.Baize
17-
private import semmle.python.frameworks.BSon
1818
private import semmle.python.frameworks.Bottle
19+
private import semmle.python.frameworks.BSon
1920
private import semmle.python.frameworks.CassandraDriver
21+
private import semmle.python.frameworks.Chameleon
2022
private import semmle.python.frameworks.Cherrypy
23+
private import semmle.python.frameworks.Chevron
2124
private import semmle.python.frameworks.ClickhouseDriver
2225
private import semmle.python.frameworks.Cryptodome
2326
private import semmle.python.frameworks.Cryptography
@@ -30,10 +33,12 @@ private import semmle.python.frameworks.FastApi
3033
private import semmle.python.frameworks.Flask
3134
private import semmle.python.frameworks.FlaskAdmin
3235
private import semmle.python.frameworks.FlaskSqlAlchemy
36+
private import semmle.python.frameworks.Genshi
3337
private import semmle.python.frameworks.Gradio
3438
private import semmle.python.frameworks.Httpx
3539
private import semmle.python.frameworks.Idna
3640
private import semmle.python.frameworks.Invoke
41+
private import semmle.python.frameworks.Jinja2
3742
private import semmle.python.frameworks.Jmespath
3843
private import semmle.python.frameworks.Joblib
3944
private import semmle.python.frameworks.JsonPickle
@@ -42,6 +47,7 @@ private import semmle.python.frameworks.Ldap3
4247
private import semmle.python.frameworks.Libtaxii
4348
private import semmle.python.frameworks.Libxml2
4449
private import semmle.python.frameworks.Lxml
50+
private import semmle.python.frameworks.Mako
4551
private import semmle.python.frameworks.MarkupSafe
4652
private import semmle.python.frameworks.Multidict
4753
private import semmle.python.frameworks.Mysql
@@ -78,6 +84,7 @@ private import semmle.python.frameworks.Streamlit
7884
private import semmle.python.frameworks.Toml
7985
private import semmle.python.frameworks.Torch
8086
private import semmle.python.frameworks.Tornado
87+
private import semmle.python.frameworks.TRender
8188
private import semmle.python.frameworks.Twisted
8289
private import semmle.python.frameworks.Ujson
8390
private import semmle.python.frameworks.Urllib3
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `airspeed` library.
3+
* See https://github.com/purcell/airspeed.
4+
*/
5+
6+
private import python
7+
private import semmle.python.ApiGraphs
8+
private import semmle.python.Concepts
9+
10+
/**
11+
* INTERNAL: Do not use.
12+
*
13+
* Provides classes modeling security-relevant aspects of the `airspeed` library.
14+
* See https://github.com/purcell/airspeed.
15+
*/
16+
module Airspeed {
17+
/** A call to `airspeed.Template`. */
18+
private class AirspeedTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
19+
AirspeedTemplateConstruction() {
20+
this = API::moduleImport("airspeed").getMember("Template").getACall()
21+
}
22+
23+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
24+
}
25+
}

python/ql/lib/semmle/python/frameworks/Bottle.qll

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module Bottle {
3939
ViewCallable() { this = any(BottleRouteSetup rs).getARequestHandler() }
4040
}
4141

42-
/** Get methods that reprsent a route in Bottle */
42+
/** Get methods that represent a route in Bottle */
4343
string routeMethods() { result = ["route", "get", "post", "put", "delete", "patch"] }
4444

4545
private class BottleRouteSetup extends Http::Server::RouteSetup::Range, DataFlow::CallCfgNode {
@@ -171,5 +171,17 @@ module Bottle {
171171
override predicate valueAllowsNewline() { none() }
172172
}
173173
}
174+
175+
/** Provides models for functions that construct templates. */
176+
module Templates {
177+
/** A call to `bottle.template`or `bottle.SimpleTemplate`. */
178+
private class BottleTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
179+
BottleTemplateConstruction() {
180+
this = API::moduleImport("bottle").getMember(["template", "SimpleTemplate"]).getACall()
181+
}
182+
183+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
184+
}
185+
}
174186
}
175187
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `chameleon` PyPI package.
3+
* See https://chameleon.readthedocs.io/en/latest/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.ApiGraphs
8+
private import semmle.python.Concepts
9+
10+
/**
11+
* INTERNAL: Do not use.
12+
*
13+
* Provides classes modeling security-relevant aspects of the `chameleon` PyPI package.
14+
* See https://chameleon.readthedocs.io/en/latest/.
15+
*/
16+
module Chameleon {
17+
/** A call to `chameleon.PageTemplate`. */
18+
private class ChameleonTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
19+
ChameleonTemplateConstruction() {
20+
this = API::moduleImport("chameleon").getMember("PageTemplate").getACall()
21+
}
22+
23+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `chevron` PyPI package.
3+
* See https://pypi.org/project/chevron.
4+
*/
5+
6+
private import python
7+
private import semmle.python.ApiGraphs
8+
private import semmle.python.Concepts
9+
10+
/**
11+
* INTERNAL: Do not use.
12+
*
13+
* Provides classes modeling security-relevant aspects of the `chevron` PyPI package.
14+
* See https://pypi.org/project/chevron.
15+
*/
16+
module Chevron {
17+
/** A call to `chevron.render`. */
18+
private class ChevronRenderConstruction extends TemplateConstruction::Range, API::CallNode {
19+
ChevronRenderConstruction() {
20+
this = API::moduleImport("chevron").getMember("render").getACall()
21+
}
22+
23+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
24+
}
25+
}

python/ql/lib/semmle/python/frameworks/Django.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,4 +2996,17 @@ module PrivateDjango {
29962996
any()
29972997
}
29982998
}
2999+
3000+
// ---------------------------------------------------------------------------
3001+
// Templates
3002+
// ---------------------------------------------------------------------------
3003+
/** A call to `django.template.Template` */
3004+
private class DjangoTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
3005+
DjangoTemplateConstruction() {
3006+
this = API::moduleImport("django").getMember("template").getMember("Template").getACall()
3007+
}
3008+
3009+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
3010+
}
3011+
// TODO: Support `from_string` on instances of `django.template.Engine`.
29993012
}

python/ql/lib/semmle/python/frameworks/Flask.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,4 +721,16 @@ module Flask {
721721
preservesValue = false
722722
}
723723
}
724+
725+
/** A call to `flask.render_template_string` or `flask.stream_template_string` as a template construction sink. */
726+
private class FlaskTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
727+
FlaskTemplateConstruction() {
728+
this =
729+
API::moduleImport("flask")
730+
.getMember(["render_template_string", "stream_template_string"])
731+
.getACall()
732+
}
733+
734+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
735+
}
724736
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `Genshi` PyPI package.
3+
* See https://genshi.edgewall.org/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.ApiGraphs
8+
private import semmle.python.Concepts
9+
10+
/**
11+
* INTERNAL: Do not use.
12+
*
13+
* Provides classes modeling security-relevant aspects of the `Genshi` PyPI package.
14+
* See https://genshi.edgewall.org/.
15+
*/
16+
module Genshi {
17+
/** A call to `genshi.template.text.NewTextTemplate` or `genshi.template.text.OldTextTemplate`. */
18+
private class GenshiTextTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
19+
GenshiTextTemplateConstruction() {
20+
this =
21+
API::moduleImport("genshi")
22+
.getMember("template")
23+
.getMember("text")
24+
.getMember(["NewTextTemplate", "OldTextTemplate", "TextTemplate"])
25+
.getACall()
26+
}
27+
28+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
29+
}
30+
31+
/** A call to `genshi.template.MarkupTemplate` */
32+
private class GenshiMarkupTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
33+
GenshiMarkupTemplateConstruction() {
34+
this =
35+
API::moduleImport("genshi")
36+
.getMember("template")
37+
.getMember("markup")
38+
.getMember("MarkupTemplate")
39+
.getACall()
40+
}
41+
42+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
43+
}
44+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `jinja2` PyPI package.
3+
* See https://jinja.palletsprojects.com.
4+
*/
5+
6+
private import python
7+
private import semmle.python.ApiGraphs
8+
private import semmle.python.Concepts
9+
private import semmle.python.frameworks.data.ModelsAsData
10+
11+
/**
12+
* INTERNAL: Do not use
13+
*
14+
* Provides classes modeling security-relevant aspects of the `jinja2` PyPI package.
15+
* See https://jinja.palletsprojects.com.
16+
*/
17+
module Jinja2 {
18+
/** A call to `jinja2.Template`. */
19+
private class Jinja2TemplateConstruction extends TemplateConstruction::Range, API::CallNode {
20+
Jinja2TemplateConstruction() {
21+
this = API::moduleImport("jinja2").getMember("Template").getACall()
22+
}
23+
24+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
25+
}
26+
27+
/** Definitions for modeling jinja `Environment`s. */
28+
module EnvironmentClass {
29+
/** Gets a reference to the `jinja2.Environment` class. */
30+
API::Node classRef() {
31+
result = API::moduleImport("jinja2").getMember("Environment")
32+
or
33+
result = ModelOutput::getATypeNode("jinja.Environment~Subclass").getASubclass*()
34+
}
35+
36+
/** Gets a reference to an instance of `jinja2.Environment`. */
37+
API::Node instance() { result = classRef().getAnInstance() }
38+
39+
/** A call to `jinja2.Environment.from_string`. */
40+
private class Jinja2FromStringConstruction extends TemplateConstruction::Range, API::CallNode {
41+
Jinja2FromStringConstruction() {
42+
this = EnvironmentClass::instance().getMember("from_string").getACall()
43+
}
44+
45+
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)