GHSA-2hch-c97c-g99x: AVideo has SSRF Protection Bypass via HTTP Redirect and DNS Rebinding in isSSRFSafeURL()
SummaryTwo 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.A 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.**Severity:** High — CVSS 3.1: 7.7 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)DetailsFinding 1: Redirect-Based SSRF Bypass**Vulnerable code — `plugin/AI/receiveAsync.json.php` (line ~162–165):****Vulnerable code — `objects/EpgParser.php` (line ~358–362):****Safe code for comparison — `objects/functions.php`, `url_get_contents()`:****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).Finding 2: DNS Rebinding TOCTOU (Multiple Callers)`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).**Affected callers (no DNS pinning):**`objects/aVideoEncoderReceiveImage.json.php` — 4 call sites`objects/aVideoEncoder.json.php` — 1 call site`plugin/BulkEmbed/save.json.php` — 1 call site`plugin/AI/receiveAsync.json.php` — 1 call site`objects/EpgParser.php` — 1 call site`plugin/Scheduler/Scheduler.php` — 1 call sitePoCRedirect Bypass PoCAttacker runs an HTTP server that returns a 302 redirect:Attacker triggers AI image generation and intercepts the callback:`isSSRFSafeURL("http://ATTACKER_IP:8888/redir")` resolves attacker IP → public → **passes**`file_get_contents("http://ATTACKER_IP:8888/redir")` follows 302 to `http://169.254.169.254/...` — **no SSRF re-check occurs**Cloud metadata (including IAM credentials) is saved as a video thumbnail, retrievable by the attacker**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.DNS Rebinding PoCConfigure a domain with TTL=0 DNS that alternates:First query: public IP (passes `isSSRFSafeURL`)Second query: `127.0.0.1` (reaches internal services)Submit `http://rebind.attacker.com/image.jpg` to any affected endpoint`isSSRFSafeURL()` resolves → public IP → passes (discards `$resolvedIP`)`url_get_contents()` / `file_get_contents()` resolves again → `127.0.0.1` → SSRF achievedImpactAn authenticated attacker can force the AVideo server to make HTTP requests to arbitrary internal hosts, including:**Cloud metadata endpoints** (169.254.169.254) — exfiltrate IAM credentials, instance identity**Internal services** on localhost or private network (databases, admin panels, monitoring)**Port scanning** of the internal network using the server as a proxyThe exfiltrated data is stored as video thumbnails/images, making it retrievable through the application's public interface.Suggested Fix**Fix 1 (Redirect bypass — immediate):** Route both affected files through `url_get_contents()` which already handles redirects safely, or add explicit no-redirect context:**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`.CreditKai Aizen <kai.aizen.dev@gmail.com>
CVSS v3.1: 7.7