Skip to content

Commit 75e2555

Browse files
committed
Python: Add rest_framework taint tests
1 parent 095f896 commit 75e2555

File tree

5 files changed

+125
-0
lines changed

5 files changed

+125
-0
lines changed

python/ql/test/library-tests/frameworks/rest_framework/ConceptsTest.expected

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.ConceptsTest
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
argumentToEnsureNotTaintedNotMarkedAsSpurious
2+
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
3+
failures
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import experimental.meta.InlineTaintTest
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from rest_framework.decorators import api_view, parser_classes
2+
from rest_framework.views import APIView
3+
from rest_framework.request import Request
4+
from rest_framework.response import Response
5+
from rest_framework.parsers import JSONParser
6+
7+
from django.urls import path
8+
9+
ensure_tainted = ensure_not_tainted = print
10+
11+
# function based view
12+
# see https://www.django-rest-framework.org/api-guide/views/#function-based-views
13+
14+
15+
@api_view(["POST"])
16+
@parser_classes([JSONParser])
17+
def test_taint(request: Request, routed_param): # $ requestHandler routedParameter=routed_param
18+
ensure_tainted(routed_param) # $ tainted
19+
20+
ensure_tainted(request) # $ tainted
21+
22+
# Has all the standard attributes of a django HttpRequest
23+
# 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
25+
26+
# special new attributes added, see https://www.django-rest-framework.org/api-guide/requests/
27+
ensure_tainted(
28+
request.data, # $ MISSING: tainted
29+
request.data["key"], # $ MISSING: tainted
30+
31+
# alias for .GET
32+
request.query_params, # $ MISSING: tainted
33+
request.query_params["key"], # $ MISSING: tainted
34+
request.query_params.get("key"), # $ MISSING: tainted
35+
request.query_params.getlist("key"), # $ MISSING: tainted
36+
request.query_params.getlist("key")[0], # $ MISSING: tainted
37+
request.query_params.pop("key"), # $ MISSING: tainted
38+
request.query_params.pop("key")[0], # $ MISSING: tainted
39+
40+
# see more detailed tests of `request.user` below
41+
request.user, # $ MISSING: tainted
42+
43+
request.auth, # $ MISSING: tainted
44+
45+
# seems much more likely attack vector than .method, so included
46+
request.content_type, # $ tainted
47+
48+
# file-like
49+
request.stream, # $ MISSING: tainted
50+
request.stream.read(), # $ MISSING: tainted
51+
)
52+
53+
ensure_not_tainted(
54+
# although these could technically be user-controlled, it seems more likely to lead to FPs than interesting results.
55+
request.accepted_media_type,
56+
request.method, # $ SPURIOUS: tainted
57+
)
58+
59+
# --------------------------------------------------------------------------
60+
# request.user
61+
# --------------------------------------------------------------------------
62+
#
63+
# This will normally be an instance of django.contrib.auth.models.User
64+
# (authenticated) so we assume that normally user-controlled fields such as
65+
# username/email is user-controlled, but that password isn't (since it's a hash).
66+
# see https://docs.djangoproject.com/en/3.2/ref/contrib/auth/#fields
67+
ensure_tainted(
68+
request.user.username, # $ MISSING: tainted
69+
request.user.first_name, # $ MISSING: tainted
70+
request.user.last_name, # $ MISSING: tainted
71+
request.user.email, # $ MISSING: tainted
72+
)
73+
ensure_not_tainted(request.user.password)
74+
75+
return Response("ok")
76+
77+
78+
# class based view
79+
# see https://www.django-rest-framework.org/api-guide/views/#class-based-views
80+
81+
82+
class MyClass(APIView):
83+
def initial(self, request, *args, **kwargs):
84+
# this method will be called before processing any request
85+
ensure_tainted(request) # $ MISSING: tainted
86+
87+
def get(self, request: Request, routed_param): # $ requestHandler routedParameter=routed_param
88+
ensure_tainted(routed_param) # $ tainted
89+
90+
# request taint is the same as in function_based_view above
91+
ensure_tainted(
92+
request, # $ tainted
93+
request.data # $ MISSING: tainted
94+
)
95+
96+
# same as for standard Django view
97+
ensure_tainted(self.args, self.kwargs) # $ tainted
98+
99+
return Response("ok")
100+
101+
102+
103+
# fake setup, you can't actually run this
104+
urlpatterns = [
105+
path("test-taint/<routed_param>", test_taint), # $ routeSetup="test-taint/<routed_param>"
106+
path("ClassView/<routed_param>", MyClass.as_view()), # $ routeSetup="ClassView/<routed_param>"
107+
]
108+
109+
# tests with no route-setup, but we can still tell that these are using Django REST
110+
# framework
111+
112+
@api_view(["POST"])
113+
def function_based_no_route(request: Request, possible_routed_param):
114+
ensure_tainted(request, possible_routed_param) # $ MISSING: tainted
115+
116+
117+
class ClassBasedNoRoute(APIView):
118+
def get(self, request: Request, possible_routed_param):
119+
ensure_tainted(request, possible_routed_param) # $ MISSING: tainted

0 commit comments

Comments
 (0)