{"title":"HTTP Request Smuggling via Inconsistent Content-Length and Transfer-Encoding Headers","language":"Python","severity":"Critical","cwe":"CWE-444","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"from flask import Flask, request, Response\nimport requests\napp = Flask(__name__)\nREVERSE_PROXY = \"http://internal-backend:8080\"\n@app.route('/api/iot/telemetry', methods=['POST'])\ndef forward_telemetry():\n    device_id = request.headers.get('X-Device-ID', 'unknown')\n    raw_body = request.get_data()\n    fwd_headers = {k: v for k, v in request.headers if k.lower() not in ['host']}\n    fwd_headers['Content-Length'] = str(len(raw_body))\n    if 'Transfer-Encoding' in request.headers:\n        fwd_headers['Transfer-Encoding'] = request.headers['Transfer-Encoding']\n    resp = requests.post(f\"{REVERSE_PROXY}/ingest/{device_id}\", data=raw_body, headers=fwd_headers, allow_redirects=False)\n    return Response(resp.content, status=resp.status_code)","explanation":"The proxy blindly copies both Content-Length and Transfer-Encoding headers from the untrusted incoming request and forwards them together to the internal backend. When both CL and TE headers are present simultaneously, HTTP/1.1 parsers on the backend may resolve the ambiguity differently from the front-end, allowing an attacker to smuggle a partial second request body that is interpreted as the beginning of the next request by the backend — classic CL.TE or TE.CL HTTP Request Smuggling. Additionally, the unsanitized X-Device-ID is interpolated directly into the forwarded URL path, enabling SSRF or path traversal against internal endpoints.","remediation":"The fix removes the blind forwarding of Content-Length and Transfer-Encoding headers from the incoming request. Instead, only an explicit allowlist of safe, non-framing headers is forwarded, and the `requests` library is allowed to compute the correct Content-Length from the actual body data. Additionally, the X-Device-ID value is validated against a strict alphanumeric regex to prevent path traversal and SSRF via URL path injection.","secure_code":"from flask import Flask, request, Response\nimport requests\nimport re\n\napp = Flask(__name__)\nREVERSE_PROXY = \"http://internal-backend:8080\"\n\n# Allowlist of safe headers to forward (lowercase)\nSAFE_FORWARD_HEADERS = {\n    'content-type',\n    'accept',\n    'x-device-id',\n    'authorization',\n    'x-request-id',\n}\n\n# Regex for validating device ID to prevent path traversal / SSRF\nDEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\\-]+$')\n\n\n@app.route('/api/iot/telemetry', methods=['POST'])\ndef forward_telemetry():\n    device_id = request.headers.get('X-Device-ID', 'unknown')\n\n    # Validate device_id to prevent path traversal and SSRF\n    if not DEVICE_ID_PATTERN.match(device_id):\n        return Response(\"Invalid Device ID\", status=400)\n\n    raw_body = request.get_data()\n\n    # Only forward explicitly safe headers; never forward Transfer-Encoding,\n    # Content-Length, or other hop-by-hop / framing headers.\n    fwd_headers = {}\n    for k, v in request.headers:\n        if k.lower() in SAFE_FORWARD_HEADERS:\n            fwd_headers[k] = v\n\n    # Let the `requests` library compute Content-Length from the actual body.\n    # Do NOT set Content-Length or Transfer-Encoding manually.\n    resp = requests.post(\n        f\"{REVERSE_PROXY}/ingest/{device_id}\",\n        data=raw_body,\n        headers=fwd_headers,\n        allow_redirects=False,\n    )\n    return Response(resp.content, status=resp.status_code)"}