Skip to content

Commit 5d77e62

Browse files
committed
Python: Add basic rest_framework Request modeling
1 parent 57e13c6 commit 5d77e62

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,69 @@ private module RestFramework {
107107

108108
override string getFramework() { result = "Django (rest_framework)" }
109109
}
110+
111+
// ---------------------------------------------------------------------------
112+
// request modeling
113+
// ---------------------------------------------------------------------------
114+
/**
115+
* A parameter that will receive a `rest_framework.request.Request` instance when a
116+
* request handler is invoked.
117+
*/
118+
private class RestFrameworkRequestHandlerRequestParam extends Request::InstanceSource,
119+
RemoteFlowSource::Range, DataFlow::ParameterNode {
120+
RestFrameworkRequestHandlerRequestParam() {
121+
// rest_framework.views.APIView subclass
122+
exists(RestFrameworkApiViewClass vc |
123+
this.getParameter() =
124+
vc.getARequestHandler().(PrivateDjango::DjangoRouteHandler).getRequestParam()
125+
)
126+
or
127+
// annotated with @api_view decorator
128+
exists(PrivateDjango::DjangoRouteHandler rh | rh instanceof RestFrameworkFunctionBasedView |
129+
this.getParameter() = rh.getRequestParam()
130+
)
131+
}
132+
133+
override string getSourceType() { result = "rest_framework.request.HttpRequest" }
134+
}
135+
136+
/**
137+
* Provides models for the `rest_framework.request.Request` class
138+
*
139+
* See https://www.django-rest-framework.org/api-guide/requests/.
140+
*/
141+
module Request {
142+
/** Gets a reference to the `rest_framework.request.Request` class. */
143+
private API::Node classRef() {
144+
result = API::moduleImport("rest_framework").getMember("request").getMember("Request")
145+
}
146+
147+
/**
148+
* A source of instances of `rest_framework.request.Request`, extend this class to model new instances.
149+
*
150+
* This can include instantiations of the class, return values from function
151+
* calls, or a special parameter that will be set when functions are called by an external
152+
* library.
153+
*
154+
* Use the predicate `Request::instance()` to get references to instances of `rest_framework.request.Request`.
155+
*/
156+
abstract class InstanceSource extends PrivateDjango::django::http::request::HttpRequest::InstanceSource {
157+
}
158+
159+
/** A direct instantiation of `rest_framework.request.Request`. */
160+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
161+
ClassInstantiation() { this = classRef().getACall() }
162+
}
163+
164+
/** Gets a reference to an instance of `rest_framework.request.Request`. */
165+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
166+
t.start() and
167+
result instanceof InstanceSource
168+
or
169+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
170+
}
171+
172+
/** Gets a reference to an instance of `rest_framework.request.Request`. */
173+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
174+
}
110175
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_taint(request: Request, routed_param): # $ requestHandler routedParamet
2121

2222
# Has all the standard attributes of a django HttpRequest
2323
# see https://github.com/encode/django-rest-framework/blob/00cd4ef864a8bf6d6c90819a983017070f9f08a5/rest_framework/request.py#L410-L418
24-
ensure_tainted(request.resolve_match.args) # $ MISSING: tainted
24+
ensure_tainted(request.resolver_match.args) # $ tainted
2525

2626
# special new attributes added, see https://www.django-rest-framework.org/api-guide/requests/
2727
ensure_tainted(
@@ -121,7 +121,7 @@ def get(self, request: Request, routed_param): # $ requestHandler routedParamete
121121
@api_view(["POST"])
122122
def function_based_no_route(request: Request, possible_routed_param): # $ requestHandler routedParameter=possible_routed_param
123123
ensure_tainted(
124-
request, # $ MISSING: tainted
124+
request, # $ tainted
125125
possible_routed_param, # $ tainted
126126
)
127127

0 commit comments

Comments
 (0)