diff --git a/python/ql/src/semmle/python/Exprs.qll b/python/ql/src/semmle/python/Exprs.qll index 264bc826150a..e90210edc652 100644 --- a/python/ql/src/semmle/python/Exprs.qll +++ b/python/ql/src/semmle/python/Exprs.qll @@ -126,6 +126,11 @@ class Expr extends Expr_, AstNode { this.pointsTo(value, _) } + /** Gets a value that this expression might "point-to". */ + Value pointsTo() { + this.pointsTo(result) + } + } /** An attribute expression, such as `value.attr` */ diff --git a/python/ql/src/semmle/python/web/pyramid/Redirect.qll b/python/ql/src/semmle/python/web/pyramid/Redirect.qll index ee47a885d221..8c7e57a4285b 100644 --- a/python/ql/src/semmle/python/web/pyramid/Redirect.qll +++ b/python/ql/src/semmle/python/web/pyramid/Redirect.qll @@ -1,16 +1,16 @@ -/** Provides class representing the `pyramid.redirect` function. +/** + * Provides class representing the `pyramid.redirect` function. * This module is intended to be imported into a taint-tracking query * to extend `TaintSink`. */ -import python +import python import semmle.python.security.TaintTracking import semmle.python.security.strings.Basic import semmle.python.web.Http -private ClassObject redirectClass() { - exists(ModuleObject ex | - ex.getName() = "pyramid.httpexceptions" | +private ClassValue redirectClass() { + exists(ModuleValue ex | ex.getName() = "pyramid.httpexceptions" | ex.attr("HTTPFound") = result or ex.attr("HTTPTemporaryRedirect") = result @@ -21,19 +21,13 @@ private ClassObject redirectClass() { * Represents an argument to the `tornado.redirect` function. */ class PyramidRedirect extends HttpRedirectTaintSink { - - override string toString() { - result = "pyramid.redirect" - } + override string toString() { result = "pyramid.redirect" } PyramidRedirect() { - exists(CallNode call | - call.getFunction().refersTo(redirectClass()) - | + exists(CallNode call | call.getFunction().pointsTo(redirectClass()) | call.getArg(0) = this or call.getArgByName("location") = this ) } - } diff --git a/python/ql/src/semmle/python/web/pyramid/Request.qll b/python/ql/src/semmle/python/web/pyramid/Request.qll index 53750b3cb7c2..2514e77d5ac0 100644 --- a/python/ql/src/semmle/python/web/pyramid/Request.qll +++ b/python/ql/src/semmle/python/web/pyramid/Request.qll @@ -1,25 +1,17 @@ import python - import semmle.python.security.TaintTracking import semmle.python.web.Http private import semmle.python.web.webob.Request private import semmle.python.web.pyramid.View class PyramidRequest extends BaseWebobRequest { + PyramidRequest() { this = "pyramid.request" } - PyramidRequest() { - this = "pyramid.request" - } - - override ClassValue getType() { - result = Value::named("pyramid.request.Request") - } - + override ClassValue getType() { result = Value::named("pyramid.request.Request") } } /** Source of pyramid request objects */ class PyramidViewArgument extends TaintSource { - PyramidViewArgument() { exists(Function view_func | is_pyramid_view_function(view_func) and @@ -27,13 +19,7 @@ class PyramidViewArgument extends TaintSource { ) } - override predicate isSourceOf(TaintKind kind) { - kind instanceof PyramidRequest - } - - override string toString() { - result = "pyramid.view.argument" - } + override predicate isSourceOf(TaintKind kind) { kind instanceof PyramidRequest } + override string toString() { result = "pyramid.view.argument" } } - diff --git a/python/ql/src/semmle/python/web/pyramid/Response.qll b/python/ql/src/semmle/python/web/pyramid/Response.qll index cecf30a9e6e7..511d1e283228 100644 --- a/python/ql/src/semmle/python/web/pyramid/Response.qll +++ b/python/ql/src/semmle/python/web/pyramid/Response.qll @@ -1,17 +1,15 @@ import python - - import semmle.python.security.TaintTracking import semmle.python.security.strings.Basic import semmle.python.web.Http - private import semmle.python.web.pyramid.View private import semmle.python.web.Http -/** A pyramid response, which is vulnerable to any sort of - * http response malice. */ +/** + * A pyramid response, which is vulnerable to any sort of + * http response malice. + */ class PyramidRoutedResponse extends HttpResponseTaintSink { - PyramidRoutedResponse() { exists(PyFunctionObject view | is_pyramid_view_function(view.getFunction()) and @@ -19,23 +17,16 @@ class PyramidRoutedResponse extends HttpResponseTaintSink { ) } - override predicate sinks(TaintKind kind) { - kind instanceof StringKind - } - - override string toString() { - result = "pyramid.routed.response" - } + override predicate sinks(TaintKind kind) { kind instanceof StringKind } + override string toString() { result = "pyramid.routed.response" } } - class PyramidCookieSet extends CookieSet, CallNode { - PyramidCookieSet() { exists(ControlFlowNode f | f = this.getFunction().(AttrNode).getObject("set_cookie") and - f.refersTo(_, ModuleObject::named("pyramid").attr("Response"), _) + f.pointsTo().getClass() = Value::named("pyramid.response.Response") ) } @@ -44,5 +35,4 @@ class PyramidCookieSet extends CookieSet, CallNode { override ControlFlowNode getKey() { result = this.getArg(0) } override ControlFlowNode getValue() { result = this.getArg(1) } - } diff --git a/python/ql/src/semmle/python/web/pyramid/View.qll b/python/ql/src/semmle/python/web/pyramid/View.qll index 64cb4cf3271f..3bf49de87c32 100644 --- a/python/ql/src/semmle/python/web/pyramid/View.qll +++ b/python/ql/src/semmle/python/web/pyramid/View.qll @@ -1,14 +1,9 @@ import python -ModuleObject thePyramidViewModule() { - result.getName() = "pyramid.view" -} +ModuleValue thePyramidViewModule() { result.getName() = "pyramid.view" } -Object thePyramidViewConfig() { - result = thePyramidViewModule().attr("view_config") -} +Value thePyramidViewConfig() { result = thePyramidViewModule().attr("view_config") } predicate is_pyramid_view_function(Function func) { - func.getADecorator().refersTo(_, thePyramidViewConfig(), _) + func.getADecorator().pointsTo().getClass() = thePyramidViewConfig() } - diff --git a/python/ql/test/library-tests/web/pyramid/Routing.expected b/python/ql/test/library-tests/web/pyramid/Routing.expected new file mode 100644 index 000000000000..795f604b1f9d --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Routing.expected @@ -0,0 +1,3 @@ +| test.py:7 | Function home | +| test.py:15 | Function greet | +| test.py:24 | Function stuff | diff --git a/python/ql/test/library-tests/web/pyramid/Routing.ql b/python/ql/test/library-tests/web/pyramid/Routing.ql new file mode 100644 index 000000000000..ef21e5510fee --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Routing.ql @@ -0,0 +1,9 @@ +import python + +import semmle.python.web.pyramid.View + +from Function func + +where is_pyramid_view_function(func) + +select func.getLocation().toString(), func.toString() diff --git a/python/ql/test/library-tests/web/pyramid/Sinks.expected b/python/ql/test/library-tests/web/pyramid/Sinks.expected new file mode 100644 index 000000000000..03e32dac04a6 --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Sinks.expected @@ -0,0 +1,3 @@ +| test.py:8 | Response() | externally controlled string | +| test.py:17 | Response() | externally controlled string | +| test.py:25 | Dict | externally controlled string | diff --git a/python/ql/test/library-tests/web/pyramid/Sinks.ql b/python/ql/test/library-tests/web/pyramid/Sinks.ql new file mode 100644 index 000000000000..5fb6ad477d4e --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Sinks.ql @@ -0,0 +1,11 @@ + +import python + +import semmle.python.web.HttpRequest +import semmle.python.web.HttpResponse +import semmle.python.security.strings.Untrusted + + +from TaintSink sink, TaintKind kind +where sink.sinks(kind) and sink.getLocation().getFile().getName().matches("%test.py") +select sink.getLocation().toString(), sink.(ControlFlowNode).getNode().toString(), kind diff --git a/python/ql/test/library-tests/web/pyramid/Sources.expected b/python/ql/test/library-tests/web/pyramid/Sources.expected new file mode 100644 index 000000000000..f7fe4bb2c5c2 --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Sources.expected @@ -0,0 +1,3 @@ +| test.py:7 | request | pyramid.request | +| test.py:15 | request | pyramid.request | +| test.py:24 | request | pyramid.request | diff --git a/python/ql/test/library-tests/web/pyramid/Sources.ql b/python/ql/test/library-tests/web/pyramid/Sources.ql new file mode 100644 index 000000000000..b1b7fe3e08bc --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Sources.ql @@ -0,0 +1,11 @@ + +import python + +import semmle.python.web.HttpRequest +import semmle.python.web.HttpResponse +import semmle.python.security.strings.Untrusted + + +from TaintSource src, TaintKind kind +where src.isSourceOf(kind) +select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind diff --git a/python/ql/test/library-tests/web/pyramid/Taint.expected b/python/ql/test/library-tests/web/pyramid/Taint.expected new file mode 100644 index 000000000000..94b8f844efb9 --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Taint.expected @@ -0,0 +1,11 @@ +| test.py:7 | request | pyramid.request | +| test.py:15 | request | pyramid.request | +| test.py:16 | Attribute | {externally controlled string} | +| test.py:16 | Subscript | externally controlled string | +| test.py:16 | request | pyramid.request | +| test.py:17 | BinaryExpr | externally controlled string | +| test.py:17 | name | externally controlled string | +| test.py:24 | request | pyramid.request | +| test.py:25 | Attribute | externally controlled string | +| test.py:25 | Dict | {externally controlled string} | +| test.py:25 | request | pyramid.request | diff --git a/python/ql/test/library-tests/web/pyramid/Taint.ql b/python/ql/test/library-tests/web/pyramid/Taint.ql new file mode 100644 index 000000000000..54af91f3300b --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/Taint.ql @@ -0,0 +1,12 @@ + +import python + +import semmle.python.web.HttpRequest +import semmle.python.web.HttpResponse +import semmle.python.security.strings.Untrusted + +from TaintedNode node +where node.getLocation().getFile().getName().matches("%test.py") + +select node.getLocation().toString(), node.getAstNode().toString(), node.getTaintKind() + diff --git a/python/ql/test/library-tests/web/pyramid/options b/python/ql/test/library-tests/web/pyramid/options new file mode 100644 index 000000000000..0267f80a6d65 --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/ +optimize: true diff --git a/python/ql/test/library-tests/web/pyramid/test.py b/python/ql/test/library-tests/web/pyramid/test.py new file mode 100644 index 000000000000..9fad8a7ba00a --- /dev/null +++ b/python/ql/test/library-tests/web/pyramid/test.py @@ -0,0 +1,25 @@ +from pyramid.view import view_config +from pyramid.response import Response + +@view_config( + route_name='home' +) +def home(request): + return Response('Welcome!') + + +@view_config( + route_name='greet', + request_method='POST' +) +def greet(request): + name = request.POST['arg'] + return Response('Welcome %s!' % name) + + +@view_config( + route_name='stuff', + renderer='json' +) +def stuff(request): + return {"err": 0, "body": request.body} diff --git a/python/ql/test/query-tests/Security/lib/pyramid/__init__.py b/python/ql/test/query-tests/Security/lib/pyramid/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/pyramid/response.py b/python/ql/test/query-tests/Security/lib/pyramid/response.py new file mode 100644 index 000000000000..a482ac919fff --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/pyramid/response.py @@ -0,0 +1,2 @@ +class Response(object): + pass diff --git a/python/ql/test/query-tests/Security/lib/pyramid/view.py b/python/ql/test/query-tests/Security/lib/pyramid/view.py new file mode 100644 index 000000000000..c7487bf827b6 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/pyramid/view.py @@ -0,0 +1,7 @@ +# https://docs.pylonsproject.org/projects/pyramid/en/1.10-branch/_modules/pyramid/view.html#view_config +class view_config(object): + def __init__(self, **settings): + pass + + def __call__(self, wrapped): + pass