From 8a85ea3e5b54bb5b5865f89faef85609a1cfa708 Mon Sep 17 00:00:00 2001 From: Egon Okerman Date: Mon, 30 Oct 2023 16:39:43 +0100 Subject: [PATCH 1/4] Add FastAPI --- rules/S5131/python/how-to-fix-it/fastapi.adoc | 82 +++++++++++++++++++ rules/S5131/python/rule.adoc | 2 + 2 files changed, 84 insertions(+) create mode 100644 rules/S5131/python/how-to-fix-it/fastapi.adoc diff --git a/rules/S5131/python/how-to-fix-it/fastapi.adoc b/rules/S5131/python/how-to-fix-it/fastapi.adoc new file mode 100644 index 00000000000..5ea59a2ef1c --- /dev/null +++ b/rules/S5131/python/how-to-fix-it/fastapi.adoc @@ -0,0 +1,82 @@ +== How to fix it in FastAPI + +=== Code examples + +The following code is vulnerable to cross-site scripting because it returns an HTML response that contains user input. + +If you do not intend to send HTML code to clients, the vulnerability can be fixed by specifying the type of data returned in the response. +For example, you can use the `JsonResponse` class to return JSON messages securely. + +It is also possible to set the `Content-Type` manually by using the `content_type` parameter of the `Response` constructor. + +==== Noncompliant code example + +[source,python,diff-id=41,diff-type=noncompliant] +---- +from fastapi import FastAPI, Response +import json + +app = FastAPI() + +@app.get("/example") +def example(input: str): + json = json.dumps({"data": input}) + return Response(json) # Noncompliant +---- + +[source,python,diff-id=42,diff-type=noncompliant] +---- +from fastapi import FastAPI, Response + +app = FastAPI() + +@app.get("/example") +def example(input: str): + return Response(input) # Noncompliant +---- + +==== Compliant solution + +[source,python,diff-id=41,diff-type=compliant] +---- +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI() + +@app.get("/example") +def example(input: str): + return JSONResponse({"data": input}) +---- + +[source,python,diff-id=42,diff-type=compliant] +---- +from fastapi import FastAPI, Response + +app = FastAPI() + +@app.get("/example") +def example(input: str): + return Response(input, content_type="text/plain") +---- + +=== How does this work? + +If the HTTP response is HTML code, it is highly recommended to use a template engine like https://jinja.palletsprojects.com/[Jinja] to generate it. +This template engine separates the view from the business logic and automatically encodes the output of variables, drastically reducing the risk of cross-site scripting vulnerabilities. + +If you do not intend to send HTML code to clients, the vulnerability can be fixed by correctly setting the `Content-Type` HTTP header. +This HTTP header defines which media type the browser can expect from the response, so the browser can parse it correctly. By specifying a type that is not HTML, the browser does not interpret the response as HTML, which in turn prevents cross-site scripting. + +For example, when setting the `Content-Type` header to `text/plain`, browsers will interpret the HTTP response as plaintext and will not process it any further. This allows user input to be reflected safely. + +=== Pitfalls + +include::../../common/pitfalls/content-types.adoc[] + +include::../../common/pitfalls/validation.adoc[] + +=== Going the extra mile + +include::../../common/extra-mile/csp.adoc[] + diff --git a/rules/S5131/python/rule.adoc b/rules/S5131/python/rule.adoc index 4a560b9f00f..b812840c3b1 100644 --- a/rules/S5131/python/rule.adoc +++ b/rules/S5131/python/rule.adoc @@ -16,6 +16,8 @@ include::how-to-fix-it/flask.adoc[] include::how-to-fix-it/jinja.adoc[] +include::how-to-fix-it/fastapi.adoc[] + == Resources include::../common/resources/docs.adoc[] From 73b7dbca36ae0767013bef4058504468550f4cd6 Mon Sep 17 00:00:00 2001 From: Egon Okerman Date: Mon, 30 Oct 2023 17:03:28 +0100 Subject: [PATCH 2/4] Fix wrong constructor parameter --- rules/S5131/python/how-to-fix-it/fastapi.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/S5131/python/how-to-fix-it/fastapi.adoc b/rules/S5131/python/how-to-fix-it/fastapi.adoc index 5ea59a2ef1c..d220f221adb 100644 --- a/rules/S5131/python/how-to-fix-it/fastapi.adoc +++ b/rules/S5131/python/how-to-fix-it/fastapi.adoc @@ -7,7 +7,7 @@ The following code is vulnerable to cross-site scripting because it returns an H If you do not intend to send HTML code to clients, the vulnerability can be fixed by specifying the type of data returned in the response. For example, you can use the `JsonResponse` class to return JSON messages securely. -It is also possible to set the `Content-Type` manually by using the `content_type` parameter of the `Response` constructor. +It is also possible to set the `Content-Type` manually by using the `media_type` parameter of the `Response` constructor. ==== Noncompliant code example @@ -57,7 +57,7 @@ app = FastAPI() @app.get("/example") def example(input: str): - return Response(input, content_type="text/plain") + return Response(input, media_type="text/plain") ---- === How does this work? From e176e38fd0354538752046145a09ce2bcd4e43ce Mon Sep 17 00:00:00 2001 From: Egon Okerman Date: Tue, 31 Oct 2023 13:19:29 +0100 Subject: [PATCH 3/4] Allow FastAPI as a framework --- docs/header_names/allowed_framework_names.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/header_names/allowed_framework_names.adoc b/docs/header_names/allowed_framework_names.adoc index 56aa1dc6f64..d334993bc80 100644 --- a/docs/header_names/allowed_framework_names.adoc +++ b/docs/header_names/allowed_framework_names.adoc @@ -79,6 +79,7 @@ * Django Templates * Flask * aiohttp +* FastAPI * Jinja * lxml * Paramiko From 0ad4ba44bd3757293b234f759caa57c4f27c328a Mon Sep 17 00:00:00 2001 From: Egon Okerman Date: Thu, 4 Jan 2024 10:29:33 +0100 Subject: [PATCH 4/4] Fix syntax error in noncompliant example --- rules/S5131/python/how-to-fix-it/fastapi.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/S5131/python/how-to-fix-it/fastapi.adoc b/rules/S5131/python/how-to-fix-it/fastapi.adoc index d220f221adb..605fbb2024a 100644 --- a/rules/S5131/python/how-to-fix-it/fastapi.adoc +++ b/rules/S5131/python/how-to-fix-it/fastapi.adoc @@ -20,8 +20,8 @@ app = FastAPI() @app.get("/example") def example(input: str): - json = json.dumps({"data": input}) - return Response(json) # Noncompliant + json_str = json.dumps({"data": input}) + return Response(json_str) # Noncompliant ---- [source,python,diff-id=42,diff-type=noncompliant]