Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit afe7a05

Browse files
committed
Python: Support positional arguments in Django routes
1 parent 49dd221 commit afe7a05

6 files changed

Lines changed: 71 additions & 6 deletions

File tree

change-notes/1.23/analysis-python.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@
2020
|----------------------------|------------------------|------------|
2121
| Unreachable code | Fewer false positives | Analysis now accounts for uses of `contextlib.suppress` to suppress exceptions. |
2222
| `__iter__` method returns a non-iterator | Better alert message | Alert now highlights which class is expected to be an iterator. |
23+
24+
25+
## Changes to QL libraries
26+
27+
* Django library now recognizes positional arguments from a `django.conf.urls.url` regex (Django version 1.x)

python/ql/src/semmle/python/web/django/General.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ class DjangoRoute extends CallNode {
2525
regex.getGroupName(_, _) = result
2626
)
2727
}
28+
29+
/**
30+
* Get the number of positional arguments that will be passed to the view.
31+
* Will only return a result if there are no named arguments.
32+
*/
33+
int getNumPositionalArguments() {
34+
exists(DjangoRouteRegex regex |
35+
django_route(this, regex.getAFlowNode(), _) and
36+
not exists(string s | s = regex.getGroupName(_, _)) and
37+
result = count(regex.getGroupNumber(_, _))
38+
)
39+
}
2840
}

python/ql/src/semmle/python/web/django/Request.qll

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,15 @@ class DjangoClassBasedViewRequestArgument extends DjangoRequestSource {
7979
/** An argument specified in a url routing table */
8080
class DjangoRequestParameter extends HttpRequestTaintSource {
8181
DjangoRequestParameter() {
82-
exists(DjangoRoute route |
83-
this.(ControlFlowNode).getNode() = route
84-
.getViewFunction()
85-
.getScope()
86-
.getArgByName(route.getNamedArgument())
82+
exists(DjangoRoute route, Function f |
83+
f = route.getViewFunction().getScope() |
84+
this.(ControlFlowNode).getNode() = f.getArgByName(route.getNamedArgument())
85+
or
86+
exists(int i | i >= 0 |
87+
i < route.getNumPositionalArguments() and
88+
// +1 because first argument is always the request
89+
this.(ControlFlowNode).getNode() = f.getArg(i+1)
90+
)
8791
)
8892
}
8993

python/ql/test/library-tests/web/django/Sinks.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
| test.py:25 | BinaryExpr | externally controlled string |
55
| test.py:26 | BinaryExpr | externally controlled string |
66
| test.py:34 | BinaryExpr | externally controlled string |
7+
| test.py:46 | Attribute() | externally controlled string |
8+
| test.py:57 | Attribute() | externally controlled string |
9+
| test.py:60 | Attribute() | externally controlled string |
10+
| test.py:63 | Attribute() | externally controlled string |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
| test.py:11 | request | django.request.HttpRequest |
22
| test.py:31 | request | django.request.HttpRequest |
3+
| test.py:56 | arg0 | externally controlled string |
4+
| test.py:56 | request | django.request.HttpRequest |
5+
| test.py:59 | arg0 | externally controlled string |
6+
| test.py:59 | arg1 | externally controlled string |
7+
| test.py:59 | arg2 | externally controlled string |
8+
| test.py:59 | request | django.request.HttpRequest |
9+
| test.py:62 | arg0 | externally controlled string |
10+
| test.py:62 | arg1 | externally controlled string |
11+
| test.py:62 | request | django.request.HttpRequest |

python/ql/test/library-tests/web/django/test.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,38 @@ def maybe_xss(request):
3434
resp.write("first name is " + first_name)
3535
return resp
3636

37-
urlpatterns2 = [
37+
urlpatterns = [
3838
# Route to code_execution
3939
url(r'^maybe_xss$', maybe_xss, name='maybe_xss')
4040
]
41+
42+
43+
# Non capturing group (we correctly identify page_number as a request parameter)
44+
45+
def show_articles(request, page_number=1):
46+
return HttpResponse('articles page: {}'.format(page_number))
47+
48+
urlpatterns = [
49+
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
50+
url(r'articles/^(?:page-(?P<page_number>\d+)/)?$', show_articles),
51+
]
52+
53+
54+
# Positional arguments
55+
56+
def xxs_positional_arg1(request, arg0):
57+
return HttpResponse('xxs_positional_arg1: {}'.format(arg0))
58+
59+
def xxs_positional_arg2(request, arg0, arg1, arg2):
60+
return HttpResponse('xxs_positional_arg2: {} {} {}'.format(arg0, arg1, arg2))
61+
62+
def xxs_positional_arg3(request, arg0, arg1):
63+
return HttpResponse('xxs_positional_arg3: {} {}'.format(arg0, arg1))
64+
65+
urlpatterns = [
66+
# passing as positional argument is not the recommended way of doing things,
67+
# but it is certainly possible
68+
url(r'^(.+)$', xxs_positional_arg1, name='xxs_positional_arg1'),
69+
url(r'^([^/]+)/([^/]+)/([^/]+)$', xxs_positional_arg2, name='xxs_positional_arg2'),
70+
url(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg3, name='xxs_positional_arg3'),
71+
]

0 commit comments

Comments
 (0)