# HTTP Request Smuggling via Inconsistent Content-Length and Transfer-Encoding Headers

Language: Python
Severity: Critical
CWE: CWE-444

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
from flask import Flask, request, Response
import requests
app = Flask(__name__)
REVERSE_PROXY = "http://internal-backend:8080"
@app.route('/api/iot/telemetry', methods=['POST'])
def forward_telemetry():
    device_id = request.headers.get('X-Device-ID', 'unknown')
    raw_body = request.get_data()
    fwd_headers = {k: v for k, v in request.headers if k.lower() not in ['host']}
    fwd_headers['Content-Length'] = str(len(raw_body))
    if 'Transfer-Encoding' in request.headers:
        fwd_headers['Transfer-Encoding'] = request.headers['Transfer-Encoding']
    resp = requests.post(f"{REVERSE_PROXY}/ingest/{device_id}", data=raw_body, headers=fwd_headers, allow_redirects=False)
    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
```python
from flask import Flask, request, Response
import requests
import re

app = Flask(__name__)
REVERSE_PROXY = "http://internal-backend:8080"

# Allowlist of safe headers to forward (lowercase)
SAFE_FORWARD_HEADERS = {
    'content-type',
    'accept',
    'x-device-id',
    'authorization',
    'x-request-id',
}

# Regex for validating device ID to prevent path traversal / SSRF
DEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\-]+$')


@app.route('/api/iot/telemetry', methods=['POST'])
def forward_telemetry():
    device_id = request.headers.get('X-Device-ID', 'unknown')

    # Validate device_id to prevent path traversal and SSRF
    if not DEVICE_ID_PATTERN.match(device_id):
        return Response("Invalid Device ID", status=400)

    raw_body = request.get_data()

    # Only forward explicitly safe headers; never forward Transfer-Encoding,
    # Content-Length, or other hop-by-hop / framing headers.
    fwd_headers = {}
    for k, v in request.headers:
        if k.lower() in SAFE_FORWARD_HEADERS:
            fwd_headers[k] = v

    # Let the `requests` library compute Content-Length from the actual body.
    # Do NOT set Content-Length or Transfer-Encoding manually.
    resp = requests.post(
        f"{REVERSE_PROXY}/ingest/{device_id}",
        data=raw_body,
        headers=fwd_headers,
        allow_redirects=False,
    )
    return Response(resp.content, status=resp.status_code)
```
