CVE-2022-32770,CVE-2022-32772,CVE-2022-32771
A cross-site scripting (xss) vulnerability exists in the footer alerts functionality of WWBN AVideo 11.6 and dev master commit 3f7c0364. A specially-crafted HTTP request can lead to arbitrary Javascript execution. An attacker can get an authenticated user to send a crafted 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.6 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CWE-79 - Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)
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 view logic in AVideo normally includes a generic view/include/footer.php
file to handle the pages footer.
The code in footer.php
does not sanitize variables correctly, and is thus vulnerable to multiple reflected cross-site scripting (XSS) issues. These issues can be used by an attacker, in the worst case, to take over an administrator account, for example by tricking an administrator into clicking on a link that triggers the XSS.
The issue in footer.php
starts with these lines:
<script>
$(function () {
<?php
showAlertMessage();
?>
});
</script>
This defines a Javascript function that shows an alert message. For example, with the request https://192.168.1.200/view/charts.php?msg=test
, we end up with the following code:
<script>
$(function () {
/** showAlertMessage **/
avideoAlertInfo("test");window.history.pushState({}, document.title, "https://192.168.1.200/view/charts.php?msg=test");
/** showAlertMessage END **/ });
</script>
The function showAlertMessage
is defined in objects/functions.php
:
function showAlertMessage() {
...
// [1]
$joinString = ['error', 'msg', 'success'];
foreach ($joinString as $value) {
if (!empty($_GET[$value]) && is_array($_GET[$value])) {
$_GET[$value] = array_unique($_GET[$value]);
$newStr = [];
foreach ($_GET[$value] as $value2) {
if (!empty($value2)) {
$newStr[] = $value2;
}
}
$_GET[$value] = implode("<br>", $newStr);
}
}
// [2]
$check = ['error', 'msg', 'success', 'toast'];
foreach ($check as $value) {
if (!empty($_GET[$value])) {
if (is_array($_GET[$value])) {
$newStr = [];
foreach ($_GET[$value] as $key => $value2) {
$value2 = str_replace('"', "''", $value2);
if (!empty($value2)) {
$newStr[] = $value2;
}
}
$_GET[$value] = $newStr;
} else {
$_GET[$value] = str_replace('"', "''", $_GET[$value]);
}
}
}
// [3]
echo "/** showAlertMessage **/", PHP_EOL;
if (!empty($_GET['error'])) {
echo 'avideoAlertError("' . $_GET['error'] . '");';
echo 'window.history.pushState({}, document.title, "' . getSelfURI() . '");';
}
if (!empty($_GET['msg'])) {
echo 'avideoAlertInfo("' . $_GET['msg'] . '");';
echo 'window.history.pushState({}, document.title, "' . getSelfURI() . '");';
}
if (!empty($_GET['success'])) {
echo 'avideoAlertSuccess("' . $_GET['success'] . '");';
echo 'window.history.pushState({}, document.title, "' . getSelfURI() . '");';
}
if (!empty($_GET['toast'])) {
if (!is_array($_GET['toast'])) {
$_GET['toast'] = [$_GET['toast']];
} else {
$_GET['toast'] = array_unique($_GET['toast']);
}
foreach ($_GET['toast'] as $key => $value) {
$hideAfter = strlen(strip_tags($value)) * 150;
if ($hideAfter < 3000) {
$hideAfter = 3000;
}
if ($hideAfter > 15000) {
$hideAfter = 15000;
}
echo '$.toast({
text: "' . $value . '",
hideAfter: ' . $hideAfter . ' // in milli seconds
});console.log("Toast Hide after ' . $hideAfter . '");';
}
echo 'window.history.pushState({}, document.title, "' . getSelfURI() . '");';
}
echo PHP_EOL, "/** showAlertMessage END **/";
}
At [1], the error
, msg
and success
variables are concatenated if they’re passed as arrays.
At [2], double quotes are replaced with two single quotes for error
, msg
, success
and toast
parameters.
At [3], for each of the 4 parameters, some Javascript is output. Of these 4 parameters, only error
is output after proper sanitization.
Sanitization happens in objects/security.php
, which is included in every request:
...
// filter some security here
$securityFilter = ['error', 'catName', 'type', 'channelName', 'captcha', 'showOnly', 'key', 'link', 'email', 'country', 'region', 'videoName'];
$securityFilterInt = ['isAdmin', 'priority', 'totalClips', 'rowCount'];
$securityRemoveSingleQuotes = ['search', 'searchPhrase', 'videoName', 'databaseName', 'sort', 'user', 'pass', 'encodedPass', 'isAdmin', 'videoLink', 'video_password'];
$securityRemoveNonChars = ['resolution', 'format', 'videoDirectory'];
$filterURL = ['videoURL', 'siteURL', 'redirectUri', 'encoderURL'];
...
$scanVars = ['_GET', '_POST', '_REQUEST'];
foreach ($scanVars as $value) {
$scanThis = &$$value;
...
foreach ($securityFilter as $value) {
if (!empty($scanThis[$value])) {
// [4]
$scanThis[$value] = str_replace(['\\', "--", "'", '"', """, "'", "%23", "%5c", "#"], ['', '', '', '', '', '', '', '', ''], xss_esc($scanThis[$value]));
}
}
...
}
error
is present in the $securityFilter
array, so any time a request with an error
parameter is sent, the sanitization at [4] is applied.
There are no entries for msg
, success
or toast
. All those parameters are only subject to the sanitization at [2] (double quotes replacement), which can be bypassed, thus making the three parameters vulnerable to XSS at [3].
The toast
parameter is inserted into the document with insufficient sanitization:
echo '$.toast({
text: "' . $value . '",
hideAfter: ' . $hideAfter . ' // in milli seconds
});console.log("Toast Hide after ' . $hideAfter . '");';
While an attacker can’t use double quotes to escape the string definition, one can simply inject </script><script>
to start a new script and inject any Javascript.
The success
parameter is inserted into the document with insufficient sanitization:
if (!empty($_GET['success'])) {
echo 'avideoAlertSuccess("' . $_GET['success'] . '");';
echo 'window.history.pushState({}, document.title, "' . getSelfURI() . '");';
}
While an attacker can’t use double quotes to escape the string definition, one can simply inject </script><script>
to start a new script and inject any Javascript.
The msg
parameter is inserted into the document with insufficient sanitization:
if (!empty($_GET['error'])) {
echo 'avideoAlertError("' . $_GET['error'] . '");';
echo 'window.history.pushState({}, document.title, "' . getSelfURI() . '");';
}
While an attacker can’t use double quotes to escape the string definition, one can simply inject </script><script>
to start a new script and inject any Javascript.
This is a proof-of-concept for the msg
parameter:
curl https://192.168.1.200/index.php?msg=</script><script>alert(document.cookie);</script>
One can also exploit the array concatenation happening at [1] to trigger the same XSS (note this is not possible for the toast
parameter):
curl https://192.168.1.200/view/charts.php?msg[0]=</script><script>/*&msg[1]=*/alert(1)//&msg[2]=</script>
Vendor confirms issues fixed on July 7th 2022
2022-07-05 - Vendor Disclosure
2022-07-07 - Vendor Patch Release
2022-08-16 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.