From 6c779c7fa50ec6c191c6109bafddcea61fa90b12 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Mon, 4 Aug 2025 11:59:09 +0200 Subject: [PATCH] Python: Added extra test cases for path injection with FastAPI --- .../PathInjection.expected | 18 +++++++ .../fastapi_path_injection.py | 49 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 python/ql/test/query-tests/Security/CWE-022-PathInjection/fastapi_path_injection.py diff --git a/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected b/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected index 368400d505d..46b094e024f 100644 --- a/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected @@ -1,4 +1,7 @@ #select +| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | user-provided value | +| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | user-provided value | +| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | user-provided value | | flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | This path depends on a $@. | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | path_injection.py:13:14:13:47 | ControlFlowNode for Attribute() | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:13:14:13:47 | ControlFlowNode for Attribute() | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value | | path_injection.py:21:14:21:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:21:14:21:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value | @@ -20,6 +23,13 @@ | test.py:33:14:33:14 | ControlFlowNode for x | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:33:14:33:14 | ControlFlowNode for x | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:49:14:49:14 | ControlFlowNode for y | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:49:14:49:14 | ControlFlowNode for y | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value | edges +| fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | provenance | | +| fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | fastapi_path_injection.py:20:34:20:37 | ControlFlowNode for path | provenance | | +| fastapi_path_injection.py:20:34:20:37 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | | +| fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | fastapi_path_injection.py:27:34:27:37 | ControlFlowNode for path | provenance | | +| fastapi_path_injection.py:27:34:27:37 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | | +| fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | fastapi_path_injection.py:32:34:32:37 | ControlFlowNode for path | provenance | | +| fastapi_path_injection.py:32:34:32:37 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | | | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | provenance | | | flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | flask_path_injection.py:19:15:19:21 | ControlFlowNode for request | provenance | | | flask_path_injection.py:19:5:19:11 | ControlFlowNode for dirname | flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | provenance | | @@ -143,6 +153,14 @@ edges | test.py:48:23:48:23 | ControlFlowNode for x | test.py:12:15:12:15 | ControlFlowNode for x | provenance | | | test.py:48:23:48:23 | ControlFlowNode for x | test.py:48:13:48:24 | ControlFlowNode for normalize() | provenance | Config | nodes +| fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | semmle.label | ControlFlowNode for filepath | +| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | semmle.label | ControlFlowNode for filepath | +| fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| fastapi_path_injection.py:20:34:20:37 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| fastapi_path_injection.py:27:34:27:37 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| fastapi_path_injection.py:32:34:32:37 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | flask_path_injection.py:19:5:19:11 | ControlFlowNode for dirname | semmle.label | ControlFlowNode for dirname | diff --git a/python/ql/test/query-tests/Security/CWE-022-PathInjection/fastapi_path_injection.py b/python/ql/test/query-tests/Security/CWE-022-PathInjection/fastapi_path_injection.py new file mode 100644 index 00000000000..8e66bf3ed4c --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-022-PathInjection/fastapi_path_injection.py @@ -0,0 +1,49 @@ +from fastapi import FastAPI, Depends + +app = FastAPI() + +class FileHandler: + def get_data(self, filepath: str): + with open(filepath, "r") as f: # $ Alert + return f.readline() + +file_handler = None + +def init_file_handler(): + global file_handler + file_handler = FileHandler() + +@app.get("/file/") +async def read_item(path: str): # $ Source + if file_handler is None: + init_file_handler() + return file_handler.get_data(path) + +def init_file_handler(): + return FileHandler() + +@app.get("/file2/", dependencies=[Depends(init_file_handler)]) +async def read_item(path: str, file_handler: FileHandler = Depends()): # $ Source + return file_handler.get_data(path) + + +@app.get("/file3/", dependencies=[Depends(init_file_handler)]) +async def read_item(path: str): # $ Source + return file_handler.get_data(path) + + +@app.on_event("startup") +def init_file_handler(): + app.state.file_handler1 = FileHandler() + app.state.file_handler2 = FileHandler() + +def get_data_source(): + return app.state.file_handler1 + +@app.get("/file4/") +async def read_item(path: str, data_source=Depends(get_data_source)): # $ MISSING: Source + return data_source.get_data(path) + +@app.get("/file5/", dependencies=[Depends(init_file_handler)]) +async def read_item(path: str): # $ MISSING: Source + return app.state.file_handler2.get_data(path)