GHSA-8p58-35c3-ccxx: AVideo has an Unauthenticated Blind SQL Injection in RTMP on_publish Callback via Stream Name Parameter
SummaryThe 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:**Injection point #1 — unconditional (no `p` parameter needed):**At line 120, `$_POST['name']` is passed directly to `LiveTransmitionHistory::getLatest()`:Inside `getLatest()`, the key is interpolated into a LIKE clause without escaping:**Injection point #2 — when `$_GET['p']` is provided:**At line 146, `$_POST['name']` is passed to `LiveTransmition::keyExists()`:Inside `keyExists()`, `cleanUpKey()` is called (which only strips adaptive/playlist/sub suffixes — no SQL escaping), then the key is interpolated directly:**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):**A ~5-second response time confirms injection. The payload:Avoids `&` and `=` (stripped by line 117)Avoids `_` and `-` in positions where `cleanUpKey()` would splitUses `%23` (`#`) to comment out the trailing `%'`**Data extraction — character-by-character:****Injection point #2 (via keyExists):**This reaches `keyExists()` at line 146, producing:ImpactAn unauthenticated remote attacker can:**Extract all database contents** via time-based blind SQL injection, including:User password hashes (bcrypt)Email addresses and personal informationAPI keys, session tokens, and live stream passwordsSite configuration and secrets stored in database tables**Authenticate as any user to the streaming system** — extracted password hashes can be used directly as the `$_GET['p']` parameter since `on_publish.php:153` compares `$_GET['p'] === $user->getPassword()` against the raw stored hash, allowing the attacker to start streams impersonating any user.**Enumerate database structure** — the injection can be used to query `information_schema` tables, mapping the entire database for further exploitation.The first injection point (via `getLatest()`) is reached unconditionally on every request — no additional parameters beyond `name` and `tcurl` are required.Recommended FixUse parameterized queries in both affected functions:**Fix `LiveTransmition::keyExists()` at `plugin/Live/Objects/LiveTransmition.php:298-303`:****Fix `LiveTransmitionHistory::getLatest()` at `plugin/Live/Objects/LiveTransmitionHistory.php:494-495`:****Fix `LiveTransmitionHistory::getLatestFromKey()` at `plugin/Live/Objects/LiveTransmitionHistory.php:681-688`:**All three fixes use the existing `sqlDAL::readSql()` parameterized binding support (`"s"` format for string, values array) which is already used elsewhere in the codebase.
CVSS v3.1: 7.5