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.
This commit is contained in:
Rasmus Wriedt Larsen
2021-10-29 14:43:54 +02:00
parent 222db37c0d
commit 57e13c6066
3 changed files with 61 additions and 10 deletions

View File

@@ -1898,13 +1898,7 @@ module PrivateDjango {
*
* Most functions take a django HttpRequest as a parameter (but not all).
*/
private class DjangoRouteHandler extends Function {
DjangoRouteHandler() {
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
or
any(DjangoViewClass vc).getARequestHandler() = this
}
class DjangoRouteHandler extends Function instanceof DjangoRouteHandler::Range {
/**
* Gets the index of the parameter where the first routed parameter can be passed --
* that is, the one just after any possible `self` or HttpRequest parameters.
@@ -1924,6 +1918,18 @@ module PrivateDjango {
Parameter getRequestParam() { result = this.getArg(this.getRequestParamIndex()) }
}
module DjangoRouteHandler {
abstract class Range extends Function { }
class StandardDjangoRouteHandlers extends Range {
StandardDjangoRouteHandlers() {
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
or
any(DjangoViewClass vc).getARequestHandler() = this
}
}
}
/**
* A method named `get_redirect_url` on a django view class.
*
@@ -1945,7 +1951,7 @@ module PrivateDjango {
}
/** A data-flow node that sets up a route on a server, using the django framework. */
abstract private class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
abstract class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
/** Gets the data-flow node that is used as the argument for the view handler. */
abstract DataFlow::Node getViewArg();

View File

@@ -28,6 +28,9 @@ private import semmle.python.frameworks.Stdlib
* - https://pypi.org/project/djangorestframework/
*/
private module RestFramework {
// ---------------------------------------------------------------------------
// rest_framework.views.APIView handling
// ---------------------------------------------------------------------------
/**
* An `API::Node` representing the `rest_framework.views.APIView` class or any subclass
* that has explicitly been modeled in the CodeQL libraries.
@@ -65,4 +68,43 @@ private module RestFramework {
]
}
}
// ---------------------------------------------------------------------------
// rest_framework.decorators.api_view handling
// ---------------------------------------------------------------------------
/**
* A function that is a request handler since it is decorated with `rest_framework.decorators.api_view`
*/
class RestFrameworkFunctionBasedView extends PrivateDjango::DjangoRouteHandler::Range {
RestFrameworkFunctionBasedView() {
this.getADecorator() =
API::moduleImport("rest_framework")
.getMember("decorators")
.getMember("api_view")
.getACall()
.asExpr()
}
}
/**
* Ensuring that all `RestFrameworkFunctionBasedView` are also marked as a
* `HTTP::Server::RequestHandler`. We only need this for the ones that doesn't have a
* known route setup.
*/
class RestFrameworkFunctionBasedViewWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView {
RestFrameworkFunctionBasedViewWithoutKnownRoute() {
not exists(PrivateDjango::DjangoRouteSetup setup | setup.getARequestHandler() = this)
}
override Parameter getARoutedParameter() {
// Since we don't know the URL pattern, we simply mark all parameters as a routed
// parameter. This should give us more RemoteFlowSources but could also lead to
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
result in [this.getArg(_), this.getArgByName(_)] and
not result = any(int i | i < this.getFirstPossibleRoutedParamIndex() | this.getArg(i))
}
override string getFramework() { result = "Django (rest_framework)" }
}
}

View File

@@ -119,8 +119,11 @@ urlpatterns = [
# framework
@api_view(["POST"])
def function_based_no_route(request: Request, possible_routed_param):
ensure_tainted(request, possible_routed_param) # $ MISSING: tainted
def function_based_no_route(request: Request, possible_routed_param): # $ requestHandler routedParameter=possible_routed_param
ensure_tainted(
request, # $ MISSING: tainted
possible_routed_param, # $ tainted
)
class ClassBasedNoRoute(APIView):