Talos Vulnerability Report

TALOS-2023-1885

WWBN AVideo import.json.php temporary copy unrestricted php file upload vulnerability

January 10, 2024
CVE Number

CVE-2023-49715

SUMMARY

A unrestricted php file upload vulnerability exists in the import.json.php temporary copy functionality of WWBN AVideo dev master commit 15fed957fb. A specially crafted HTTP request can lead to arbitrary code execution when chained with an LFI vulnerability. An attacker can send a series of HTTP requests to trigger this vulnerability.

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 dev master commit 15fed957fb

PRODUCT URLS

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

CVSSv3 SCORE

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

CWE

CWE-434 - Unrestricted Upload of File with Dangerous Type

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.

This page allows for uploading files with any content, but the extension needs to be one of the following (from include_config.php):

$global['allowedExtension'] = ['gif', 'jpg', 'mp4', 'webm', 'mp3', 'm4a', 'ogg', 'zip', 'm3u8'];

The extension is checked in objects/aVideoEncoder.json.php:

...
if (empty($_REQUEST['format']) || !in_array($_REQUEST['format'], $global['allowedExtension'])) {
    $obj->msg = "aVideoEncoder.json: ERROR Extension not allowed File {$_REQUEST['format']}";
    _error_log($obj->msg. ": " . json_encode($_REQUEST));
    die(json_encode($obj));
}
...

When uploading a video this way, the filename is chosen based on both format and resolution [1]:

    ...
    if (!empty($_FILES['video']['tmp_name'])) {
        $resolution = '';
        if (!empty($_REQUEST['resolution'])) {
            $resolution = "_{$_REQUEST['resolution']}";
        }
[1]     $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));
[2]     $destinationFile = decideMoveUploadedToVideos($_FILES['video']['tmp_name'], $filename);
    }
    ...

Given format=mp4 and resolution=1.php, the resulting filename will be _1.php.mp4.
decideMoveUploadedToVideos will eventually move the uploaded file to videos/_1.php/_1.php.mp4 [2].

While this operation allows us to specify an invalid resolution such as “1.php”, this won’t directly represent an exploitable issue.

The page objects/import.json.php, however, can be used to move the uploaded file to a path like /tmp/_1.php. Let’s see how this happens:

    ...
[3] if (!preg_match("/.*\\.mp4$/i", $_POST['fileURI'])) {
        return false;
    }
    ...
[4] $obj->fileURI = pathinfo($_POST['fileURI']);

    //get description
[5] $filename = $obj->fileURI['dirname'] . DIRECTORY_SEPARATOR . $obj->fileURI['filename'];
    ...
    $tmpDir = sys_get_temp_dir();
[6] $tmpFileName = $tmpDir.DIRECTORY_SEPARATOR.$obj->fileURI['filename'];
[7] $source = $obj->fileURI['dirname'] . DIRECTORY_SEPARATOR . $obj->fileURI['basename'];

[8] if (!copy($source, $tmpFileName)) {
        $obj->msg = "failed to copy $filename...\n";
        die(json_encode($obj));
    }
    ...

The fileURI parameter must end by .mp4 [3].
At [4] the path components of fileURI are extracted, and the filename value is created by stripping off the extension [5].
At [6], tmpFileName is made of the system’s temporary directory (tmp) and the filename without extension, while source is going to be the path specified by fileURI, including the extension [7].
Finally at [8], fileURI is copied over to /tmp, without its extension.

In conclusion, if we specify the videos/_1.php/_1.php.mp4 file just uploaded as fileURI, it will be moved to /tmp/_1.php. This effectively allows an attacker to upload an arbitrary .php file to a known location, which can lead to arbitrary code execution when used in conjunction with a local file inclusion vulnerability. This is indeed the case, as demonstrated in TALOS-2023-1886.

It’s important to note, however, that there is a complication, as right at the end of objects/import.json.php, the following include directive is executed:

require_once $global['systemRootPath'] . 'view/mini-upload-form/upload.php';

Inside upload.php, the temporary file is moved to its final location, meaning it gets removed from /tmp, thanks to this line:

decideMoveUploadedToVideos($tmp_name, $filenameMP4, $video->getType());

Despite this, the /tmp/_1.php file will be available for a short period of time, which is enough for an attacker to execute a series of commands.

TIMELINE

2023-12-14 - Vendor Disclosure
2023-12-15 - Vendor Patch Release
2024-01-10 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.