From ee8c215b8bb5097756e699526216a38645ed83d6 Mon Sep 17 00:00:00 2001 From: Matteo Vitali Date: Thu, 4 Sep 2025 16:58:17 +0200 Subject: [PATCH] Remediate security issues - Upgrade to jinja2~=3.1.0 - Remove hardcoded secrets using os.getenv - Add timeout handler to requests - Security hardening of Dockerfile - Fix SSTI vulnerability on jinja2 template rendering - Turn on coraza waf --- .env_temp | 2 +- Dockerfile | 2 +- Dockerfile.alpine | 4 ++-- app/config.py | 4 ++-- app/main.py | 8 +++----- caddy/Caddyfile | 2 +- requirements/common.in | 4 ++-- tests/test_main.py | 9 ++++----- 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.env_temp b/.env_temp index 0857975..407b4de 100644 --- a/.env_temp +++ b/.env_temp @@ -1,4 +1,4 @@ -APP_IMAGE=python-insecure-app:debian +APP_IMAGE=python-insecure-app:wolfi-noshell COMPOSE_FILE=docker-compose.yaml DEBUG=True LETSENCRYPT_EMAIL=info@example.com diff --git a/Dockerfile b/Dockerfile index b715d64..b28a952 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM python:3.13-slim-trixie@sha256:b04b5d7233d2ad9c379e22ea8927cd1378cd15c60d4ef876c065b25ea8fb3bf3 AS debian LABEL project="Python Insecure App" service="FastAPI" stage="debian" -# RUN python3 -m pip install --upgrade pip~=26.1 +RUN python3 -m pip install --upgrade pip~=26.1 ENV NONROOT=nonroot \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 \ diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 2d19f51..eb81422 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -3,8 +3,8 @@ FROM python:3.13-alpine@sha256:420cd0bf0f3998275875e02ecd5808168cf0843cbb4d3c536432f729247b2acc AS alpine LABEL project="Python Insecure App" service="FastAPI" stage="alpine" -# RUN python3 -m pip install --upgrade pip~=26.1 \ -# && apk upgrade --no-cache xz-libs +RUN python3 -m pip install --upgrade pip~=26.1 \ + && apk upgrade --no-cache xz-libs ENV NONROOT=nonroot \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 \ diff --git a/app/config.py b/app/config.py index 44c3c1c..0c44241 100644 --- a/app/config.py +++ b/app/config.py @@ -10,6 +10,6 @@ PUBLIC_IP_SERVICE_URL = os.getenv("PUBLIC_IP_SERVICE_URL") -SUPER_SECRET_NAME = "John Ripper" # FIXME: os.getenv("SUPER_SECRET_NAME") +SUPER_SECRET_NAME = os.getenv("SUPER_SECRET_NAME") -SUPER_SECRET_TOKEN = "5u93R53Cr3tT0k3n" # FIXME: os.getenv("SUPER_SECRET_TOKEN") +SUPER_SECRET_TOKEN = os.getenv("SUPER_SECRET_TOKEN") diff --git a/app/main.py b/app/main.py index cafefd2..1bba862 100644 --- a/app/main.py +++ b/app/main.py @@ -28,14 +28,12 @@ async def try_hack_me(name: str = config.SUPER_SECRET_NAME): """ try: # Get the public IP address from an external service - public_ip_response = requests.get(config.PUBLIC_IP_SERVICE_URL) + public_ip_response = requests.get(config.PUBLIC_IP_SERVICE_URL, timeout=5) public_ip_response.raise_for_status() except (requests.HTTPError, requests.exceptions.InvalidSchema): public_ip = "Unknown" else: public_ip = public_ip_response.text name = name or config.SUPER_SECRET_NAME - content = f"

Hello, {name}!

Public IP: {public_ip}

" - # https://fastapi.tiangolo.com/advanced/custom-response/#return-a-response - # FIXME: return HTMLResponse(content) - return Template(content).render() + content = "

Hello, {{name}}!

Public IP: {{public_ip}}

" + return Template(content).render(name=name, public_ip=public_ip) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 573234f..4bface7 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -10,7 +10,7 @@ (waf_rules) { coraza_waf { directives ` - SecRuleEngine Off + SecRuleEngine On SecRequestBodyAccess On SecRequestBodyLimitAction Reject SecDebugLogLevel 9 diff --git a/requirements/common.in b/requirements/common.in index a1be67e..2416b38 100644 --- a/requirements/common.in +++ b/requirements/common.in @@ -1,4 +1,4 @@ -r base.in -fastapi[standard]~=0.115.0 -jinja2~=3.0.0 +fastapi[standard]~=0.136.0 +jinja2~=3.1.0 requests~=2.33.0 diff --git a/tests/test_main.py b/tests/test_main.py index e83f82a..0c3bd14 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -39,8 +39,7 @@ def test_root(requests_mock): response.content.decode() == "

Hello, Bob!

Public IP: 123.45.67.89

" ) - # TODO: Test the root endpoint with SSTI simulation - # response = client.get("/?name={{7*6}}") - # assert response.status_code == 200 - # assert "42" not in response.content.decode() - # assert "{{7*6}}" in response.content.decode() + response = client.get("/?name={{7*6}}") + assert response.status_code == 200 + assert "42" not in response.content.decode() + assert "{{7*6}}" in response.content.decode()