Talos Vulnerability Report

TALOS-2025-2212

WWBN AVideo aVideoEncoder.json.php unzip race condition vulnerability

July 24, 2025
CVE Number

CVE-2025-25214

SUMMARY

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.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

AVideo - https://github.com/WWBN/AVideo

CVSSv3 SCORE

8.8 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-362 - Concurrent Execution using Shared Resource with Improper Synchronization (‘Race Condition’)

DETAILS

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.

TIMELINE

2025-07-10 - Vendor Disclosure
2025-07-14 - Vendor Patch Release
2025-07-24 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.