diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index cc0712d181b7..94d660d75108 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -861,6 +861,31 @@ class LdapFilterEscaping extends Escaping { LdapFilterEscaping() { super.getKind() = Escaping::getLdapFilterKind() } } +/** + * A data-flow node that constructs a template in a templating engine. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `TemplateConstruction::Range` instead. + */ +class TemplateConstruction extends DataFlow::Node instanceof TemplateConstruction::Range { + /** Gets the argument that specifies the template source. */ + DataFlow::Node getSourceArg() { result = super.getSourceArg() } +} + +/** Provides classes for modeling template construction APIs. */ +module TemplateConstruction { + /** + * A data-flow node that constructs a template in a templating engine. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `TemplateConstruction` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument that specifies the template source. */ + abstract DataFlow::Node getSourceArg(); + } +} + /** Provides classes for modeling HTTP-related APIs. */ module Http { /** Gets an HTTP verb, in upper case */ diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index da35994b955d..af9417308ab3 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -11,13 +11,17 @@ private import semmle.python.frameworks.Aiohttp private import semmle.python.frameworks.Aiomysql private import semmle.python.frameworks.Aiopg private import semmle.python.frameworks.Aiosqlite +private import semmle.python.frameworks.Airspeed private import semmle.python.frameworks.Anyio private import semmle.python.frameworks.Asyncpg private import semmle.python.frameworks.Baize +private import semmle.python.frameworks.Bottle private import semmle.python.frameworks.BSon private import semmle.python.frameworks.Bottle private import semmle.python.frameworks.CassandraDriver +private import semmle.python.frameworks.Chameleon private import semmle.python.frameworks.Cherrypy +private import semmle.python.frameworks.Chevron private import semmle.python.frameworks.ClickhouseDriver private import semmle.python.frameworks.Cryptodome private import semmle.python.frameworks.Cryptography @@ -30,10 +34,12 @@ private import semmle.python.frameworks.FastApi private import semmle.python.frameworks.Flask private import semmle.python.frameworks.FlaskAdmin private import semmle.python.frameworks.FlaskSqlAlchemy +private import semmle.python.frameworks.Genshi private import semmle.python.frameworks.Gradio private import semmle.python.frameworks.Httpx private import semmle.python.frameworks.Idna private import semmle.python.frameworks.Invoke +private import semmle.python.frameworks.Jinja2 private import semmle.python.frameworks.Jmespath private import semmle.python.frameworks.Joblib private import semmle.python.frameworks.JsonPickle @@ -42,6 +48,7 @@ private import semmle.python.frameworks.Ldap3 private import semmle.python.frameworks.Libtaxii private import semmle.python.frameworks.Libxml2 private import semmle.python.frameworks.Lxml +private import semmle.python.frameworks.Mako private import semmle.python.frameworks.MarkupSafe private import semmle.python.frameworks.Multidict private import semmle.python.frameworks.Mysql @@ -78,6 +85,7 @@ private import semmle.python.frameworks.Streamlit private import semmle.python.frameworks.Toml private import semmle.python.frameworks.Torch private import semmle.python.frameworks.Tornado +private import semmle.python.frameworks.TRender private import semmle.python.frameworks.Twisted private import semmle.python.frameworks.Ujson private import semmle.python.frameworks.Urllib3 diff --git a/python/ql/lib/semmle/python/frameworks/Airspeed.qll b/python/ql/lib/semmle/python/frameworks/Airspeed.qll new file mode 100644 index 000000000000..a08a1b4a46be --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Airspeed.qll @@ -0,0 +1,25 @@ +/** + * Provides classes modeling security-relevant aspects of the `airspeed` library. + * See https://github.com/purcell/airspeed. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts + +/** + * INTERNAL: Do not use. + * + * Provides classes modeling security-relevant aspects of the `airspeed` library. + * See https://github.com/purcell/airspeed. + */ +module Airspeed { + /** A call to `airspeed.Template`. */ + private class AirspeedTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + AirspeedTemplateConstruction() { + this = API::moduleImport("airspeed").getMember("Template").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Bottle.qll b/python/ql/lib/semmle/python/frameworks/Bottle.qll index ce2a41dbaf4e..c03ea3df184e 100644 --- a/python/ql/lib/semmle/python/frameworks/Bottle.qll +++ b/python/ql/lib/semmle/python/frameworks/Bottle.qll @@ -39,7 +39,7 @@ module Bottle { ViewCallable() { this = any(BottleRouteSetup rs).getARequestHandler() } } - /** Get methods that reprsent a route in Bottle */ + /** Get methods that represent a route in Bottle */ string routeMethods() { result = ["route", "get", "post", "put", "delete", "patch"] } private class BottleRouteSetup extends Http::Server::RouteSetup::Range, DataFlow::CallCfgNode { @@ -171,5 +171,17 @@ module Bottle { override predicate valueAllowsNewline() { none() } } } + + /** Provides models for functions that construct templates. */ + module Templates { + /** A call to `bottle.template`or `bottle.SimpleTemplate`. */ + private class BottleTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + BottleTemplateConstruction() { + this = API::moduleImport("bottle").getMember(["template", "SimpleTemplate"]).getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } + } } } diff --git a/python/ql/lib/semmle/python/frameworks/Chameleon.qll b/python/ql/lib/semmle/python/frameworks/Chameleon.qll new file mode 100644 index 000000000000..cf5444c40ce2 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Chameleon.qll @@ -0,0 +1,25 @@ +/** + * Provides classes modeling security-relevant aspects of the `chameleon` PyPI package. + * See https://chameleon.readthedocs.io/en/latest/. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts + +/** + * INTERNAL: Do not use. + * + * Provides classes modeling security-relevant aspects of the `chameleon` PyPI package. + * See https://chameleon.readthedocs.io/en/latest/. + */ +module Chameleon { + /** A call to `chameleon.PageTemplate`. */ + private class ChameleonTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + ChameleonTemplateConstruction() { + this = API::moduleImport("chameleon").getMember("PageTemplate").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Chevron.qll b/python/ql/lib/semmle/python/frameworks/Chevron.qll new file mode 100644 index 000000000000..ec5676a2f04a --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Chevron.qll @@ -0,0 +1,25 @@ +/** + * Provides classes modeling security-relevant aspects of the `chevron` PyPI package. + * See https://pypi.org/project/chevron. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts + +/** + * INTERNAL: Do not use. + * + * Provides classes modeling security-relevant aspects of the `chevron` PyPI package. + * See https://pypi.org/project/chevron. + */ +module Chevron { + /** A call to `chevron.render`. */ + private class ChevronRenderConstruction extends TemplateConstruction::Range, API::CallNode { + ChevronRenderConstruction() { + this = API::moduleImport("chevron").getMember("render").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index 351420818c38..4aa5776ad54b 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2996,4 +2996,17 @@ module PrivateDjango { any() } } + + // --------------------------------------------------------------------------- + // Templates + // --------------------------------------------------------------------------- + /** A call to `django.template.Template` */ + private class DjangoTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + DjangoTemplateConstruction() { + this = API::moduleImport("django").getMember("template").getMember("Template").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } + // TODO: Support `from_string` on instances of `django.template.Engine`. } diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index 62722a1958ac..0e5d6065c474 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -721,4 +721,16 @@ module Flask { preservesValue = false } } + + /** A call to `flask.render_template_string` or `flask.stream_template_string` as a template construction sink. */ + private class FlaskTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + FlaskTemplateConstruction() { + this = + API::moduleImport("flask") + .getMember(["render_template_string", "stream_template_string"]) + .getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } } diff --git a/python/ql/lib/semmle/python/frameworks/Genshi.qll b/python/ql/lib/semmle/python/frameworks/Genshi.qll new file mode 100644 index 000000000000..8e368391cf74 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Genshi.qll @@ -0,0 +1,44 @@ +/** + * Provides classes modeling security-relevant aspects of the `Genshi` PyPI package. + * See https://genshi.edgewall.org/. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts + +/** + * INTERNAL: Do not use. + * + * Provides classes modeling security-relevant aspects of the `Genshi` PyPI package. + * See https://genshi.edgewall.org/. + */ +module Genshi { + /** A call to `genshi.template.text.NewTextTemplate` or `genshi.template.text.OldTextTemplate`. */ + private class GenshiTextTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + GenshiTextTemplateConstruction() { + this = + API::moduleImport("genshi") + .getMember("template") + .getMember("text") + .getMember(["NewTextTemplate", "OldTextTemplate", "TextTemplate"]) + .getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } + + /** A call to `genshi.template.MarkupTemplate` */ + private class GenshiMarkupTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + GenshiMarkupTemplateConstruction() { + this = + API::moduleImport("genshi") + .getMember("template") + .getMember("markup") + .getMember("MarkupTemplate") + .getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Jinja2.qll b/python/ql/lib/semmle/python/frameworks/Jinja2.qll new file mode 100644 index 000000000000..0d0a8d989211 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Jinja2.qll @@ -0,0 +1,56 @@ +/** + * Provides classes modeling security-relevant aspects of the `jinja2` PyPI package. + * See https://jinja.palletsprojects.com. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts +private import semmle.python.frameworks.data.ModelsAsData + +/** + * INTERNAL: Do not use + * + * Provides classes modeling security-relevant aspects of the `jinja2` PyPI package. + * See https://jinja.palletsprojects.com. + */ +module Jinja2 { + /** A call to `jinja2.Template`. */ + private class Jinja2TemplateConstruction extends TemplateConstruction::Range, API::CallNode { + Jinja2TemplateConstruction() { + this = API::moduleImport("jinja2").getMember("Template").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } + + /** Definitions for modeling jinja `Environment`s. */ + module EnvironmentClass { + /** Gets a reference to the `jinja2.Environment` class. */ + API::Node classRef() { + result = API::moduleImport("jinja2").getMember("Environment") + or + result = ModelOutput::getATypeNode("jinja.Environment~Subclass").getASubclass*() + } + + /** Gets a reference to an instance of `jinja2.Environment`. */ + private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) { + t.start() and + result = EnvironmentClass::classRef().getACall() + or + exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) + } + + /** Gets a reference to an instance of `jinja2.Environment`. */ + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + + /** A call to `jinja2.Environment.from_string`. */ + private class Jinja2FromStringConstruction extends TemplateConstruction::Range, + DataFlow::MethodCallNode + { + Jinja2FromStringConstruction() { this.calls(EnvironmentClass::instance(), "from_string") } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Mako.qll b/python/ql/lib/semmle/python/frameworks/Mako.qll new file mode 100644 index 000000000000..2209c0f89d2f --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Mako.qll @@ -0,0 +1,25 @@ +/** + * Provides classes modeling security-relevant aspects of the `Mako` PyPI package. + * See https://www.makotemplates.org/. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts + +/** + * INTERNAL: Do not use. + * + * Provides classes modeling security-relevant aspects of the `Mako` PyPI package. + * See https://www.makotemplates.org/. + */ +module Mako { + /** A call to `mako.template.Template`. */ + private class MakoTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + MakoTemplateConstruction() { + this = API::moduleImport("mako").getMember("template").getMember("Template").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/TRender.qll b/python/ql/lib/semmle/python/frameworks/TRender.qll new file mode 100644 index 000000000000..fae27f418c34 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/TRender.qll @@ -0,0 +1,25 @@ +/** + * Provides classes modeling security-relevant aspects of the `trender` PyPI package. + * See https://github.com/cesbit/trender. + */ + +private import python +private import semmle.python.ApiGraphs +private import semmle.python.Concepts + +/** + * INTERNAL: Do not use. + * + * Provides classes modeling security-relevant aspects of the `trender` PyPI package. + * See https://github.com/cesbit/trender. + */ +module TRender { + /** A call to `trender.TRender`. */ + private class TRenderTemplateConstruction extends TemplateConstruction::Range, API::CallNode { + TRenderTemplateConstruction() { + this = API::moduleImport("trender").getMember("TRender").getACall() + } + + override DataFlow::Node getSourceArg() { result = this.getArg(0) } + } +} diff --git a/python/ql/src/experimental/Security/CWE-074/TemplateInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionCustomizations.qll similarity index 81% rename from python/ql/src/experimental/Security/CWE-074/TemplateInjectionCustomizations.qll rename to python/ql/lib/semmle/python/security/dataflow/TemplateInjectionCustomizations.qll index 593ca9fee4cf..e61d55253090 100644 --- a/python/ql/src/experimental/Security/CWE-074/TemplateInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionCustomizations.qll @@ -9,7 +9,6 @@ private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards -private import TemplateConstructionConcept /** * Provides default sources, sinks and sanitizers for detecting @@ -32,11 +31,6 @@ module TemplateInjection { */ abstract class Sanitizer extends DataFlow::Node { } - /** - * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! - */ - deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; - /** * An active threat-model source, considered as a flow source. */ @@ -53,7 +47,4 @@ module TemplateInjection { * A comparison with a constant, considered as a sanitizer-guard. */ class ConstCompareAsSanitizerGuard extends Sanitizer, ConstCompareBarrier { } - - /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ - deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; } diff --git a/python/ql/src/experimental/Security/CWE-074/TemplateInjectionQuery.qll b/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll similarity index 63% rename from python/ql/src/experimental/Security/CWE-074/TemplateInjectionQuery.qll rename to python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll index 111485e2602d..22c228f48d59 100644 --- a/python/ql/src/experimental/Security/CWE-074/TemplateInjectionQuery.qll +++ b/python/ql/lib/semmle/python/security/dataflow/TemplateInjectionQuery.qll @@ -1,5 +1,9 @@ /** * Provides a taint-tracking configuration for detecting "template injection" vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `TemplateInjectionFlow` is needed, otherwise + * `TemplateInjectionCustomizations` should be imported instead. */ private import python @@ -7,7 +11,7 @@ import semmle.python.dataflow.new.DataFlow import semmle.python.dataflow.new.TaintTracking import TemplateInjectionCustomizations::TemplateInjection -module TemplateInjectionConfig implements DataFlow::ConfigSig { +private module TemplateInjectionConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { node instanceof Source } predicate isSink(DataFlow::Node node) { node instanceof Sink } @@ -15,4 +19,5 @@ module TemplateInjectionConfig implements DataFlow::ConfigSig { predicate isBarrierIn(DataFlow::Node node) { node instanceof Sanitizer } } +/** Global taint-tracking for detecting "template injection" vulnerabilities. */ module TemplateInjectionFlow = TaintTracking::Global; diff --git a/python/ql/src/Security/CWE-074/TemplateInjection.qhelp b/python/ql/src/Security/CWE-074/TemplateInjection.qhelp new file mode 100644 index 000000000000..c3770d59cf2a --- /dev/null +++ b/python/ql/src/Security/CWE-074/TemplateInjection.qhelp @@ -0,0 +1,30 @@ + + + +

+ A template from a server templating engine such as Jinja constructed from user input can allow the user to execute arbitrary code using certain template features. It can also allow for cross-site scripting. +

+
+ +

+ Ensure that an untrusted value is not used to directly construct a template. + Jinja also provides a SandboxedEnvironment that prohibits access to unsafe methods and attributes, that can be used if constructing a template from user input is absolutely necessary. +

+
+ +

In the following case, template is used to generate a Jinja2 template string. This can lead to remote code execution.

+ + +

The following is an example of a string that could be used to cause remote code execution when interpreted as a template:

+ + +

In the following case, user input is not used to construct the template; rather is only used for as the parameters to render the template, which is safe.

+ + +

In the following case, a SandboxedEnvironment is used, preventing remote code execution.

+ +
+ +
  • Portswigger: Server-Side Template Injection.
  • +
    +
    diff --git a/python/ql/src/experimental/Security/CWE-074/TemplateInjection.ql b/python/ql/src/Security/CWE-074/TemplateInjection.ql similarity index 65% rename from python/ql/src/experimental/Security/CWE-074/TemplateInjection.ql rename to python/ql/src/Security/CWE-074/TemplateInjection.ql index a10ad09a6ac9..2ea68414259e 100644 --- a/python/ql/src/experimental/Security/CWE-074/TemplateInjection.ql +++ b/python/ql/src/Security/CWE-074/TemplateInjection.ql @@ -1,20 +1,20 @@ /** * @name Server Side Template Injection - * @description Using user-controlled data to create a template can cause security issues. + * @description Using user-controlled data to create a template can lead to remote code execution or cross site scripting. * @kind path-problem * @problem.severity error * @precision high + * @security-severity 9.3 * @id py/template-injection * @tags security - * experimental * external/cwe/cwe-074 */ import python -import TemplateInjectionQuery +import semmle.python.security.dataflow.TemplateInjectionQuery import TemplateInjectionFlow::PathGraph from TemplateInjectionFlow::PathNode source, TemplateInjectionFlow::PathNode sink where TemplateInjectionFlow::flowPath(source, sink) -select sink.getNode(), source, sink, "This Template depends on $@.", source.getNode(), +select sink.getNode(), source, sink, "This Template construction depends on $@.", source.getNode(), "user-provided value" diff --git a/python/ql/src/experimental/Security/CWE-074/JinjaBad.py b/python/ql/src/Security/CWE-074/examples/JinjaBad.py similarity index 55% rename from python/ql/src/experimental/Security/CWE-074/JinjaBad.py rename to python/ql/src/Security/CWE-074/examples/JinjaBad.py index aaac3ec819eb..0a82135b49ba 100644 --- a/python/ql/src/experimental/Security/CWE-074/JinjaBad.py +++ b/python/ql/src/Security/CWE-074/examples/JinjaBad.py @@ -1,19 +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 +from jinja2 import Template, escape def a(request): - # Load the template template = request.GET['template'] - t = Jinja2_Template(template) + + # BAD: Template is constructed from user input. + t = 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), -] +] \ No newline at end of file diff --git a/python/ql/src/Security/CWE-074/examples/JinjaGoodParam.py b/python/ql/src/Security/CWE-074/examples/JinjaGoodParam.py new file mode 100644 index 000000000000..1d8bb6962f6a --- /dev/null +++ b/python/ql/src/Security/CWE-074/examples/JinjaGoodParam.py @@ -0,0 +1,17 @@ +from django.urls import path +from django.http import HttpResponse +from jinja2 import Template, escape + + +def a(request): + # GOOD: Template is a constant, not constructed from user input + t = Template("Hello, {{name}}!") + + name = request.GET['name'] + html = t.render(name=escape(name)) + return HttpResponse(html) + + +urlpatterns = [ + path('a', a), +] \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-074/JinjaGood.py b/python/ql/src/Security/CWE-074/examples/JinjaGoodSandbox.py similarity index 56% rename from python/ql/src/experimental/Security/CWE-074/JinjaGood.py rename to python/ql/src/Security/CWE-074/examples/JinjaGoodSandbox.py index a1b605618501..488591c6f83e 100644 --- a/python/ql/src/experimental/Security/CWE-074/JinjaGood.py +++ b/python/ql/src/Security/CWE-074/examples/JinjaGoodSandbox.py @@ -1,20 +1,21 @@ from django.urls import path from django.http import HttpResponse -from jinja2 import Template as Jinja2_Template -from jinja2 import Environment, DictLoader, escape +from jinja2 import escape +from jinja2.sandbox import SandboxedEnvironment def a(request): - # Load the template + env = SandboxedEnvironment() template = request.GET['template'] - env = SandboxedEnvironment(undefined=StrictUndefined) + + # GOOD: A sandboxed environment is used to construct the template. 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), -] +] \ No newline at end of file diff --git a/python/ql/src/Security/CWE-074/examples/template_exploit.txt b/python/ql/src/Security/CWE-074/examples/template_exploit.txt new file mode 100644 index 000000000000..607b95bd8d8e --- /dev/null +++ b/python/ql/src/Security/CWE-074/examples/template_exploit.txt @@ -0,0 +1 @@ +{% for s in ().__class__.__base__.__subclasses__() %}{% if "warning" in s.__name__ %}{{s()._module.__builtins__['__import__']('os').system('cat /etc/passwd') }}{% endif %}{% endfor %} diff --git a/python/ql/src/change-notes/2024-11-21-template-injection.md b/python/ql/src/change-notes/2024-11-21-template-injection.md new file mode 100644 index 000000000000..a2d782f8cc07 --- /dev/null +++ b/python/ql/src/change-notes/2024-11-21-template-injection.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* The Server Side Template Injection query (`py/template-injection`), originally contributed to the experimental query pack by @porcupineyhairs, has been promoted to the ain query suite. This query finds instances of templates for a template engine such as Jinja being constructed with user input. \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-074/TemplateConstructionConcept.qll b/python/ql/src/experimental/Security/CWE-074/TemplateConstructionConcept.qll deleted file mode 100644 index a20babf15eb6..000000000000 --- a/python/ql/src/experimental/Security/CWE-074/TemplateConstructionConcept.qll +++ /dev/null @@ -1,159 +0,0 @@ -private import python -private import semmle.python.dataflow.new.DataFlow -private import semmle.python.ApiGraphs - -/** - * A data-flow node that constructs a template. - * - * Extend this class to refine existing API models. If you want to model new APIs, - * extend `TemplateConstruction::Range` instead. - */ -class TemplateConstruction extends DataFlow::Node instanceof TemplateConstruction::Range { - /** Gets the argument that specifies the template source. */ - DataFlow::Node getSourceArg() { result = super.getSourceArg() } -} - -/** Provides a class for modeling new system-command execution APIs. */ -module TemplateConstruction { - /** - * A data-flow node that constructs a template. - * - * Extend this class to model new APIs. If you want to refine existing API models, - * extend `TemplateConstruction` instead. - */ - abstract class Range extends DataFlow::Node { - /** Gets the argument that specifies the template source. */ - abstract DataFlow::Node getSourceArg(); - } -} - -// ----------------------------------------------------------------------------- -/** A call to `airspeed.Template`. */ -class AirspeedTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - AirspeedTemplateConstruction() { - this = API::moduleImport("airspeed").getMember("Template").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `bottle.SimpleTemplate`. */ -class BottleSimpleTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - BottleSimpleTemplateConstruction() { - this = API::moduleImport("bottle").getMember("SimpleTemplate").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `bottle.template`. */ -class BottleTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - BottleTemplateConstruction() { - this = API::moduleImport("bottle").getMember("template").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `chameleon.PageTemplate`. */ -class ChameleonTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - ChameleonTemplateConstruction() { - this = API::moduleImport("chameleon").getMember("PageTemplate").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `Cheetah.Template.Template`. */ -class CheetahTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - CheetahTemplateConstruction() { - this = - API::moduleImport("Cheetah") - .getMember("Template") - .getMember("Template") - .getASubclass*() - .getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `chevron.render`. */ -class ChevronRenderConstruction extends TemplateConstruction::Range, API::CallNode { - ChevronRenderConstruction() { this = API::moduleImport("chevron").getMember("render").getACall() } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `django.template.Template` */ -class DjangoTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - DjangoTemplateConstruction() { - this = API::moduleImport("django").getMember("template").getMember("Template").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -// TODO: support django.template.engines["django"]].from_string -/** A call to `flask.render_template_string`. */ -class FlaskTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - FlaskTemplateConstruction() { - this = API::moduleImport("flask").getMember("render_template_string").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `genshi.template.TextTemplate`. */ -class GenshiTextTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - GenshiTextTemplateConstruction() { - this = API::moduleImport("genshi").getMember("template").getMember("TextTemplate").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `genshi.template.MarkupTemplate` */ -class GenshiMarkupTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - GenshiMarkupTemplateConstruction() { - this = API::moduleImport("genshi").getMember("template").getMember("MarkupTemplate").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `jinja2.Template`. */ -class Jinja2TemplateConstruction extends TemplateConstruction::Range, API::CallNode { - Jinja2TemplateConstruction() { - this = API::moduleImport("jinja2").getMember("Template").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `jinja2.from_string`. */ -class Jinja2FromStringConstruction extends TemplateConstruction::Range, API::CallNode { - Jinja2FromStringConstruction() { - this = API::moduleImport("jinja2").getMember("from_string").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `mako.template.Template`. */ -class MakoTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - MakoTemplateConstruction() { - this = API::moduleImport("mako").getMember("template").getMember("Template").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} - -/** A call to `trender.TRender`. */ -class TRenderTemplateConstruction extends TemplateConstruction::Range, API::CallNode { - TRenderTemplateConstruction() { - this = API::moduleImport("trender").getMember("TRender").getACall() - } - - override DataFlow::Node getSourceArg() { result = this.getArg(0) } -} diff --git a/python/ql/src/experimental/Security/CWE-074/TemplateInjection.qhelp b/python/ql/src/experimental/Security/CWE-074/TemplateInjection.qhelp deleted file mode 100644 index b044243fc8e1..000000000000 --- a/python/ql/src/experimental/Security/CWE-074/TemplateInjection.qhelp +++ /dev/null @@ -1,24 +0,0 @@ - - - -

    - 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. -

    -
    - -

    - 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. -

    -
    - -

    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.

    - - -

    Here we have fixed the problem by using the Jinja sandbox environment for evaluating untrusted code.

    - -
    - -
  • Portswigger : [Server Side Template Injection](https://portswigger.net/web-security/server-side-template-injection)
  • -
    -
    diff --git a/python/ql/test/experimental/meta/ConceptsTest.qll b/python/ql/test/experimental/meta/ConceptsTest.qll index 8ab87e56d1c4..40aa9c951b0d 100644 --- a/python/ql/test/experimental/meta/ConceptsTest.qll +++ b/python/ql/test/experimental/meta/ConceptsTest.qll @@ -663,6 +663,20 @@ module CorsMiddlewareTest implements TestSig { } } +module TemplateConstructionTest implements TestSig { + string getARelevantTag() { result = "templateConstruction" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(location.getFile().getRelativePath()) and + exists(TemplateConstruction tc | + location = tc.getLocation() and + element = tc.toString() and + value = prettyNodeForInlineTest(tc.getSourceArg()) and + tag = "templateConstruction" + ) + } +} + import MakeTest, MergeTests5, MergeTests5>>> + CsrfLocalProtectionSettingTest, + MergeTests3>>> diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/AirspeedSsti.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/AirspeedSsti.py deleted file mode 100644 index 8938d8602f8d..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/AirspeedSsti.py +++ /dev/null @@ -1,11 +0,0 @@ -import airspeed -from flask import Flask, request - - -app = Flask(__name__) - - -@route('/other') -def a(): - template = request.args.get('template') - return airspeed.Template(template) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/BottleSsti.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/BottleSsti.py deleted file mode 100644 index b5f8a5feeffa..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/BottleSsti.py +++ /dev/null @@ -1,20 +0,0 @@ -from bottle import Bottle, route, request, redirect, response, SimpleTemplate -from bottle import template as temp - - -app = Bottle() - - -@route('/other') -def a(): - template = request.query.template - tpl = SimpleTemplate(template) - tpl.render(name='World') - return tmp - - -@route('/other2') -def b(): - template = request.query.template - temp(template, name='World') - return tmp diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/Chameleon.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/Chameleon.py deleted file mode 100644 index f58a641a9be3..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/Chameleon.py +++ /dev/null @@ -1,10 +0,0 @@ -from chameleon import PageTemplate -from django.urls import path -from django.http import HttpResponse - - -def chameleon(request): - template = request.GET['template'] - tmpl = PageTemplate(template) - return HttpResponse(tmpl) - diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/CheetahSinks.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/CheetahSinks.py deleted file mode 100644 index 7f9fed4decf5..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/CheetahSinks.py +++ /dev/null @@ -1,22 +0,0 @@ -from flask import Flask, request -from Cheetah.Template import Template - - -app = Flask(__name__) - - -@app.route('/other') -def a(): - template = request.args.get('template') - return Template(template) - - -class Template3(Template): - title = 'Hello World Example!' - contents = 'Hello World!' - - -@app.route('/other2') -def b(): - template = request.args.get('template') - t3 = Template3(template) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/ChevronSsti.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/ChevronSsti.py deleted file mode 100644 index f3b0e57fc8f7..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/ChevronSsti.py +++ /dev/null @@ -1,24 +0,0 @@ -from flask import Flask, request -import chevron - - -app = Flask(__name__) - - -@app.route('/other') -def a(): - template = request.args.get('template') - return chevron.render(template, {"key": "value"}) - - -@app.route('/other2') -def b(): - template = request.args.get('template') - args = { - 'template': template, - - 'data': { - 'key': 'value' - } - } - return chevron.render(**args) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/DjangoTemplates.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/DjangoTemplates.py deleted file mode 100644 index 26f48fd92780..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/DjangoTemplates.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.urls import path -from django.http import HttpResponse -from django.template import Template, Context, Engine, engines - - -def dj(request): - # Load the template - template = request.GET['template'] - t = Template(template) - ctx = Context(locals()) - html = t.render(ctx) - return HttpResponse(html) - - -def djEngine(request): - # Load the template - template = request.GET['template'] - - django_engine = engines['django'] - t = django_engine.from_string(template) - ctx = Context(locals()) - html = t.render(ctx) - return HttpResponse(html) - - -def djEngineJinja(request): - # Load the template - template = request.GET['template'] - - django_engine = engines['jinja'] - t = django_engine.from_string(template) - ctx = Context(locals()) - html = t.render(ctx) - return HttpResponse(html) - - -urlpatterns = [ - path('', dj), - path('', djEngine), - path('', djEngineJinja), -] diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/FlaskTemplate.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/FlaskTemplate.py deleted file mode 100644 index b74e3cce715d..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/FlaskTemplate.py +++ /dev/null @@ -1,22 +0,0 @@ -from flask import Flask, request - - -app = Flask(__name__) - - -@app.route("/") -def home(): - from flask import render_template_string - if request.args.get('template'): - return render_template_string(request.args.get('template')) - - -@app.route("/a") -def a(): - import flask - return flask.render_template_string(request.args.get('template')) - - - -if __name__ == "__main__": - app.run(debug=True) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/Genshi.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/Genshi.py deleted file mode 100644 index 7800c50da968..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/Genshi.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.urls import path -from django.http import HttpResponse -from genshi.template import TextTemplate,MarkupTemplate - -def genshi1(): - template = request.GET['template'] - tmpl = MarkupTemplate(template) - return HttpResponse(tmpl) - -def genshi2(): - template = request.GET['template'] - tmpl = TextTemplate(template) - return HttpResponse(tmpl) - -urlpatterns = [ - path('', genshi1), - path('', genshi2) -] diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/MakoSsti.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/MakoSsti.py deleted file mode 100644 index 7f6b25cb26cb..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/MakoSsti.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.urls import path -from django.http import HttpResponse -from mako.template import Template - - -def mako(request): - # Load the template - template = request.GET['template'] - mytemplate = Template(template) - return HttpResponse(mytemplate) - - -urlpatterns = [ - path('', mako) -] diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TRender.py b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TRender.py deleted file mode 100644 index 2514f22b8059..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TRender.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path -from django.http import HttpResponse -from trender import TRender - -def trender(request): - template = request.GET['template'] - compiled = TRender(template) - return HttpResponse(compiled) - -urlpatterns = [ - path('', trender) -] diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.expected b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.expected deleted file mode 100644 index 06cf81cc6aaf..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.expected +++ /dev/null @@ -1,107 +0,0 @@ -edges -| AirspeedSsti.py:2:26:2:32 | ControlFlowNode for ImportMember | AirspeedSsti.py:2:26:2:32 | ControlFlowNode for request | provenance | | -| AirspeedSsti.py:2:26:2:32 | ControlFlowNode for request | AirspeedSsti.py:10:16:10:22 | ControlFlowNode for request | provenance | | -| AirspeedSsti.py:10:5:10:12 | ControlFlowNode for template | AirspeedSsti.py:11:30:11:37 | ControlFlowNode for template | provenance | | -| AirspeedSsti.py:10:16:10:22 | ControlFlowNode for request | AirspeedSsti.py:10:16:10:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| AirspeedSsti.py:10:16:10:27 | ControlFlowNode for Attribute | AirspeedSsti.py:10:16:10:43 | ControlFlowNode for Attribute() | provenance | dict.get | -| AirspeedSsti.py:10:16:10:43 | ControlFlowNode for Attribute() | AirspeedSsti.py:10:5:10:12 | ControlFlowNode for template | provenance | | -| CheetahSinks.py:1:26:1:32 | ControlFlowNode for ImportMember | CheetahSinks.py:1:26:1:32 | ControlFlowNode for request | provenance | | -| CheetahSinks.py:1:26:1:32 | ControlFlowNode for request | CheetahSinks.py:10:16:10:22 | ControlFlowNode for request | provenance | | -| CheetahSinks.py:1:26:1:32 | ControlFlowNode for request | CheetahSinks.py:21:16:21:22 | ControlFlowNode for request | provenance | | -| CheetahSinks.py:10:5:10:12 | ControlFlowNode for template | CheetahSinks.py:11:21:11:28 | ControlFlowNode for template | provenance | | -| CheetahSinks.py:10:16:10:22 | ControlFlowNode for request | CheetahSinks.py:10:16:10:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| CheetahSinks.py:10:16:10:27 | ControlFlowNode for Attribute | CheetahSinks.py:10:16:10:43 | ControlFlowNode for Attribute() | provenance | dict.get | -| CheetahSinks.py:10:16:10:43 | ControlFlowNode for Attribute() | CheetahSinks.py:10:5:10:12 | ControlFlowNode for template | provenance | | -| CheetahSinks.py:21:5:21:12 | ControlFlowNode for template | CheetahSinks.py:22:20:22:27 | ControlFlowNode for template | provenance | | -| CheetahSinks.py:21:16:21:22 | ControlFlowNode for request | CheetahSinks.py:21:16:21:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| CheetahSinks.py:21:16:21:27 | ControlFlowNode for Attribute | CheetahSinks.py:21:16:21:43 | ControlFlowNode for Attribute() | provenance | dict.get | -| CheetahSinks.py:21:16:21:43 | ControlFlowNode for Attribute() | CheetahSinks.py:21:5:21:12 | ControlFlowNode for template | provenance | | -| ChevronSsti.py:1:26:1:32 | ControlFlowNode for ImportMember | ChevronSsti.py:1:26:1:32 | ControlFlowNode for request | provenance | | -| ChevronSsti.py:1:26:1:32 | ControlFlowNode for request | ChevronSsti.py:10:16:10:22 | ControlFlowNode for request | provenance | | -| ChevronSsti.py:10:5:10:12 | ControlFlowNode for template | ChevronSsti.py:11:27:11:34 | ControlFlowNode for template | provenance | | -| ChevronSsti.py:10:16:10:22 | ControlFlowNode for request | ChevronSsti.py:10:16:10:27 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| ChevronSsti.py:10:16:10:27 | ControlFlowNode for Attribute | ChevronSsti.py:10:16:10:43 | ControlFlowNode for Attribute() | provenance | dict.get | -| ChevronSsti.py:10:16:10:43 | ControlFlowNode for Attribute() | ChevronSsti.py:10:5:10:12 | ControlFlowNode for template | provenance | | -| DjangoTemplates.py:6:8:6:14 | ControlFlowNode for request | DjangoTemplates.py:8:5:8:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | -| DjangoTemplates.py:8:5:8:12 | ControlFlowNode for template | DjangoTemplates.py:9:18:9:25 | ControlFlowNode for template | provenance | | -| FlaskTemplate.py:1:26:1:32 | ControlFlowNode for ImportMember | FlaskTemplate.py:1:26:1:32 | ControlFlowNode for request | provenance | | -| FlaskTemplate.py:1:26:1:32 | ControlFlowNode for request | FlaskTemplate.py:10:8:10:14 | ControlFlowNode for request | provenance | | -| FlaskTemplate.py:1:26:1:32 | ControlFlowNode for request | FlaskTemplate.py:11:39:11:45 | ControlFlowNode for request | provenance | | -| FlaskTemplate.py:1:26:1:32 | ControlFlowNode for request | FlaskTemplate.py:17:41:17:47 | ControlFlowNode for request | provenance | | -| FlaskTemplate.py:10:8:10:14 | ControlFlowNode for request | FlaskTemplate.py:11:39:11:50 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| FlaskTemplate.py:11:39:11:45 | ControlFlowNode for request | FlaskTemplate.py:11:39:11:50 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| FlaskTemplate.py:11:39:11:50 | ControlFlowNode for Attribute | FlaskTemplate.py:11:39:11:66 | ControlFlowNode for Attribute() | provenance | dict.get | -| FlaskTemplate.py:17:41:17:47 | ControlFlowNode for request | FlaskTemplate.py:17:41:17:52 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| FlaskTemplate.py:17:41:17:52 | ControlFlowNode for Attribute | FlaskTemplate.py:17:41:17:68 | ControlFlowNode for Attribute() | provenance | dict.get | -| JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | JinjaSsti.py:9:5:9:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | -| JinjaSsti.py:9:5:9:12 | ControlFlowNode for template | JinjaSsti.py:10:25:10:32 | ControlFlowNode for template | provenance | | -| JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | JinjaSsti.py:19:5:19:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | -| JinjaSsti.py:19:5:19:12 | ControlFlowNode for template | JinjaSsti.py:20:28:20:35 | ControlFlowNode for template | provenance | | -| MakoSsti.py:6:10:6:16 | ControlFlowNode for request | MakoSsti.py:8:5:8:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | -| MakoSsti.py:8:5:8:12 | ControlFlowNode for template | MakoSsti.py:9:27:9:34 | ControlFlowNode for template | provenance | | -| TRender.py:5:13:5:19 | ControlFlowNode for request | TRender.py:6:5:6:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | -| TRender.py:6:5:6:12 | ControlFlowNode for template | TRender.py:7:24:7:31 | ControlFlowNode for template | provenance | | -nodes -| AirspeedSsti.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| AirspeedSsti.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| AirspeedSsti.py:10:5:10:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| AirspeedSsti.py:10:16:10:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| AirspeedSsti.py:10:16:10:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| AirspeedSsti.py:10:16:10:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| AirspeedSsti.py:11:30:11:37 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| CheetahSinks.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| CheetahSinks.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| CheetahSinks.py:10:5:10:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| CheetahSinks.py:10:16:10:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| CheetahSinks.py:10:16:10:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| CheetahSinks.py:10:16:10:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| CheetahSinks.py:11:21:11:28 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| CheetahSinks.py:21:5:21:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| CheetahSinks.py:21:16:21:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| CheetahSinks.py:21:16:21:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| CheetahSinks.py:21:16:21:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| CheetahSinks.py:22:20:22:27 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| ChevronSsti.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| ChevronSsti.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| ChevronSsti.py:10:5:10:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| ChevronSsti.py:10:16:10:22 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| ChevronSsti.py:10:16:10:27 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| ChevronSsti.py:10:16:10:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| ChevronSsti.py:11:27:11:34 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| DjangoTemplates.py:6:8:6:14 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| DjangoTemplates.py:8:5:8:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| DjangoTemplates.py:9:18:9:25 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| FlaskTemplate.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | -| FlaskTemplate.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| FlaskTemplate.py:10:8:10:14 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| FlaskTemplate.py:11:39:11:45 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| FlaskTemplate.py:11:39:11:50 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| FlaskTemplate.py:11:39:11:66 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| FlaskTemplate.py:17:41:17:47 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| FlaskTemplate.py:17:41:17:52 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| FlaskTemplate.py:17:41:17:68 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| JinjaSsti.py:9:5:9:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| JinjaSsti.py:10:25:10:32 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| JinjaSsti.py:19:5:19:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| JinjaSsti.py:20:28:20:35 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| MakoSsti.py:6:10:6:16 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| MakoSsti.py:8:5:8:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| MakoSsti.py:9:27:9:34 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| TRender.py:5:13:5:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| TRender.py:6:5:6:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -| TRender.py:7:24:7:31 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | -subpaths -#select -| AirspeedSsti.py:11:30:11:37 | ControlFlowNode for template | AirspeedSsti.py:2:26:2:32 | ControlFlowNode for ImportMember | AirspeedSsti.py:11:30:11:37 | ControlFlowNode for template | This Template depends on $@. | AirspeedSsti.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | -| CheetahSinks.py:11:21:11:28 | ControlFlowNode for template | CheetahSinks.py:1:26:1:32 | ControlFlowNode for ImportMember | CheetahSinks.py:11:21:11:28 | ControlFlowNode for template | This Template depends on $@. | CheetahSinks.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| CheetahSinks.py:22:20:22:27 | ControlFlowNode for template | CheetahSinks.py:1:26:1:32 | ControlFlowNode for ImportMember | CheetahSinks.py:22:20:22:27 | ControlFlowNode for template | This Template depends on $@. | CheetahSinks.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| ChevronSsti.py:11:27:11:34 | ControlFlowNode for template | ChevronSsti.py:1:26:1:32 | ControlFlowNode for ImportMember | ChevronSsti.py:11:27:11:34 | ControlFlowNode for template | This Template depends on $@. | ChevronSsti.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| DjangoTemplates.py:9:18:9:25 | ControlFlowNode for template | DjangoTemplates.py:6:8:6:14 | ControlFlowNode for request | DjangoTemplates.py:9:18:9:25 | ControlFlowNode for template | This Template depends on $@. | DjangoTemplates.py:6:8:6:14 | ControlFlowNode for request | user-provided value | -| FlaskTemplate.py:11:39:11:66 | ControlFlowNode for Attribute() | FlaskTemplate.py:1:26:1:32 | ControlFlowNode for ImportMember | FlaskTemplate.py:11:39:11:66 | ControlFlowNode for Attribute() | This Template depends on $@. | FlaskTemplate.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| FlaskTemplate.py:17:41:17:68 | ControlFlowNode for Attribute() | FlaskTemplate.py:1:26:1:32 | ControlFlowNode for ImportMember | FlaskTemplate.py:17:41:17:68 | ControlFlowNode for Attribute() | This Template depends on $@. | FlaskTemplate.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| JinjaSsti.py:10:25:10:32 | ControlFlowNode for template | JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | JinjaSsti.py:10:25:10:32 | ControlFlowNode for template | This Template depends on $@. | JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | user-provided value | -| JinjaSsti.py:20:28:20:35 | ControlFlowNode for template | JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | JinjaSsti.py:20:28:20:35 | ControlFlowNode for template | This Template depends on $@. | JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | user-provided value | -| MakoSsti.py:9:27:9:34 | ControlFlowNode for template | MakoSsti.py:6:10:6:16 | ControlFlowNode for request | MakoSsti.py:9:27:9:34 | ControlFlowNode for template | This Template depends on $@. | MakoSsti.py:6:10:6:16 | ControlFlowNode for request | user-provided value | -| TRender.py:7:24:7:31 | ControlFlowNode for template | TRender.py:5:13:5:19 | ControlFlowNode for request | TRender.py:7:24:7:31 | ControlFlowNode for template | This Template depends on $@. | TRender.py:5:13:5:19 | ControlFlowNode for request | user-provided value | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.qlref b/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.qlref deleted file mode 100644 index 90efec9f6360..000000000000 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE-074/TemplateInjection.ql diff --git a/python/ql/test/library-tests/frameworks/Genshi/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/Genshi/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/Genshi/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/Genshi/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/Genshi/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/Genshi/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/Genshi/template_test.py b/python/ql/test/library-tests/frameworks/Genshi/template_test.py new file mode 100644 index 000000000000..d585ee1a81ea --- /dev/null +++ b/python/ql/test/library-tests/frameworks/Genshi/template_test.py @@ -0,0 +1,9 @@ +from genshi.template.text import TextTemplate, NewTextTemplate, OldTextTemplate +from genshi.template.markup import MarkupTemplate + +def test(): + a = TextTemplate("abc") # $ templateConstruction="abc" + a = OldTextTemplate("abc") # $ templateConstruction="abc" + a = NewTextTemplate("abc") # $ templateConstruction="abc" + a = MarkupTemplate("abc") # $ templateConstruction="abc" + return a \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/Mako/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/Mako/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/Mako/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/Mako/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/Mako/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/Mako/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/Mako/template_test.py b/python/ql/test/library-tests/frameworks/Mako/template_test.py new file mode 100644 index 000000000000..224954cf2633 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/Mako/template_test.py @@ -0,0 +1,4 @@ +from mako.template import Template + +def test(): + return Template("abc") # $ templateConstruction="abc" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/TRender/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/TRender/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/TRender/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/TRender/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/TRender/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/TRender/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/TRender/template_test.py b/python/ql/test/library-tests/frameworks/TRender/template_test.py new file mode 100644 index 000000000000..f62d33c26d53 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/TRender/template_test.py @@ -0,0 +1,4 @@ +from trender import TRender + +def test(): + return TRender("abc") # $ templateConstruction="abc" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/airspeed/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/airspeed/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/airspeed/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/airspeed/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/airspeed/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/airspeed/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/airspeed/template_test.py b/python/ql/test/library-tests/frameworks/airspeed/template_test.py new file mode 100644 index 000000000000..34d4c29fff6f --- /dev/null +++ b/python/ql/test/library-tests/frameworks/airspeed/template_test.py @@ -0,0 +1,4 @@ +from airspeed import Template + +def test(): + return Template("abc") # $ templateConstruction="abc" diff --git a/python/ql/test/library-tests/frameworks/bottle/template_test.py b/python/ql/test/library-tests/frameworks/bottle/template_test.py new file mode 100644 index 000000000000..db48cfc4fc9f --- /dev/null +++ b/python/ql/test/library-tests/frameworks/bottle/template_test.py @@ -0,0 +1,9 @@ +import bottle +from bottle import response, request, template, SimpleTemplate + +app = bottle.app() +@app.route('/test', method=['OPTIONS', 'GET']) # $ routeSetup="/test" +def test1(): # $ requestHandler + template("abc") # $ templateConstruction="abc" + SimpleTemplate("abc") # $ templateConstruction="abc" + return '[1]' # $ HttpResponse mimetype=text/html responseBody='[1]' \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/chameleon/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/chameleon/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/chameleon/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/chameleon/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/chameleon/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/chameleon/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/chameleon/template_test.py b/python/ql/test/library-tests/frameworks/chameleon/template_test.py new file mode 100644 index 000000000000..ad6d85036eab --- /dev/null +++ b/python/ql/test/library-tests/frameworks/chameleon/template_test.py @@ -0,0 +1,4 @@ +from chameleon import PageTemplate + +def test(): + return PageTemplate("abc") # $ templateConstruction="abc" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/chevron/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/chevron/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/chevron/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/chevron/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/chevron/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/chevron/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/chevron/template_test.py b/python/ql/test/library-tests/frameworks/chevron/template_test.py new file mode 100644 index 000000000000..7aff524166d5 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/chevron/template_test.py @@ -0,0 +1,4 @@ +from chevron import render + +def test(): + return render("abc") # $ templateConstruction="abc" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/django-v2-v3/template_test.py b/python/ql/test/library-tests/frameworks/django-v2-v3/template_test.py new file mode 100644 index 000000000000..ba98c8f41964 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/django-v2-v3/template_test.py @@ -0,0 +1,17 @@ +from django.template import Template, engines +from django.urls import path +from django.http.response import HttpResponse + +def a(request): # $requestHandler + t = Template("abc").render() # $templateConstruction="abc" + return HttpResponse(t) # $HttpResponse + +def b(request): # $requestHandler + # This case is not currently supported + t = django.template.engines["django"].from_string("abc") # $MISSING:templateConstruction="abc" + return HttpResponse(t) # $HttpResponse + +urlpatterns = [ + path("a", a), # $ routeSetup="a" + path("b", b), # $ routeSetup="b" +] \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/flask/taint_test.py b/python/ql/test/library-tests/frameworks/flask/taint_test.py index 227aecbf7452..ac8a5a82dc28 100644 --- a/python/ql/test/library-tests/frameworks/flask/taint_test.py +++ b/python/ql/test/library-tests/frameworks/flask/taint_test.py @@ -222,25 +222,25 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route # render_template_string source = TAINTED_STRING ensure_tainted(source) # $ tainted - res = render_template_string(source) + res = render_template_string(source) # $ templateConstruction=source ensure_tainted(res) # $ tainted # since template variables are auto-escaped, we don't treat result as tainted # see https://flask.palletsprojects.com/en/2.3.x/api/#flask.render_template_string - res = render_template_string("Hello {{ foo }}", foo=TAINTED_STRING) + res = render_template_string("Hello {{ foo }}", foo=TAINTED_STRING) # $ templateConstruction="Hello {{ foo }}" ensure_not_tainted(res) # stream_template_string source = TAINTED_STRING ensure_tainted(source) # $ tainted - res = stream_template_string(source) + res = stream_template_string(source) # $ templateConstruction=source for x in res: ensure_tainted(x) # $ tainted # since template variables are auto-escaped, we don't treat result as tainted # see https://flask.palletsprojects.com/en/2.3.x/api/#flask.stream_template_string - res = stream_template_string("Hello {{ foo }}", foo=TAINTED_STRING) + res = stream_template_string("Hello {{ foo }}", foo=TAINTED_STRING) # $ templateConstruction="Hello {{ foo }}" for x in res: ensure_not_tainted(x) diff --git a/python/ql/test/library-tests/frameworks/flask/template_test.py b/python/ql/test/library-tests/frameworks/flask/template_test.py new file mode 100644 index 000000000000..8d867e148291 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/flask/template_test.py @@ -0,0 +1,16 @@ +from flask import Flask, Response, stream_with_context, render_template_string, stream_template_string +app = Flask(__name__) + +@app.route("/a") # $routeSetup="/a" +def a(): # $requestHandler + r = render_template_string("abc") # $ templateConstruction="abc" + return r # $ HttpResponse + +@app.route("/b") # $routeSetup="/b" +def b(): # $requestHandler + s = stream_template_string("abc") # $ templateConstruction="abc" + r = Response(stream_with_context(s)) # $ HttpResponse + return r # $ HttpResponse + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/jinja2/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/jinja2/ConceptsTest.expected new file mode 100644 index 000000000000..a74f2c23cda2 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/jinja2/ConceptsTest.expected @@ -0,0 +1,2 @@ +testFailures +failures \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/jinja2/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/jinja2/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/jinja2/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/jinja2/template_test.py b/python/ql/test/library-tests/frameworks/jinja2/template_test.py new file mode 100644 index 000000000000..587de84f6216 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/jinja2/template_test.py @@ -0,0 +1,7 @@ +from jinja2 import Environment, Template + +def test(): + env = Environment() + t = env.from_string("abc") # $ templateConstruction="abc" + t = Template("abc") # $ templateConstruction="abc" + return t \ No newline at end of file diff --git a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/JinjaSsti.py b/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/JinjaSsti.py similarity index 76% rename from python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/JinjaSsti.py rename to python/ql/test/query-tests/Security/CWE-074-TemplateInjection/JinjaSsti.py index 28225c81cbaa..f1fe834e4936 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-074-TemplateInjection/JinjaSsti.py +++ b/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/JinjaSsti.py @@ -1,13 +1,13 @@ from django.urls import path from django.http import HttpResponse -from jinja2 import Template as Jinja2_Template +from jinja2 import Template from jinja2 import Environment, DictLoader, escape def a(request): # Load the template template = request.GET['template'] - t = Jinja2_Template(template) + t = Template(template) # BAD: Template constructed from user input name = request.GET['name'] # Render the template with the context data html = t.render(name=escape(name)) @@ -17,7 +17,8 @@ def b(request): import jinja2 # Load the template template = request.GET['template'] - t = jinja2.from_string(template) + env = Environment() + t = env.from_string(template) # BAD: Template constructed from user input name = request.GET['name'] # Render the template with the context data html = t.render(name=escape(name)) diff --git a/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.expected b/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.expected new file mode 100644 index 000000000000..3a833787a982 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.expected @@ -0,0 +1,16 @@ +edges +| JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | JinjaSsti.py:9:5:9:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | +| JinjaSsti.py:9:5:9:12 | ControlFlowNode for template | JinjaSsti.py:10:18:10:25 | ControlFlowNode for template | provenance | | +| JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | JinjaSsti.py:19:5:19:12 | ControlFlowNode for template | provenance | AdditionalTaintStep | +| JinjaSsti.py:19:5:19:12 | ControlFlowNode for template | JinjaSsti.py:21:25:21:32 | ControlFlowNode for template | provenance | | +nodes +| JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| JinjaSsti.py:9:5:9:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | +| JinjaSsti.py:10:18:10:25 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | +| JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| JinjaSsti.py:19:5:19:12 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | +| JinjaSsti.py:21:25:21:32 | ControlFlowNode for template | semmle.label | ControlFlowNode for template | +subpaths +#select +| JinjaSsti.py:10:18:10:25 | ControlFlowNode for template | JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | JinjaSsti.py:10:18:10:25 | ControlFlowNode for template | This Template construction depends on $@. | JinjaSsti.py:7:7:7:13 | ControlFlowNode for request | user-provided value | +| JinjaSsti.py:21:25:21:32 | ControlFlowNode for template | JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | JinjaSsti.py:21:25:21:32 | ControlFlowNode for template | This Template construction depends on $@. | JinjaSsti.py:16:7:16:13 | ControlFlowNode for request | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.qlref b/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.qlref new file mode 100644 index 000000000000..ead6bb469c6a --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-074-TemplateInjection/TemplateInjection.qlref @@ -0,0 +1 @@ +Security/CWE-074/TemplateInjection.ql \ No newline at end of file