Skip to content

Commit 57e13c6

Browse files
committed
Python: rest_framework.decorators.api_view handling
Had to expose even more things, and had to make the `DjangoRouteHandler` modeling more flexible so I could extend the char-pred in a different file.
1 parent 222db37 commit 57e13c6

File tree

3 files changed

+61
-10
lines changed

3 files changed

+61
-10
lines changed

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,13 +1898,7 @@ module PrivateDjango {
18981898
*
18991899
* Most functions take a django HttpRequest as a parameter (but not all).
19001900
*/
1901-
private class DjangoRouteHandler extends Function {
1902-
DjangoRouteHandler() {
1903-
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
1904-
or
1905-
any(DjangoViewClass vc).getARequestHandler() = this
1906-
}
1907-
1901+
class DjangoRouteHandler extends Function instanceof DjangoRouteHandler::Range {
19081902
/**
19091903
* Gets the index of the parameter where the first routed parameter can be passed --
19101904
* that is, the one just after any possible `self` or HttpRequest parameters.
@@ -1924,6 +1918,18 @@ module PrivateDjango {
19241918
Parameter getRequestParam() { result = this.getArg(this.getRequestParamIndex()) }
19251919
}
19261920

1921+
module DjangoRouteHandler {
1922+
abstract class Range extends Function { }
1923+
1924+
class StandardDjangoRouteHandlers extends Range {
1925+
StandardDjangoRouteHandlers() {
1926+
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
1927+
or
1928+
any(DjangoViewClass vc).getARequestHandler() = this
1929+
}
1930+
}
1931+
}
1932+
19271933
/**
19281934
* A method named `get_redirect_url` on a django view class.
19291935
*
@@ -1945,7 +1951,7 @@ module PrivateDjango {
19451951
}
19461952

19471953
/** A data-flow node that sets up a route on a server, using the django framework. */
1948-
abstract private class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
1954+
abstract class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
19491955
/** Gets the data-flow node that is used as the argument for the view handler. */
19501956
abstract DataFlow::Node getViewArg();
19511957

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ private import semmle.python.frameworks.Stdlib
2828
* - https://pypi.org/project/djangorestframework/
2929
*/
3030
private module RestFramework {
31+
// ---------------------------------------------------------------------------
32+
// rest_framework.views.APIView handling
33+
// ---------------------------------------------------------------------------
3134
/**
3235
* An `API::Node` representing the `rest_framework.views.APIView` class or any subclass
3336
* that has explicitly been modeled in the CodeQL libraries.
@@ -65,4 +68,43 @@ private module RestFramework {
6568
]
6669
}
6770
}
71+
72+
// ---------------------------------------------------------------------------
73+
// rest_framework.decorators.api_view handling
74+
// ---------------------------------------------------------------------------
75+
/**
76+
* A function that is a request handler since it is decorated with `rest_framework.decorators.api_view`
77+
*/
78+
class RestFrameworkFunctionBasedView extends PrivateDjango::DjangoRouteHandler::Range {
79+
RestFrameworkFunctionBasedView() {
80+
this.getADecorator() =
81+
API::moduleImport("rest_framework")
82+
.getMember("decorators")
83+
.getMember("api_view")
84+
.getACall()
85+
.asExpr()
86+
}
87+
}
88+
89+
/**
90+
* Ensuring that all `RestFrameworkFunctionBasedView` are also marked as a
91+
* `HTTP::Server::RequestHandler`. We only need this for the ones that doesn't have a
92+
* known route setup.
93+
*/
94+
class RestFrameworkFunctionBasedViewWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
95+
PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView {
96+
RestFrameworkFunctionBasedViewWithoutKnownRoute() {
97+
not exists(PrivateDjango::DjangoRouteSetup setup | setup.getARequestHandler() = this)
98+
}
99+
100+
override Parameter getARoutedParameter() {
101+
// Since we don't know the URL pattern, we simply mark all parameters as a routed
102+
// parameter. This should give us more RemoteFlowSources but could also lead to
103+
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
104+
result in [this.getArg(_), this.getArgByName(_)] and
105+
not result = any(int i | i < this.getFirstPossibleRoutedParamIndex() | this.getArg(i))
106+
}
107+
108+
override string getFramework() { result = "Django (rest_framework)" }
109+
}
68110
}

python/ql/test/library-tests/frameworks/rest_framework/taint_test.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,11 @@ def get(self, request: Request, routed_param): # $ requestHandler routedParamete
119119
# framework
120120

121121
@api_view(["POST"])
122-
def function_based_no_route(request: Request, possible_routed_param):
123-
ensure_tainted(request, possible_routed_param) # $ MISSING: tainted
122+
def function_based_no_route(request: Request, possible_routed_param): # $ requestHandler routedParameter=possible_routed_param
123+
ensure_tainted(
124+
request, # $ MISSING: tainted
125+
possible_routed_param, # $ tainted
126+
)
124127

125128

126129
class ClassBasedNoRoute(APIView):

0 commit comments

Comments
 (0)