Skip to content

Commit 2d5c6e2

Browse files
committed
Python: FastAPI: Add taint test
1 parent c839f35 commit 2d5c6e2

2 files changed

Lines changed: 97 additions & 5 deletions

File tree

python/ql/test/library-tests/frameworks/fastapi/basic.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,8 @@ async def get_baz(baz_id: int): # $ requestHandler routedParameter=baz_id
5959
# Docs:
6060
# see https://fastapi.tiangolo.com/tutorial/path-params/
6161

62-
# More stuff that we should support:
62+
# Things we should look at supporting:
6363
# - https://fastapi.tiangolo.com/tutorial/dependencies/
64-
# - Extra taint-steps for files
65-
# - https://fastapi.tiangolo.com/tutorial/request-files/
66-
# - https://fastapi.tiangolo.com/tutorial/request-files/#uploadfile
6764
# - https://fastapi.tiangolo.com/tutorial/background-tasks/
68-
#
6965
# - https://fastapi.tiangolo.com/tutorial/middleware/
7066
# - https://fastapi.tiangolo.com/tutorial/encoder/
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# --- to make things runable ---
2+
3+
ensure_tainted = ensure_not_tainted = print
4+
5+
# --- real code ---
6+
7+
from fastapi import FastAPI
8+
from typing import Optional, List
9+
from pydantic import BaseModel
10+
11+
12+
app = FastAPI()
13+
14+
15+
class Foo(BaseModel):
16+
foo: str
17+
18+
19+
class MyComplexModel(BaseModel):
20+
field: str
21+
main_foo: Foo
22+
other_foos: List[Foo]
23+
24+
25+
@app.post("/test_taint/{name}/{number}") # $ routeSetup="/test_taint/{name}/{number}"
26+
async def test_taint(name : str, number : int, also_input: MyComplexModel): # $ requestHandler routedParameter=name routedParameter=number routedParameter=also_input
27+
ensure_tainted(
28+
name, # $ tainted
29+
number, # $ tainted
30+
31+
also_input, # $ tainted
32+
also_input.field, # $ MISSING: tainted
33+
34+
also_input.main_foo, # $ MISSING: tainted
35+
also_input.main_foo.foo, # $ MISSING: tainted
36+
37+
also_input.other_foos, # $ MISSING: tainted
38+
also_input.other_foos[0], # $ MISSING: tainted
39+
also_input.other_foos[0].foo, # $ MISSING: tainted
40+
[f.foo for f in also_input.other_foos], # $ MISSING: tainted
41+
)
42+
43+
return "ok" # $ HttpResponse
44+
45+
46+
# --- body ---
47+
# see https://fastapi.tiangolo.com/tutorial/body-multiple-params/
48+
49+
from fastapi import Body
50+
51+
# request is made such as `/will-be-query-param?name=foo`
52+
@app.post("/will-be-query-param") # $ routeSetup="/will-be-query-param"
53+
async def will_be_query_param(name: str): # $ requestHandler routedParameter=name
54+
ensure_tainted(name) # $ tainted
55+
return "ok" # $ HttpResponse
56+
57+
# with the `= Body(...)` "annotation" FastAPI will know to transmit `name` as part of
58+
# the HTTP post body
59+
@app.post("/will-not-be-query-param") # $ routeSetup="/will-not-be-query-param"
60+
async def will_not_be_query_param(name: str = Body("foo", media_type="text/plain")): # $ requestHandler routedParameter=name
61+
ensure_tainted(name) # $ tainted
62+
return "ok" # $ HttpResponse
63+
64+
65+
# --- form data ---
66+
# see https://fastapi.tiangolo.com/tutorial/request-forms/
67+
68+
from fastapi import Form
69+
70+
@app.post("/form-example") # $ routeSetup="/form-example"
71+
async def form_example(username: str = Form(None)): # $ requestHandler routedParameter=username
72+
ensure_tainted(username) # $ tainted
73+
return "ok" # $ HttpResponse
74+
75+
76+
# --- file upload ---
77+
# see https://fastapi.tiangolo.com/tutorial/request-files/
78+
# see https://fastapi.tiangolo.com/tutorial/request-files/#uploadfile
79+
80+
from fastapi import File, UploadFile
81+
82+
@app.post("/file-upload") # $ routeSetup="/file-upload"
83+
async def file_upload(f1: bytes = File(None), f2: UploadFile = File(None)): # $ requestHandler routedParameter=f1 routedParameter=f2
84+
ensure_tainted(
85+
f1, # $ tainted
86+
87+
f2, # $ tainted
88+
f2.filename, # $ MISSING: tainted
89+
f2.content_type, # $ MISSING: tainted
90+
f2.file, # $ MISSING: tainted
91+
f2.file.read(), # $ MISSING: tainted
92+
f2.file.readline(), # $ MISSING: tainted
93+
f2.file.readlines(), # $ MISSING: tainted
94+
await f2.read(), # $ MISSING: tainted
95+
)
96+
return "ok" # $ HttpResponse

0 commit comments

Comments
 (0)