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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions python/ql/src/semmle/python/Exprs.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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` */
Expand Down
20 changes: 7 additions & 13 deletions python/ql/src/semmle/python/web/pyramid/Redirect.qll
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
)
}

}
22 changes: 4 additions & 18 deletions python/ql/src/semmle/python/web/pyramid/Request.qll
Original file line number Diff line number Diff line change
@@ -1,39 +1,25 @@
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
this.(ControlFlowNode).getNode() = view_func.getArg(0)
)
}

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" }
}

24 changes: 7 additions & 17 deletions python/ql/src/semmle/python/web/pyramid/Response.qll
Original file line number Diff line number Diff line change
@@ -1,41 +1,32 @@
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
this = view.getAReturnedNode()
)
}

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")
)
}

Expand All @@ -44,5 +35,4 @@ class PyramidCookieSet extends CookieSet, CallNode {
override ControlFlowNode getKey() { result = this.getArg(0) }

override ControlFlowNode getValue() { result = this.getArg(1) }

}
11 changes: 3 additions & 8 deletions python/ql/src/semmle/python/web/pyramid/View.qll
Original file line number Diff line number Diff line change
@@ -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()
}

3 changes: 3 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Routing.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| test.py:7 | Function home |
| test.py:15 | Function greet |
| test.py:24 | Function stuff |
9 changes: 9 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Routing.ql
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 3 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Sinks.expected
Original file line number Diff line number Diff line change
@@ -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 |
11 changes: 11 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Sinks.ql
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Sources.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| test.py:7 | request | pyramid.request |
| test.py:15 | request | pyramid.request |
| test.py:24 | request | pyramid.request |
11 changes: 11 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Sources.ql
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Taint.expected
Original file line number Diff line number Diff line change
@@ -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 |
12 changes: 12 additions & 0 deletions python/ql/test/library-tests/web/pyramid/Taint.ql
Original file line number Diff line number Diff line change
@@ -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()

2 changes: 2 additions & 0 deletions python/ql/test/library-tests/web/pyramid/options
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/
optimize: true
25 changes: 25 additions & 0 deletions python/ql/test/library-tests/web/pyramid/test.py
Original file line number Diff line number Diff line change
@@ -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}
Empty file.
2 changes: 2 additions & 0 deletions python/ql/test/query-tests/Security/lib/pyramid/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Response(object):
pass
7 changes: 7 additions & 0 deletions python/ql/test/query-tests/Security/lib/pyramid/view.py
Original file line number Diff line number Diff line change
@@ -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