Talos Vulnerability Report

TALOS-2023-1897

WWBN AVideo userRecoverPass.php captcha validation recovery notification bypass vulnerability

January 10, 2024
CVE Number

CVE-2023-50172

SUMMARY

A recovery notification bypass vulnerability exists in the userRecoverPass.php captcha validation functionality of WWBN AVideo dev master commit 15fed957fb. A specially crafted HTTP request can lead to the silent creation of a recovery pass code for any user.

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

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

CWE

CWE-640 - Weak Password Recovery Mechanism for Forgotten Password

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.

AVideo allows users to recover their account access when they forget their password. This functionality is implemented by objects/userRecoverPass.php:

    ...
    $user = new User(0, $_REQUEST['user'], false);
[1] if (!(!empty($_REQUEST['user']) && !empty($_REQUEST['recoverpass']))) {
        $obj = new stdClass();
        $obj->user = $_REQUEST['user'];
[2]     $obj->captcha = $_REQUEST['captcha'];
[2]     $obj->reloadCaptcha = false;
        $obj->session_id = session_id();
        
        header('Content-Type: application/json');
[3]     if(empty($user->getStatus())){
            $obj->error = __("User not found");
            die(json_encode($obj));
        }
[3]     if($user->getStatus() !== 'a'){
            $obj->error = __("The user is not active");
            die(json_encode($obj));
        }
        if (!empty($user->getEmail())) {
[4]         $recoverPass = $user->setRecoverPass();
[6]         if (empty($_REQUEST['captcha'])) {
                $obj->error = __("Captcha is empty");
            } else {
[7]             if ($user->save()) {
                    require_once 'captcha.php';
[8]                 $valid = Captcha::validation($_REQUEST['captcha']);
[9]                 if ($valid) {
                        //Create a new PHPMailer instance
                        $mail = new \PHPMailer\PHPMailer\PHPMailer();
                        setSiteSendMessage($mail);
                        //Set who the message is to be sent from
                        $mail->setFrom($config->getContactEmail(), $config->getWebSiteTitle());
                        //Set who the message is to be sent to
                        $mail->addAddress($user->getEmail());
                        //Set the subject line
                        $mail->Subject = __('Recover Pass from') .' '. $config->getWebSiteTitle();

                        $msg = __("You asked for a recover link, click on the provided link") . " <a href='{$global['webSiteRootURL']}recoverPass?user={$_REQUEST['user']}&recoverpass={$recoverPass}'>" . __("Reset password") . "</a>";

                        $mail->msgHTML($msg);

                        //send the message, check for errors
[10]                    if (!$mail->send()) {
                            $obj->error = __("Message could not be sent") . " " . $mail->ErrorInfo;
                        } else {
                            $obj->success = __("Message sent");
                        }
                    } else {
                        $obj->error = __("Your code is not valid");
                        $obj->reloadCaptcha = true;
                    }
                } else {
                    $obj->error = __("Recover password could not be saved!");
                }
            }
    ...

The purpose of the code block above is to create a recovery code and send it as a link to the user’s email address. Once the user receives the link, they can click it to reach an interface where they can set a new password for their account.

The code block can be entered when user is set but recoverpass is not [1]. Then, user and captcha parameters are retrieved from the request [2].
If the user is valid [3], a recovery code ($recoveryPass) is generated via setRecoverPass() [4].

public function setRecoverPass($forceChange = false)
{
    // let the same recover pass if it was 10 minutes ago
    if (!$this->isRecoverPassExpired($this->recoverPass) && empty($forceChange) && !empty($this->recoverPass) && !empty($recoverPass) && !empty($this->modified) && strtotime($this->modified) > strtotime("-10 minutes")) {
        return $this->recoverPass;
    }
[5] $this->recoverPass = $this->createRecoverPass();
    return $this->recoverPass;
}

The details for the code generation are not important for this advisory. Notice however that the recoverPass is set [5] in the user object.

Continuing on in the block above, if captcha is not empty [6], the user object will be saved [7] (to database). Afterward, this the captcha is verified [8] (the captcha is previously set in session by requesting objects/getCaptcha.php).
Only if the captcha is valid [9] is the recovery email composed and sent to the user [10].

The issue in this logic is that the recoverPass is set despite the captcha being wrong, as no action should be taken when the captcha fails. This also allows an attacker to set a recoveryPass in the user’s object, without the user being notified. If an attacker can then guess the recoveryPass, they can use it to set a new password for any user. This has been demonstrated in TALOS-2023-1896 and TALOS-2023-1900.

Exploit Proof of Concept

This proof-of-concept sets a recoverPass for the user admin object, without sending the code via email:

echo "Generate captcha so palavra in session is populated"
curl -k -s -c cookies.txt "https://localhost/captcha"

echo -e "Create recovery code even if a wrong captcha is submitted"
curl -k -s -b cookies.txt "https://localhost/objects/userRecoverPass.php?user=admin&captcha=wrongcaptcha"
TIMELINE

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

Credit

Discovered by Claudio Bozzato of Cisco Talos.