mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #2858 from RasmusWL/python-support-django2
Approved by tausbn
This commit is contained in:
@@ -4,6 +4,8 @@ The following changes in version 1.24 affect Python analysis in all applications
|
|||||||
|
|
||||||
## General improvements
|
## General improvements
|
||||||
|
|
||||||
|
Support for Django version 2.x and 3.x
|
||||||
|
|
||||||
## New queries
|
## New queries
|
||||||
|
|
||||||
| **Query** | **Tags** | **Purpose** |
|
| **Query** | **Tags** | **Purpose** |
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class BottleRoute extends ControlFlowNode {
|
|||||||
|
|
||||||
Function getFunction() { bottle_route(this, _, result) }
|
Function getFunction() { bottle_route(this, _, result) }
|
||||||
|
|
||||||
Parameter getNamedArgument() {
|
Parameter getANamedArgument() {
|
||||||
exists(string name, Function func |
|
exists(string name, Function func |
|
||||||
func = this.getFunction() and
|
func = this.getFunction() and
|
||||||
func.getArgByName(name) = result and
|
func.getArgByName(name) = result and
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class UntrustedFile extends TaintKind {
|
|||||||
/** Parameter to a bottle request handler function */
|
/** Parameter to a bottle request handler function */
|
||||||
class BottleRequestParameter extends HttpRequestTaintSource {
|
class BottleRequestParameter extends HttpRequestTaintSource {
|
||||||
BottleRequestParameter() {
|
BottleRequestParameter() {
|
||||||
exists(BottleRoute route | route.getNamedArgument() = this.(ControlFlowNode).getNode())
|
exists(BottleRoute route | route.getANamedArgument() = this.(ControlFlowNode).getNode())
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSourceOf(TaintKind kind) { kind instanceof UntrustedStringKind }
|
override predicate isSourceOf(TaintKind kind) { kind instanceof UntrustedStringKind }
|
||||||
|
|||||||
@@ -2,39 +2,142 @@ import python
|
|||||||
import semmle.python.regex
|
import semmle.python.regex
|
||||||
import semmle.python.web.Http
|
import semmle.python.web.Http
|
||||||
|
|
||||||
predicate django_route(CallNode call, ControlFlowNode regex, FunctionValue view) {
|
// TODO: Since django uses `path = partial(...)`, our analysis doesn't understand this is
|
||||||
exists(FunctionValue url |
|
// a FunctionValue, so we can't use `FunctionValue.getArgumentForCall`
|
||||||
Value::named("django.conf.urls.url") = url and
|
// https://github.com/django/django/blob/master/django/urls/conf.py#L76
|
||||||
url.getArgumentForCall(call, 0) = regex and
|
abstract class DjangoRoute extends CallNode {
|
||||||
url.getArgumentForCall(call, 1).pointsTo(view)
|
DjangoViewHandler getViewHandler() {
|
||||||
)
|
result = view_handler_from_view_arg(this.getArg(1))
|
||||||
|
or
|
||||||
|
result = view_handler_from_view_arg(this.getArgByName("view"))
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract string getANamedArgument();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of positional arguments that will be passed to the view.
|
||||||
|
* Will only return a result if there are no named arguments.
|
||||||
|
*/
|
||||||
|
abstract int getNumPositionalArguments();
|
||||||
}
|
}
|
||||||
|
|
||||||
class DjangoRouteRegex extends RegexString {
|
/**
|
||||||
DjangoRouteRegex() { django_route(_, this.getAFlowNode(), _) }
|
* For function based views -- also see `DjangoClassBasedViewHandler`
|
||||||
|
* https://docs.djangoproject.com/en/1.11/topics/http/views/
|
||||||
|
* https://docs.djangoproject.com/en/3.0/topics/http/views/
|
||||||
|
*/
|
||||||
|
class DjangoViewHandler extends PythonFunctionValue {
|
||||||
|
|
||||||
|
/** Gets the index of the 'request' argument */
|
||||||
|
int getRequestArgIndex() {
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DjangoRoute extends CallNode {
|
/**
|
||||||
DjangoRoute() { django_route(this, _, _) }
|
* Class based views
|
||||||
|
* https://docs.djangoproject.com/en/1.11/topics/class-based-views/
|
||||||
|
* https://docs.djangoproject.com/en/3.0/topics/class-based-views/
|
||||||
|
*/
|
||||||
|
private class DjangoViewClass extends ClassValue {
|
||||||
|
DjangoViewClass() {
|
||||||
|
Value::named("django.views.generic.View") = this.getASuperType()
|
||||||
|
or
|
||||||
|
Value::named("django.views.View") = this.getASuperType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FunctionValue getViewFunction() { django_route(this, _, result) }
|
class DjangoClassBasedViewHandler extends DjangoViewHandler {
|
||||||
|
DjangoClassBasedViewHandler() {
|
||||||
string getNamedArgument() {
|
exists(DjangoViewClass cls |
|
||||||
exists(DjangoRouteRegex regex |
|
cls.lookup(httpVerbLower()) = this
|
||||||
django_route(this, regex.getAFlowNode(), _) and
|
|
||||||
regex.getGroupName(_, _) = result
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override int getRequestArgIndex() {
|
||||||
* Get the number of positional arguments that will be passed to the view.
|
// due to `self` being the first parameter
|
||||||
* Will only return a result if there are no named arguments.
|
result = 1
|
||||||
*/
|
}
|
||||||
int getNumPositionalArguments() {
|
}
|
||||||
exists(DjangoRouteRegex regex |
|
|
||||||
django_route(this, regex.getAFlowNode(), _) and
|
/**
|
||||||
not exists(string s | s = regex.getGroupName(_, _)) and
|
* Gets the function that will handle requests when `view_arg` is used as the view argument to a
|
||||||
|
* django route. That is, this methods handles Class-based Views and its `as_view()` function.
|
||||||
|
*/
|
||||||
|
private DjangoViewHandler view_handler_from_view_arg(ControlFlowNode view_arg) {
|
||||||
|
// Function-based view
|
||||||
|
result = view_arg.pointsTo()
|
||||||
|
or
|
||||||
|
// Class-based view
|
||||||
|
exists(ClassValue cls |
|
||||||
|
cls = view_arg.(CallNode).getFunction().(AttrNode).getObject("as_view").pointsTo() and
|
||||||
|
result = cls.lookup(httpVerbLower())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need this "dummy" class, since otherwise the regex argument would not be considered
|
||||||
|
// a regex (RegexString is abstract)
|
||||||
|
class DjangoRouteRegex extends RegexString {
|
||||||
|
DjangoRouteRegex() { exists(DjangoRegexRoute route | route.getRouteArg() = this.getAFlowNode()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DjangoRegexRoute extends DjangoRoute {
|
||||||
|
ControlFlowNode route;
|
||||||
|
|
||||||
|
DjangoRegexRoute() {
|
||||||
|
exists(FunctionValue route_maker |
|
||||||
|
// Django 1.x: https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url
|
||||||
|
Value::named("django.conf.urls.url") = route_maker and
|
||||||
|
route_maker.getArgumentForCall(this, 0) = route
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Django 2.x and 3.x: https://docs.djangoproject.com/en/3.0/ref/urls/#re-path
|
||||||
|
this = Value::named("django.urls.re_path").getACall() and
|
||||||
|
(
|
||||||
|
route = this.getArg(0)
|
||||||
|
or
|
||||||
|
route = this.getArgByName("route")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlFlowNode getRouteArg() { result = route }
|
||||||
|
|
||||||
|
override string getANamedArgument() {
|
||||||
|
exists(DjangoRouteRegex regex | regex.getAFlowNode() = route |
|
||||||
|
result = regex.getGroupName(_, _)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override int getNumPositionalArguments() {
|
||||||
|
not exists(this.getANamedArgument()) and
|
||||||
|
exists(DjangoRouteRegex regex | regex.getAFlowNode() = route |
|
||||||
result = count(regex.getGroupNumber(_, _))
|
result = count(regex.getGroupNumber(_, _))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DjangoPathRoute extends DjangoRoute {
|
||||||
|
ControlFlowNode route;
|
||||||
|
|
||||||
|
DjangoPathRoute() {
|
||||||
|
// Django 2.x and 3.x: https://docs.djangoproject.com/en/3.0/ref/urls/#path
|
||||||
|
this = Value::named("django.urls.path").getACall() and
|
||||||
|
(
|
||||||
|
route = this.getArg(0)
|
||||||
|
or
|
||||||
|
route = this.getArgByName("route")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getANamedArgument() {
|
||||||
|
// regexp taken from django:
|
||||||
|
// https://github.com/django/django/blob/7d1bf29977bb368d7c28e7c6eb146db3b3009ae7/django/urls/resolvers.py#L199
|
||||||
|
exists(StrConst route_str, string match |
|
||||||
|
route_str = route.getNode() and
|
||||||
|
match = route_str.getText().regexpFind("<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>", _, _) and
|
||||||
|
result = match.regexpCapture("<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>", 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override int getNumPositionalArguments() { none() }
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ class DjangoRedirect extends HttpRedirectTaintSink {
|
|||||||
override string toString() { result = "django.redirect" }
|
override string toString() { result = "django.redirect" }
|
||||||
|
|
||||||
DjangoRedirect() {
|
DjangoRedirect() {
|
||||||
exists(CallNode call |
|
this = redirect().getACall().getAnArg()
|
||||||
redirect().getACall() = call and
|
|
||||||
this = call.getAnArg()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,54 +39,35 @@ class DjangoQueryDict extends TaintKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DjangoRequestSource extends HttpRequestTaintSource {
|
/** A Django request parameter */
|
||||||
|
class DjangoRequestSource extends HttpRequestTaintSource {
|
||||||
|
DjangoRequestSource() {
|
||||||
|
exists(DjangoRoute route, DjangoViewHandler view, int request_arg_index |
|
||||||
|
route.getViewHandler() = view and
|
||||||
|
request_arg_index = view.getRequestArgIndex() and
|
||||||
|
this = view.getScope().getArg(request_arg_index).asName().getAFlowNode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override string toString() { result = "Django request source" }
|
override string toString() { result = "Django request source" }
|
||||||
|
|
||||||
override predicate isSourceOf(TaintKind kind) { kind instanceof DjangoRequest }
|
override predicate isSourceOf(TaintKind kind) { kind instanceof DjangoRequest }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function based views
|
|
||||||
* https://docs.djangoproject.com/en/1.11/topics/http/views/
|
|
||||||
*/
|
|
||||||
private class DjangoFunctionBasedViewRequestArgument extends DjangoRequestSource {
|
|
||||||
DjangoFunctionBasedViewRequestArgument() {
|
|
||||||
exists(FunctionValue view |
|
|
||||||
django_route(_, _, view) and
|
|
||||||
this = view.getScope().getArg(0).asName().getAFlowNode()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class based views
|
|
||||||
* https://docs.djangoproject.com/en/1.11/topics/class-based-views/
|
|
||||||
*/
|
|
||||||
private class DjangoView extends ClassValue {
|
|
||||||
DjangoView() { Value::named("django.views.generic.View") = this.getASuperType() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private FunctionValue djangoViewHttpMethod() {
|
|
||||||
exists(DjangoView view | view.lookup(httpVerbLower()) = result)
|
|
||||||
}
|
|
||||||
|
|
||||||
class DjangoClassBasedViewRequestArgument extends DjangoRequestSource {
|
|
||||||
DjangoClassBasedViewRequestArgument() {
|
|
||||||
this = djangoViewHttpMethod().getScope().getArg(1).asName().getAFlowNode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An argument specified in a url routing table */
|
/** An argument specified in a url routing table */
|
||||||
class DjangoRequestParameter extends HttpRequestTaintSource {
|
class DjangoRequestParameter extends HttpRequestTaintSource {
|
||||||
DjangoRequestParameter() {
|
DjangoRequestParameter() {
|
||||||
exists(DjangoRoute route, Function f |
|
exists(DjangoRoute route, Function f, DjangoViewHandler view, int request_arg_index |
|
||||||
f = route.getViewFunction().getScope() |
|
route.getViewHandler() = view and
|
||||||
this.(ControlFlowNode).getNode() = f.getArgByName(route.getNamedArgument())
|
request_arg_index = view.getRequestArgIndex() and
|
||||||
|
f = view.getScope()
|
||||||
|
|
|
||||||
|
this.(ControlFlowNode).getNode() = f.getArgByName(route.getANamedArgument())
|
||||||
or
|
or
|
||||||
exists(int i | i >= 0 |
|
exists(int i | i >= 0 |
|
||||||
i < route.getNumPositionalArguments() and
|
i < route.getNumPositionalArguments() and
|
||||||
// +1 because first argument is always the request
|
// +1 because first argument is always the request
|
||||||
this.(ControlFlowNode).getNode() = f.getArg(i+1)
|
this.(ControlFlowNode).getNode() = f.getArg(request_arg_index + 1 + i)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,15 @@ class DjangoResponse extends TaintKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ClassValue theDjangoHttpResponseClass() {
|
private ClassValue theDjangoHttpResponseClass() {
|
||||||
result = Value::named("django.http.response.HttpResponse") and
|
(
|
||||||
|
// version 1.x
|
||||||
|
result = Value::named("django.http.response.HttpResponse")
|
||||||
|
or
|
||||||
|
// version 2.x
|
||||||
|
// https://docs.djangoproject.com/en/2.2/ref/request-response/#httpresponse-objects
|
||||||
|
result = Value::named("django.http.HttpResponse")
|
||||||
|
) and
|
||||||
|
// TODO: does this do anything? when could they be the same???
|
||||||
not result = theDjangoHttpRedirectClass()
|
not result = theDjangoHttpRedirectClass()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,9 @@ import python
|
|||||||
FunctionValue redirect() { result = Value::named("django.shortcuts.redirect") }
|
FunctionValue redirect() { result = Value::named("django.shortcuts.redirect") }
|
||||||
|
|
||||||
ClassValue theDjangoHttpRedirectClass() {
|
ClassValue theDjangoHttpRedirectClass() {
|
||||||
|
// version 1.x
|
||||||
result = Value::named("django.http.response.HttpResponseRedirectBase")
|
result = Value::named("django.http.response.HttpResponseRedirectBase")
|
||||||
|
or
|
||||||
|
// version 2.x
|
||||||
|
result = Value::named("django.http.HttpResponseRedirectBase")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
| test_1x.py:13:21:13:24 | django.redirect | externally controlled string |
|
||||||
|
| test_2x_3x.py:13:21:13:24 | django.redirect | externally controlled string |
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import python
|
||||||
|
import semmle.python.web.HttpRedirect
|
||||||
|
import semmle.python.security.strings.Untrusted
|
||||||
|
|
||||||
|
from HttpRedirectTaintSink sink, TaintKind kind
|
||||||
|
where sink.sinks(kind)
|
||||||
|
select sink, kind
|
||||||
@@ -1,7 +1,23 @@
|
|||||||
| views.py:7:25:7:63 | django.Response(...) | externally controlled string |
|
| views_1x.py:8:25:8:63 | django.Response(...) | externally controlled string |
|
||||||
| views.py:11:25:11:52 | django.Response(...) | externally controlled string |
|
| views_1x.py:12:25:12:52 | django.Response(...) | externally controlled string |
|
||||||
| views.py:15:25:15:53 | django.Response(...) | externally controlled string |
|
| views_1x.py:16:25:16:53 | django.Response(...) | externally controlled string |
|
||||||
| views.py:23:29:23:60 | django.Response(...) | externally controlled string |
|
| views_1x.py:21:15:21:42 | django.Response.write(...) | externally controlled string |
|
||||||
| views.py:29:29:29:65 | django.Response(...) | externally controlled string |
|
| views_1x.py:30:29:30:60 | django.Response(...) | externally controlled string |
|
||||||
| views.py:34:25:34:63 | django.Response(...) | externally controlled string |
|
| views_1x.py:36:29:36:65 | django.Response(...) | externally controlled string |
|
||||||
| views.py:38:25:38:70 | django.Response(...) | externally controlled string |
|
| views_1x.py:41:25:41:63 | django.Response(...) | externally controlled string |
|
||||||
|
| views_1x.py:45:25:45:70 | django.Response(...) | externally controlled string |
|
||||||
|
| views_1x.py:66:25:66:55 | django.Response(...) | externally controlled string |
|
||||||
|
| views_1x.py:75:25:75:33 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:8:25:8:63 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:12:25:12:52 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:16:25:16:53 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:21:15:21:42 | django.Response.write(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:30:29:30:60 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:36:29:36:65 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:41:25:41:63 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:45:25:45:70 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:66:25:66:40 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:79:25:79:61 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:82:25:82:69 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:85:25:85:64 | django.Response(...) | externally controlled string |
|
||||||
|
| views_2x_3x.py:88:25:88:32 | django.Response(...) | externally controlled string |
|
||||||
|
|||||||
@@ -1,19 +1,51 @@
|
|||||||
| test.py:5:19:5:25 | request | django.request.HttpRequest |
|
| test_1x.py:6:19:6:25 | request | django.request.HttpRequest |
|
||||||
| test.py:5:28:5:31 | path | externally controlled string |
|
| test_1x.py:6:28:6:31 | path | externally controlled string |
|
||||||
| test.py:11:19:11:25 | request | django.request.HttpRequest |
|
| test_1x.py:12:19:12:25 | request | django.request.HttpRequest |
|
||||||
| test.py:11:28:11:31 | path | externally controlled string |
|
| test_1x.py:12:28:12:31 | path | externally controlled string |
|
||||||
| views.py:6:19:6:25 | request | django.request.HttpRequest |
|
| test_2x_3x.py:6:19:6:25 | request | django.request.HttpRequest |
|
||||||
| views.py:6:28:6:30 | foo | externally controlled string |
|
| test_2x_3x.py:6:28:6:31 | path | externally controlled string |
|
||||||
| views.py:6:33:6:35 | bar | externally controlled string |
|
| test_2x_3x.py:12:19:12:25 | request | django.request.HttpRequest |
|
||||||
| views.py:10:20:10:26 | request | django.request.HttpRequest |
|
| test_2x_3x.py:12:28:12:31 | path | externally controlled string |
|
||||||
| views.py:14:21:14:27 | request | django.request.HttpRequest |
|
| views_1x.py:7:19:7:25 | request | django.request.HttpRequest |
|
||||||
| views.py:22:20:22:26 | request | django.request.HttpRequest |
|
| views_1x.py:7:28:7:30 | foo | externally controlled string |
|
||||||
| views.py:28:19:28:25 | request | django.request.HttpRequest |
|
| views_1x.py:7:33:7:35 | bar | externally controlled string |
|
||||||
| views.py:32:19:32:25 | request | django.request.HttpRequest |
|
| views_1x.py:11:20:11:26 | request | django.request.HttpRequest |
|
||||||
| views.py:32:28:32:38 | page_number | externally controlled string |
|
| views_1x.py:15:21:15:27 | request | django.request.HttpRequest |
|
||||||
| views.py:37:24:37:30 | request | django.request.HttpRequest |
|
| views_1x.py:19:21:19:27 | request | django.request.HttpRequest |
|
||||||
| views.py:37:33:37:36 | arg0 | externally controlled string |
|
| views_1x.py:29:20:29:26 | request | django.request.HttpRequest |
|
||||||
| views.py:37:39:37:42 | arg1 | externally controlled string |
|
| views_1x.py:29:29:29:37 | untrusted | externally controlled string |
|
||||||
| views.py:57:15:57:21 | request | django.request.HttpRequest |
|
| views_1x.py:35:19:35:25 | request | django.request.HttpRequest |
|
||||||
| views.py:57:24:57:31 | username | externally controlled string |
|
| views_1x.py:35:28:35:36 | untrusted | externally controlled string |
|
||||||
| views.py:66:30:66:36 | request | django.request.HttpRequest |
|
| views_1x.py:39:19:39:25 | request | django.request.HttpRequest |
|
||||||
|
| views_1x.py:39:28:39:38 | page_number | externally controlled string |
|
||||||
|
| views_1x.py:44:24:44:30 | request | django.request.HttpRequest |
|
||||||
|
| views_1x.py:44:33:44:36 | arg0 | externally controlled string |
|
||||||
|
| views_1x.py:44:39:44:42 | arg1 | externally controlled string |
|
||||||
|
| views_1x.py:65:15:65:21 | request | django.request.HttpRequest |
|
||||||
|
| views_1x.py:65:24:65:31 | username | externally controlled string |
|
||||||
|
| views_1x.py:74:13:74:19 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:7:19:7:25 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:7:28:7:30 | foo | externally controlled string |
|
||||||
|
| views_2x_3x.py:7:33:7:35 | bar | externally controlled string |
|
||||||
|
| views_2x_3x.py:11:20:11:26 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:15:21:15:27 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:19:21:19:27 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:29:20:29:26 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:29:29:29:37 | untrusted | externally controlled string |
|
||||||
|
| views_2x_3x.py:35:19:35:25 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:35:28:35:36 | untrusted | externally controlled string |
|
||||||
|
| views_2x_3x.py:39:19:39:25 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:39:28:39:38 | page_number | externally controlled string |
|
||||||
|
| views_2x_3x.py:44:24:44:30 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:44:33:44:36 | arg0 | externally controlled string |
|
||||||
|
| views_2x_3x.py:44:39:44:42 | arg1 | externally controlled string |
|
||||||
|
| views_2x_3x.py:78:17:78:23 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:78:26:78:36 | page_number | externally controlled string |
|
||||||
|
| views_2x_3x.py:81:17:81:23 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:81:26:81:28 | foo | externally controlled string |
|
||||||
|
| views_2x_3x.py:81:31:81:33 | bar | externally controlled string |
|
||||||
|
| views_2x_3x.py:81:36:81:38 | baz | externally controlled string |
|
||||||
|
| views_2x_3x.py:84:17:84:23 | request | django.request.HttpRequest |
|
||||||
|
| views_2x_3x.py:84:26:84:28 | foo | externally controlled string |
|
||||||
|
| views_2x_3x.py:84:31:84:33 | bar | externally controlled string |
|
||||||
|
| views_2x_3x.py:87:26:87:32 | request | django.request.HttpRequest |
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"""tests for Django 1.x"""
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
19
python/ql/test/library-tests/web/django/test_2x_3x.py
Normal file
19
python/ql/test/library-tests/web/django/test_2x_3x.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"""tests for Django 2.x and 3.x"""
|
||||||
|
from django.urls import path
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
|
||||||
|
|
||||||
|
def with_template(request, path='default'):
|
||||||
|
env = {'path': path}
|
||||||
|
# We would need to understand django templates to know if this is safe or not
|
||||||
|
return render(request, 'possibly-vulnerable-template.html', env)
|
||||||
|
|
||||||
|
|
||||||
|
def vuln_redirect(request, path):
|
||||||
|
return redirect(path)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('/<path>', with_template),
|
||||||
|
path('/redirect/<path>', vuln_redirect),
|
||||||
|
]
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"""test of views for Django 1.x"""
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
@@ -15,16 +16,22 @@ def post_params_xss(request):
|
|||||||
return HttpResponse(request.POST.get("untrusted"))
|
return HttpResponse(request.POST.get("untrusted"))
|
||||||
|
|
||||||
|
|
||||||
|
def http_resp_write(request):
|
||||||
|
rsp = HttpResponse()
|
||||||
|
rsp.write(request.GET.get("untrusted"))
|
||||||
|
return rsp
|
||||||
|
|
||||||
|
|
||||||
class Foo(object):
|
class Foo(object):
|
||||||
# Note: since Foo is used as the super type in a class view, it will be able to handle requests.
|
# Note: since Foo is used as the super type in a class view, it will be able to handle requests.
|
||||||
|
|
||||||
# TODO: Currently we don't flag `untrusted` as a DjangoRequestParameter
|
|
||||||
def post(self, request, untrusted):
|
def post(self, request, untrusted):
|
||||||
return HttpResponse('Foo post: {}'.format(untrusted))
|
return HttpResponse('Foo post: {}'.format(untrusted))
|
||||||
|
|
||||||
|
|
||||||
class ClassView(View, Foo):
|
class ClassView(View, Foo):
|
||||||
# TODO: Currently we don't flag `untrusted` as a DjangoRequestParameter
|
|
||||||
def get(self, request, untrusted):
|
def get(self, request, untrusted):
|
||||||
return HttpResponse('ClassView get: {}'.format(untrusted))
|
return HttpResponse('ClassView get: {}'.format(untrusted))
|
||||||
|
|
||||||
@@ -42,6 +49,7 @@ urlpatterns = [
|
|||||||
url(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss),
|
url(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss),
|
||||||
url(r'^get_params$', get_params_xss),
|
url(r'^get_params$', get_params_xss),
|
||||||
url(r'^post_params$', post_params_xss),
|
url(r'^post_params$', post_params_xss),
|
||||||
|
url(r'^http_resp_write$', http_resp_write),
|
||||||
url(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()),
|
url(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()),
|
||||||
|
|
||||||
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
|
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
|
||||||
@@ -51,22 +59,21 @@ urlpatterns = [
|
|||||||
url(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'),
|
url(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
################################################################################
|
||||||
# Using patterns() for routing
|
# Using patterns() for routing
|
||||||
|
|
||||||
def show_user(request, username):
|
def show_user(request, username):
|
||||||
pass
|
return HttpResponse('show_user {}'.format(username))
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(url(r'^users/(?P<username>[^/]+)$', show_user))
|
urlpatterns = patterns(url(r'^users/(?P<username>[^/]+)$', show_user))
|
||||||
|
|
||||||
|
################################################################################
|
||||||
# Show we understand the keyword arguments to django.conf.urls.url
|
# Show we understand the keyword arguments to django.conf.urls.url
|
||||||
|
|
||||||
def we_understand_url_kwargs(request):
|
def kw_args(request):
|
||||||
pass
|
return HttpResponse('kw_args')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(view=we_understand_url_kwargs, regex=r'^specifying-as-kwargs-is-not-a-problem$')
|
url(view=kw_args, regex=r'^kw_args$')
|
||||||
]
|
]
|
||||||
122
python/ql/test/library-tests/web/django/views_2x_3x.py
Normal file
122
python/ql/test/library-tests/web/django/views_2x_3x.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"""testing views for Django 2.x and 3.x"""
|
||||||
|
from django.urls import path, re_path
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
|
||||||
|
def url_match_xss(request, foo, bar, no_taint=None):
|
||||||
|
return HttpResponse('url_match_xss: {} {}'.format(foo, bar))
|
||||||
|
|
||||||
|
|
||||||
|
def get_params_xss(request):
|
||||||
|
return HttpResponse(request.GET.get("untrusted"))
|
||||||
|
|
||||||
|
|
||||||
|
def post_params_xss(request):
|
||||||
|
return HttpResponse(request.POST.get("untrusted"))
|
||||||
|
|
||||||
|
|
||||||
|
def http_resp_write(request):
|
||||||
|
rsp = HttpResponse()
|
||||||
|
rsp.write(request.GET.get("untrusted"))
|
||||||
|
return rsp
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(object):
|
||||||
|
# Note: since Foo is used as the super type in a class view, it will be able to handle requests.
|
||||||
|
|
||||||
|
|
||||||
|
def post(self, request, untrusted):
|
||||||
|
return HttpResponse('Foo post: {}'.format(untrusted))
|
||||||
|
|
||||||
|
|
||||||
|
class ClassView(View, Foo):
|
||||||
|
|
||||||
|
def get(self, request, untrusted):
|
||||||
|
return HttpResponse('ClassView get: {}'.format(untrusted))
|
||||||
|
|
||||||
|
|
||||||
|
def show_articles(request, page_number=1):
|
||||||
|
page_number = int(page_number)
|
||||||
|
return HttpResponse('articles page: {}'.format(page_number))
|
||||||
|
|
||||||
|
|
||||||
|
def xxs_positional_arg(request, arg0, arg1, no_taint=None):
|
||||||
|
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1))
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss),
|
||||||
|
re_path(r'^get_params$', get_params_xss),
|
||||||
|
re_path(r'^post_params$', post_params_xss),
|
||||||
|
re_path(r'^http_resp_write$', http_resp_write),
|
||||||
|
re_path(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()),
|
||||||
|
|
||||||
|
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
|
||||||
|
re_path(r'articles/^(?:page-(?P<page_number>\d+)/)?$', show_articles),
|
||||||
|
# passing as positional argument is not the recommended way of doing things, but it is certainly
|
||||||
|
# possible
|
||||||
|
re_path(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Show we understand the keyword arguments to from django.urls.re_path
|
||||||
|
|
||||||
|
def re_path_kwargs(request):
|
||||||
|
return HttpResponse('re_path_kwargs')
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(view=re_path_kwargs, regex=r'^specifying-as-kwargs-is-not-a-problem$')
|
||||||
|
]
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Using path
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# saying page_number is an externally controlled *string* is a bit strange, when we have an int converter :O
|
||||||
|
def page_number(request, page_number=1):
|
||||||
|
return HttpResponse('page_number: {}'.format(page_number))
|
||||||
|
|
||||||
|
def foo_bar_baz(request, foo, bar, baz):
|
||||||
|
return HttpResponse('foo_bar_baz: {} {} {}'.format(foo, bar, baz))
|
||||||
|
|
||||||
|
def path_kwargs(request, foo, bar):
|
||||||
|
return HttpResponse('path_kwargs: {} {} {}'.format(foo, bar))
|
||||||
|
|
||||||
|
def not_valid_identifier(request):
|
||||||
|
return HttpResponse('<foo!>')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('articles/', page_number),
|
||||||
|
path('articles/page-<int:page_number>', page_number),
|
||||||
|
path('<int:foo>/<str:bar>/<baz>', foo_bar_baz, name='foo-bar-baz'),
|
||||||
|
|
||||||
|
path(view=path_kwargs, route='<foo>/<bar>'),
|
||||||
|
|
||||||
|
# We should not report there is a request parameter called `not_valid!`
|
||||||
|
path('not_valid/<not_valid!>', not_valid_identifier),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# We should abort if a decorator is used. As demonstrated below, anything might happen
|
||||||
|
|
||||||
|
# def reverse_kwargs(f):
|
||||||
|
# @wraps(f)
|
||||||
|
# def f_(*args, **kwargs):
|
||||||
|
# new_kwargs = dict()
|
||||||
|
# for key, value in kwargs.items():
|
||||||
|
# new_kwargs[key[::-1]] = value
|
||||||
|
# return f(*args, **new_kwargs)
|
||||||
|
# return f_
|
||||||
|
|
||||||
|
# @reverse_kwargs
|
||||||
|
# def decorators_can_do_anything(request, oof, foo=None):
|
||||||
|
# return HttpResponse('This is a mess'[::-1])
|
||||||
|
|
||||||
|
# urlpatterns = [
|
||||||
|
# path('rev/<foo>', decorators_can_do_anything),
|
||||||
|
# ]
|
||||||
@@ -2,5 +2,6 @@
|
|||||||
def url(regex, view, kwargs=None, name=None):
|
def url(regex, view, kwargs=None, name=None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def patterns(*urls):
|
def patterns(*urls):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# see https://docs.djangoproject.com/en/1.11/_modules/django/shortcuts/#redirect
|
||||||
|
# https://github.com/django/django/blob/86908785076b2bbc31b908781da6b6ad1779b18b/django/shortcuts.py
|
||||||
|
|
||||||
|
def render(request, template_name, context=None, content_type=None, status=None, using=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def redirect(to, *args, **kwargs):
|
||||||
|
pass
|
||||||
7
python/ql/test/query-tests/Security/lib/django/urls.py
Normal file
7
python/ql/test/query-tests/Security/lib/django/urls.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from functools import partial
|
||||||
|
|
||||||
|
def _path(route, view, kwargs=None, name=None, Pattern=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
path = partial(_path, Pattern='RoutePattern (but this is a mock)')
|
||||||
|
re_path = partial(_path, Pattern='RegexPattern (but this is a mock)')
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# For django 2.x and 3.x
|
||||||
|
class View:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
# For django 1.x
|
||||||
class View:
|
class View:
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user