diff --git a/manifests/python.yml b/manifests/python.yml index dc70b68d63d..0ddc5fcd14f 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -197,7 +197,8 @@ tests/: TestXPathInjection_ExtendedLocation: missing_feature TestXPathInjection_StackTrace: missing_feature test_xss.py: - TestXSS: missing_feature + TestXSS: + '*': v3.0.0.dev TestXSS_ExtendedLocation: missing_feature TestXSS_StackTrace: missing_feature source/: diff --git a/tests/appsec/iast/sink/test_xss.py b/tests/appsec/iast/sink/test_xss.py index 441ee5cec95..e10d799ec5c 100644 --- a/tests/appsec/iast/sink/test_xss.py +++ b/tests/appsec/iast/sink/test_xss.py @@ -15,7 +15,10 @@ class TestXSS(BaseSinkTestWithoutTelemetry): insecure_endpoint = "/iast/xss/test_insecure" secure_endpoint = "/iast/xss/test_secure" data = {"param": "param"} - location_map = {"java": "com.datadoghq.system_tests.iast.utils.XSSExamples"} + location_map = { + "java": "com.datadoghq.system_tests.iast.utils.XSSExamples", + "python": {"django-poc": "app/urls.py"}, + } @rfc( diff --git a/utils/build/docker/python/django/app/urls.py b/utils/build/docker/python/django/app/urls.py index d00cd8ec4f6..d87fd707371 100644 --- a/utils/build/docker/python/django/app/urls.py +++ b/utils/build/docker/python/django/app/urls.py @@ -16,6 +16,8 @@ from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse from django.urls import path from django.views.decorators.csrf import csrf_exempt +from django.shortcuts import render +from django.utils.safestring import mark_safe from moto import mock_aws import urllib3 from iast import ( @@ -530,6 +532,20 @@ def view_iast_ssrf_secure(request): return HttpResponse("OK") +@csrf_exempt +def view_iast_xss_insecure(request): + param = request.POST.get("param", "") + # Validate the URL and enforce whitelist + return render(request, "index.html", {"param": mark_safe(param)}) + + +@csrf_exempt +def view_iast_xss_secure(request): + param = request.POST.get("param", "") + # Validate the URL and enforce whitelist + return render(request, "index.html", {"param": param}) + + @csrf_exempt def view_iast_stacktraceleak_insecure(request): return HttpResponse(""" @@ -980,6 +996,8 @@ def s3_multipart_upload(request): path("iast/path_traversal/test_secure", view_iast_path_traversal_secure), path("iast/ssrf/test_insecure", view_iast_ssrf_insecure), path("iast/ssrf/test_secure", view_iast_ssrf_secure), + path("iast/xss/test_insecure", view_iast_xss_insecure), + path("iast/xss/test_secure", view_iast_xss_secure), path("iast/stack_trace_leak/test_insecure", view_iast_stacktraceleak_insecure), path("iast/stack_trace_leak/test_secure", view_iast_stacktraceleak_secure), path("iast/source/body/test", view_iast_source_body), diff --git a/utils/build/docker/python/django/django_app/settings.py b/utils/build/docker/python/django/django_app/settings.py index 711dda90d13..31f864c44a3 100644 --- a/utils/build/docker/python/django/django_app/settings.py +++ b/utils/build/docker/python/django/django_app/settings.py @@ -55,7 +55,9 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [ + os.path.join(BASE_DIR, "django_app", "templates"), + ], "APP_DIRS": True, "OPTIONS": { "context_processors": [ diff --git a/utils/build/docker/python/django/django_app/templates/index.html b/utils/build/docker/python/django/django_app/templates/index.html new file mode 100644 index 00000000000..7135619ca9d --- /dev/null +++ b/utils/build/docker/python/django/django_app/templates/index.html @@ -0,0 +1,5 @@ + +
+Input: {{ user_input }}
+ + \ No newline at end of file diff --git a/utils/build/docker/python/fastapi.base.Dockerfile b/utils/build/docker/python/fastapi.base.Dockerfile index 65a7a52c365..59635f877dd 100644 --- a/utils/build/docker/python/fastapi.base.Dockerfile +++ b/utils/build/docker/python/fastapi.base.Dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y curl git gcc g++ make cmake RUN python --version && curl --version # install python deps -RUN pip install fastapi uvicorn cryptography==42.0.8 pycryptodome python-multipart +RUN pip install fastapi uvicorn cryptography==42.0.8 pycryptodome python-multipart jinja2 # Install Rust toolchain RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y diff --git a/utils/build/docker/python/fastapi/main.py b/utils/build/docker/python/fastapi/main.py index a1678191dda..ef1b05fc2d0 100644 --- a/utils/build/docker/python/fastapi/main.py +++ b/utils/build/docker/python/fastapi/main.py @@ -14,6 +14,7 @@ from fastapi import Header from fastapi import Request from fastapi.responses import JSONResponse +from fastapi.responses import HTMLResponse from fastapi.responses import PlainTextResponse from iast import weak_cipher from iast import weak_cipher_secure_algorithm @@ -21,6 +22,7 @@ from iast import weak_hash_duplicates from iast import weak_hash_multiple from iast import weak_hash_secure_algorithm +from jinja2 import Template import psycopg2 from pydantic import BaseModel import requests @@ -913,6 +915,20 @@ def safe_eval(expr): return "OK" +@app.post("/iast/xss/test_insecure", response_class=PlainTextResponse) +async def view_iast_xss_insecure(param: typing.Annotated[str, Form()]): + template = Template("{{ param|safe }}
") + html = template.render(param=param) + return HTMLResponse(html) + + +@app.post("/iast/xss/test_secure", response_class=PlainTextResponse) +async def view_iast_xss_secure(param: typing.Annotated[str, Form()]): + template = Template("{{ param }}
") + html = template.render(param=param) + return HTMLResponse(html) + + @app.get("/createextraservice", response_class=PlainTextResponse) def create_extra_service(serviceName: str = ""): if serviceName: diff --git a/utils/build/docker/python/flask/app.py b/utils/build/docker/python/flask/app.py index c871f800a3a..b05a5756df6 100644 --- a/utils/build/docker/python/flask/app.py +++ b/utils/build/docker/python/flask/app.py @@ -30,10 +30,12 @@ from flask import Flask from flask import Response from flask import jsonify +from flask import render_template_string from flask import request from flask import request as flask_request from flask_login import LoginManager from flask_login import login_user + from iast import weak_cipher from iast import weak_cipher_secure_algorithm from iast import weak_hash @@ -1235,6 +1237,20 @@ def safe_eval(expr): return resp +@app.route("/iast/xss/test_insecure", methods=["POST"]) +def view_iast_xss_insecure(): + param = flask_request.form["param"] + + return render_template_string("XSS: {{ param|safe }}
", param=param) + + +@app.route("/iast/xss/test_secure", methods=["POST"]) +def view_iast_xss_secure(): + param = flask_request.form["param"] + + return render_template_string("XSS: {{ param }}
", param=param) + + _TRACK_METADATA = { "metadata0": "value0", "metadata1": "value1",