CVE-2025-25214
A race condition vulnerability exists in the aVideoEncoder.json.php unzip functionality of WWBN AVideo 14.4 and dev master commit 8a8954ff. A series of specially crafted HTTP request can lead to arbitrary code execution.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
WWBN AVideo 14.4
WWBN AVideo dev master commit 8a8954ff
AVideo - https://github.com/WWBN/AVideo
8.8 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-362 - Concurrent Execution using Shared Resource with Improper Synchronization (‘Race Condition’)
AVideo is a web application, mostly written in PHP, that can be used to create an audio/video sharing website. It allows users to import videos from various sources, encode and share them in various ways. Users can sign up to the website in order to share videos, while viewers have anonymous access to the publicly-available contents. The platform provides plugins for features like live streaming, skins, YouTube uploads and more.
The objects/aVideoEncoder.json.php
file can be used to add new videos, this functionality does not need special configuration to be used. However, the user performing the request needs permission to upload videos.
When adding a new video, a downloadURL
can be specified, so that AVideo fetches the URL content and adds it as a video. Alternatively, a user can supply a zip file:
...
// get video file from encoder
[1] if (!empty($_FILES['video']['tmp_name'])) {
$obj->lines[] = __LINE__;
$resolution = '';
[2] if (!empty($_REQUEST['resolution'])) {
$obj->lines[] = __LINE__;
if (!in_array($_REQUEST['resolution'], $global['avideo_possible_resolutions'])) {
$obj->lines[] = __LINE__;
$msg = "This resolution is not possible {$_REQUEST['resolution']}";
_error_log($msg);
forbiddenPage($msg);
}
$resolution = "_{$_REQUEST['resolution']}";
}
$obj->lines[] = __LINE__;
[3] $filename = "{$videoFileName}{$resolution}.{$_REQUEST['format']}";
$fsize = filesize($_FILES['video']['tmp_name']);
_error_log("aVideoEncoder.json: receiving video upload to {$filename} filesize=" . ($fsize) . " (" . humanFileSize($fsize) . ")" . json_encode($_FILES));
[4] $destinationFile = decideMoveUploadedToVideos($_FILES['video']['tmp_name'], $filename);
At [1], the video
parameter is read, and passed at [4] when calling decideMoveUploadedToVideos
. A resolution
parameter [2] also has to be passed and must match the following whitelist, defined in include_config.php
:
[240, 360, 480, 540, 720, 1080, 1440, 2160, 'offline', 'HD', 'SD', 'Low']
In addition, a format
parameter [4] has to be specified. If format
is “zip”, the code will enter the condition at [5] inside the function decideMoveUploadedToVideos()
:
function decideMoveUploadedToVideos($tmp_name, $filename, $type = "video")
{
...
$paths = Video::getPaths($filename, true);
$destinationFile = "{$paths['path']}{$filename}";
//$destinationFile = getVideosDir() . "{$filename}";
_error_log("decideMoveUploadedToVideos: {$filename}");
$path_info = pathinfo($filename);
[5] if ($type !== "zip" && $path_info['extension'] === 'zip') {
_error_log("decideMoveUploadedToVideos: ZIp file {$filename}");
$paths = Video::getPaths($path_info['filename']);
$dir = $paths['path'];
[6] unzipDirectory($tmp_name, $dir); // unzip it
[7] cleanDirectory($dir);
At [6] unzipDirectory
is called to unzip the file specified in the video
HTTP parameter, using $dir
as destination.
After the unzip completes, cleanDirectory
[7] is called.
The implementation details for unzipDirectory
and cleanDirectory
are irrelevant, it’s enough to understand that unzipDirectory
is extracting the input file into $dir
, while cleanDirectory
is removing all files inside $dir
whose extension are not in the following whitelist:
['key', 'm3u8', 'ts', 'vtt', 'jpg', 'gif', 'mp3', 'webm', 'webp']
This means that, after cleanDirectory
completes, it’s not possible to have any .php
file left inside $dir
, which could have been abused by an attacker to run arbitrary PHP code.
However, between the calls unzipDirectory
and cleanDirectory
exists a race condition: as the input zip file gets extracted, files already get created inside $dir
, hence they’re accessible via HTTP requests even if they don’t match the whitelist above, because cleanDirectory
hasn’t been called yet.
If an attacker manages to send an HTTP request as the unzipping process happens, this would allow to execute .php
files that are being extracted within $dir
.
In practice, however, .php
files cannot be executed inside $dir
. This is because $dir
is always a subdirectory of videos/
, and a rule in videos/.htaccess
prevents any PHP file from executing within videos/
.
Nonetheless, as described in TALOS-2025-????, that rule is incomplete because it still allows the usage of .phar
files, rather than .php
, to execute PHP code even inside the videos/
directory.
By chaining these two vulnerabilities, an attacker with video upload permissions could upload a zip file containing a .phar
file and execute arbitrary PHP code.
2025-07-10 - Vendor Disclosure
2025-07-14 - Vendor Patch Release
2025-07-24 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.