mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Merge pull request #4545 from RasmusWL/python-model-django-v1
Approved by tausbn
This commit is contained in:
@@ -34,7 +34,7 @@ private module Django {
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node django_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["db", "urls", "http"] and
|
||||
attr_name in ["db", "urls", "http", "conf"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django" + "." + attr_name)
|
||||
@@ -437,6 +437,55 @@ private module Django {
|
||||
DataFlow::Node re_path() { result = urls_attr("re_path") }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// django.conf
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `django.conf` module. */
|
||||
DataFlow::Node conf() { result = django_attr("conf") }
|
||||
|
||||
/** Provides models for the `django.conf` module */
|
||||
module conf {
|
||||
// -------------------------------------------------------------------------
|
||||
// django.conf.urls
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `django.conf.urls` module. */
|
||||
private DataFlow::Node urls(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django.conf.urls")
|
||||
or
|
||||
t.startInAttr("urls") and
|
||||
result = conf()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = urls(t2).track(t2, t))
|
||||
}
|
||||
|
||||
// NOTE: had to rename due to shadowing rules in QL
|
||||
/** Gets a reference to the `django.conf.urls` module. */
|
||||
DataFlow::Node conf_urls() { result = urls(DataFlow::TypeTracker::end()) }
|
||||
|
||||
// NOTE: had to rename due to shadowing rules in QL
|
||||
/** Provides models for the `django.conf.urls` module */
|
||||
module conf_urls {
|
||||
/** Gets a reference to the `django.conf.urls.url` function. */
|
||||
private DataFlow::Node url(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django.conf.urls.url")
|
||||
or
|
||||
t.startInAttr("url") and
|
||||
result = conf_urls()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = url(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `django.conf.urls.url` function.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url
|
||||
*/
|
||||
DataFlow::Node url() { result = url(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// django.http
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -684,20 +733,48 @@ private module Django {
|
||||
}
|
||||
}
|
||||
|
||||
/** A Django route setup that uses a Regex to specify route (and routed parameters). */
|
||||
abstract private class DjangoRegexRouteSetup extends DjangoRouteSetup {
|
||||
override Parameter getARoutedParameter() {
|
||||
// If 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.
|
||||
exists(DjangoRouteHandler routeHandler | routeHandler = this.getARouteHandler() |
|
||||
not exists(this.getUrlPattern()) and
|
||||
result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and
|
||||
not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i))
|
||||
)
|
||||
or
|
||||
exists(DjangoRouteHandler routeHandler, DjangoRouteRegex regex |
|
||||
routeHandler = this.getARouteHandler() and
|
||||
regex.getRouteSetup() = this
|
||||
|
|
||||
// either using named capture groups (passed as keyword arguments) or using
|
||||
// unnamed capture groups (passed as positional arguments)
|
||||
not exists(regex.getGroupName(_, _)) and
|
||||
// first group will have group number 1
|
||||
result =
|
||||
routeHandler.getArg(routeHandler.getRequestParamIndex() + regex.getGroupNumber(_, _))
|
||||
or
|
||||
result = routeHandler.getArgByName(regex.getGroupName(_, _))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A regex that is used in a call to `django.urls.re_path`.
|
||||
* A regex that is used to set up a route.
|
||||
*
|
||||
* Needs this subclass to be considered a RegexString.
|
||||
*/
|
||||
private class DjangoUrlsRePathRegex extends RegexString {
|
||||
DjangoUrlsRePathCall rePathCall;
|
||||
private class DjangoRouteRegex extends RegexString {
|
||||
DjangoRegexRouteSetup rePathCall;
|
||||
|
||||
DjangoUrlsRePathRegex() {
|
||||
DjangoRouteRegex() {
|
||||
this instanceof StrConst and
|
||||
DataFlow::localFlow(DataFlow::exprNode(this), rePathCall.getUrlPatternArg())
|
||||
}
|
||||
|
||||
DjangoUrlsRePathCall getRePathCall() { result = rePathCall }
|
||||
DjangoRegexRouteSetup getRouteSetup() { result = rePathCall }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -705,7 +782,7 @@ private module Django {
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.0/ref/urls/#re_path
|
||||
*/
|
||||
private class DjangoUrlsRePathCall extends DjangoRouteSetup {
|
||||
private class DjangoUrlsRePathCall extends DjangoRegexRouteSetup {
|
||||
override CallNode node;
|
||||
|
||||
DjangoUrlsRePathCall() { node.getFunction() = django::urls::re_path().asCfgNode() }
|
||||
@@ -720,29 +797,26 @@ private module Django {
|
||||
djangoRouteHandlerFunctionTracker(result) = viewArg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
// If 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.
|
||||
exists(DjangoRouteHandler routeHandler | routeHandler = this.getARouteHandler() |
|
||||
not exists(this.getUrlPattern()) and
|
||||
result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and
|
||||
not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i))
|
||||
)
|
||||
or
|
||||
exists(DjangoRouteHandler routeHandler, DjangoUrlsRePathRegex regex |
|
||||
routeHandler = this.getARouteHandler() and
|
||||
regex.getRePathCall() = this
|
||||
|
|
||||
// either using named capture groups (passed as keyword arguments) or using
|
||||
// unnamed capture groups (passed as positional arguments)
|
||||
not exists(regex.getGroupName(_, _)) and
|
||||
// first group will have group number 1
|
||||
result =
|
||||
routeHandler.getArg(routeHandler.getRequestParamIndex() + regex.getGroupNumber(_, _))
|
||||
or
|
||||
result = routeHandler.getArgByName(regex.getGroupName(_, _))
|
||||
/**
|
||||
* A call to `django.conf.urls.url`.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url
|
||||
*/
|
||||
private class DjangoConfUrlsUrlCall extends DjangoRegexRouteSetup {
|
||||
override CallNode node;
|
||||
|
||||
DjangoConfUrlsUrlCall() { node.getFunction() = django::conf::conf_urls::url().asCfgNode() }
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("regex")]
|
||||
}
|
||||
|
||||
override DjangoRouteHandler getARouteHandler() {
|
||||
exists(DataFlow::Node viewArg |
|
||||
viewArg.asCfgNode() in [node.getArg(1), node.getArgByName("view")] and
|
||||
djangoRouteHandlerFunctionTracker(result) = viewArg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
@@ -0,0 +1,29 @@
|
||||
from django.http.response import HttpResponse, HttpResponseRedirect, JsonResponse, HttpResponseNotFound
|
||||
|
||||
# Not an XSS sink, since the Content-Type is not "text/html"
|
||||
# FP reported in https://github.com/github/codeql-python-team/issues/38
|
||||
def fp_json_response(request):
|
||||
# implicitly sets Content-Type to "application/json"
|
||||
return JsonResponse({"foo": request.GET.get("foo")})
|
||||
|
||||
# Not an XSS sink, since the Content-Type is not "text/html"
|
||||
def fp_manual_json_response(request):
|
||||
json_data = '{"json": "{}"}'.format(request.GET.get("foo"))
|
||||
return HttpResponse(json_data, content_type="application/json")
|
||||
|
||||
# Not an XSS sink, since the Content-Type is not "text/html"
|
||||
def fp_manual_content_type(request):
|
||||
return HttpResponse('<img src="0" onerror="alert(1)">', content_type="text/plain")
|
||||
|
||||
# XSS FP reported in https://github.com/github/codeql/issues/3466
|
||||
# Note: This should be a open-redirect sink, but not a XSS sink.
|
||||
def fp_redirect(request):
|
||||
return HttpResponseRedirect(request.GET.get("next"))
|
||||
|
||||
# Ensure that simple subclasses are still vuln to XSS
|
||||
def tp_not_found(request):
|
||||
return HttpResponseNotFound(request.GET.get("name"))
|
||||
|
||||
# Ensure we still have a XSS sink when manually setting the content_type to HTML
|
||||
def tp_manual_response_type(request):
|
||||
return HttpResponse(request.GET.get("name"), content_type="text/html; charset=utf-8")
|
||||
@@ -0,0 +1,79 @@
|
||||
"""test of views for Django 1.x"""
|
||||
from django.conf.urls import patterns, url
|
||||
from django.http.response import HttpResponse
|
||||
from django.views.generic import View
|
||||
|
||||
|
||||
def url_match_xss(request, foo, bar, no_taint=None): # $routeHandler $routedParameter=foo $routedParameter=bar
|
||||
return HttpResponse('url_match_xss: {} {}'.format(foo, bar))
|
||||
|
||||
|
||||
def get_params_xss(request): # $routeHandler
|
||||
return HttpResponse(request.GET.get("untrusted"))
|
||||
|
||||
|
||||
def post_params_xss(request): # $routeHandler
|
||||
return HttpResponse(request.POST.get("untrusted"))
|
||||
|
||||
|
||||
def http_resp_write(request): # $routeHandler
|
||||
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): # $f-:routeHandler $f-:routedParameter=untrusted
|
||||
return HttpResponse('Foo post: {}'.format(untrusted))
|
||||
|
||||
|
||||
class ClassView(View, Foo):
|
||||
|
||||
def get(self, request, untrusted): # $f-:routeHandler $f-:routedParameter=untrusted
|
||||
return HttpResponse('ClassView get: {}'.format(untrusted))
|
||||
|
||||
|
||||
def show_articles(request, page_number=1): # $routeHandler $routedParameter=page_number
|
||||
page_number = int(page_number)
|
||||
return HttpResponse('articles page: {}'.format(page_number))
|
||||
|
||||
|
||||
def xxs_positional_arg(request, arg0, arg1, no_taint=None): # $routeHandler $routedParameter=arg0 $routedParameter=arg1
|
||||
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1))
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)", url_match_xss), # $routeSetup="^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)"
|
||||
url(r"^get_params", get_params_xss), # $routeSetup="^get_params"
|
||||
url(r"^post_params", post_params_xss), # $routeSetup="^post_params"
|
||||
url(r"^http_resp_write", http_resp_write), # $routeSetup="^http_resp_write"
|
||||
url(r"^class_view/(?P<untrusted>.+)", ClassView.as_view()), # $routeSetup="^class_view/(?P<untrusted>.+)"
|
||||
|
||||
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
|
||||
url(r"articles/^(?:page-(?P<page_number>\d+)/)?", show_articles), # $routeSetup="articles/^(?:page-(?P<page_number>\d+)/)?"
|
||||
# passing as positional argument is not the recommended way of doing things, but it is certainly
|
||||
# possible
|
||||
url(r"^([^/]+)/(?:foo|bar)/([^/]+)", xxs_positional_arg, name='xxs_positional_arg'), # $routeSetup="^([^/]+)/(?:foo|bar)/([^/]+)"
|
||||
]
|
||||
|
||||
################################################################################
|
||||
# Using patterns() for routing
|
||||
|
||||
def show_user(request, username): # $routeHandler $routedParameter=username
|
||||
return HttpResponse('show_user {}'.format(username))
|
||||
|
||||
|
||||
urlpatterns = patterns(url(r"^users/(?P<username>[^/]+)", show_user)) # $routeSetup="^users/(?P<username>[^/]+)"
|
||||
|
||||
################################################################################
|
||||
# Show we understand the keyword arguments to django.conf.urls.url
|
||||
|
||||
def kw_args(request): # $routeHandler
|
||||
return HttpResponse('kw_args')
|
||||
|
||||
urlpatterns = [
|
||||
url(view=kw_args, regex=r"^kw_args") # $routeSetup="^kw_args"
|
||||
]
|
||||
@@ -97,3 +97,13 @@ urlpatterns = [
|
||||
# We should not report there is a request parameter called `not_valid!`
|
||||
path("not_valid/<not_valid!>", not_valid_identifier), # $routeSetup="not_valid/<not_valid!>"
|
||||
]
|
||||
|
||||
# This version 1.x way of defining urls is deprecated in Django 3.1, but still works
|
||||
from django.conf.urls import url
|
||||
|
||||
def deprecated(request): # $routeHandler
|
||||
return HttpResponse('deprecated')
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^deprecated/", deprecated), # $routeSetup="^deprecated/"
|
||||
]
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from django.urls import path, re_path
|
||||
|
||||
# This version 1.x way of defining urls is deprecated in Django 3.1, but still works
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
@@ -8,4 +11,5 @@ urlpatterns = [
|
||||
# inline expectation tests (which thinks the `$` would mark the beginning of a new
|
||||
# line)
|
||||
re_path(r"^ba[rz]/", views.bar_baz), # $routeSetup="^ba[rz]/"
|
||||
url(r"^deprecated/", views.deprecated), # $routeSetup="^deprecated/"
|
||||
]
|
||||
|
||||
@@ -5,3 +5,6 @@ def foo(request: HttpRequest): # $routeHandler
|
||||
|
||||
def bar_baz(request: HttpRequest): # $routeHandler
|
||||
return HttpResponse("bar_baz")
|
||||
|
||||
def deprecated(request: HttpRequest): # $routeHandler
|
||||
return HttpResponse("deprecated")
|
||||
|
||||
Reference in New Issue
Block a user