Talos Vulnerability Report

TALOS-2023-1880

WWBN AVideo aVideoEncoderReceiveImage.json.php image upload information disclosure vulnerability

January 10, 2024
CVE Number

CVE-2023-49864,CVE-2023-49863,CVE-2023-49862

SUMMARY

An information disclosure vulnerability exists in the aVideoEncoderReceiveImage.json.php image upload functionality of WWBN AVideo dev master commit 15fed957fb. A specially crafted HTTP request can lead to arbitrary file read.

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

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

CWE

CWE-73 - External Control of File Name or Path

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/aVideoEncoderReceiveImage.json.php file can be used to associate images and videos. This functionality does not need special configuration to be used. However, the user performing the request needs permission to upload videos.

A user can add several kinds of images: jpg, jgpSpectrum, gif, webp. All these fields are vulnerable. Let’s take the jpg one to explain the issue.

    ...
[1] $obj->video_id = $_REQUEST['videos_id'];
    $videoFileName = $video->getFilename();
    $paths = Video::getPaths($videoFileName, true);
    $destination_local = "{$paths['path']}{$videoFileName}";
    make_path($destination_local);
    ...
    $obj->jpgDest = "{$destination_local}.jpg";
[2] if (!file_exists($obj->jpgDest) || !fileIsAnValidImage($obj->jpgDest)) {

[3]     if (isValidURL($_REQUEST['downloadURL_image'])) {
[4]         $content = url_get_contents($_REQUEST['downloadURL_image']);
            $obj->jpgDestSize = _file_put_contents($obj->jpgDest, $content);
            _error_log("ReceiveImage: download {$_REQUEST['downloadURL_image']} to {$obj->jpgDest} " . humanFileSize($obj->jpgDestSize));

Based on the requested videos_id, a destination image file path is built. If such image does not exist or is invalid [2], the downloadURL_image is considered to fill the image contents. downloadURL_image has to be a valid URL (checked using isValidURL() [3]), and only if successfull is the image retrieved [4] and later associated with the video object.

    function isValidURL($url)
    {
        if (empty($url) || !is_string($url)) {
            return false;
        }
        if (preg_match("/^http.*/", $url) && filter_var($url, FILTER_VALIDATE_URL)) {
            return true;
        }
        return false;
    }

isValidURL is rather simple, returning true when both these conditions are true:

  • URL starts with “http”
  • URL passes PHP’s built-in FILTER_VALIDATE_URL check

Basically, $url needs to be a valid URL starting with “http”, which means schemas like file won’t work.

As previously mentioned, url_get_contents() is called after the checks above pass:

    function url_get_contents($url, $ctx = "", $timeout = 0, $debug = false, $mantainSession = false)
    {
        global $global, $mysqlHost, $mysqlUser, $mysqlPass, $mysqlDatabase, $mysqlPort;
        if (!isValidURLOrPath($url)) {
            _error_log('url_get_contents Cannot download ' . $url);
            return false;
        }
        if ($debug) {
            _error_log("url_get_contents: Start $url, $ctx, $timeout " . getSelfURI() . " " . getRealIpAddr() . " " . json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)));
        }

[5]     $response = try_get_contents_from_local($url);
        if (!empty($response)) {
            return $response;
        }
        ...

Recall that $url is fully controlled by the attacker. It is eventually passed to try_get_contents_from_local().

    function try_get_contents_from_local($url)
    {
[6]     if (substr($url, 0, 1) === '/') {
            // it is not a URL
            return file_get_contents($url);
        }
        global $global;

[7]     $parts = explode('/videos/', $url);
        if (!empty($parts[1])) {
            if (preg_match('/cache\//', $parts[1])) {
                $encoder = '';
            } else {
                $encoder = 'Encoder/';
            }
[8]         $tryFile = "{$global['systemRootPath']}{$encoder}videos/{$parts[1]}";
            if (file_exists($tryFile)) {
[9]             return file_get_contents($tryFile);
            }
        }
        return false;
    }

The check at [6] can’t be reached because our $url has to start with http.
However, if $url contains /videos/ [7], then the path on the right of it is extracted and used to build a local file path inside the videos directory. Note that anything on the left side of /videos/ (for example the hostname) is ignored.
If the file exists, it is read and its contents are returned [9] and eventually stored as an image associated with the video, which can be later retrieved with a simple HTTP request.

An attacker can exploit this issue to read any file in the system, by specifying an image URL such as http://123/videos/../../../../../../../etc/passwd, possibly also leading to privilege escalation.
Moreover, this issue allows an attacker to read configuration.php, which contains the salt used for various encryptions made by AVideo. Knowledge of the salt can be used to achieve administrator privileges, as shown in TALOS-2023-1900. For this specific case, the image URL should contain cache/, which allows for retrieving files directly in the videos directory. In practice, the image URL to read configuration.php would be http://123/videos/cache/../configuration.php.

CVE-2023-49862 - GIF image

Via the downloadURL_gifimage parameter, an attacker can specify an arbitrary file path to read from the webserver.

CVE-2023-49863 - WEBP image

Via the downloadURL_webpimage parameter, an attacker can specify an arbitrary file path to read from the webserver.

CVE-2023-49864 - JPG image

Via the downloadURL_image parameter, an attacker can specify an arbitrary file path to read from the webserver.

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.