PT-2026-35120 · Packagist · Wwbn Avideo
Published
2026-04-14
·
Updated
2026-04-14
CVSS v3.1
6.5
Medium
| Vector | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
Summary
The directory traversal fix introduced in commit 2375eb5e0 for
objects/aVideoEncoderReceiveImage.json.php only checks the URL path component (via parse url($url, PHP URL PATH)) for .. sequences. However, the downstream function try get contents from local() in objects/functionsFile.php uses explode('/videos/', $url) on the full URL string including the query string. An attacker can place the /videos/../../ traversal payload in the query string to bypass the security check and read arbitrary files from the server filesystem.Details
The security fix at commit 2375eb5e0 added a traversal check at
objects/aVideoEncoderReceiveImage.json.php:49:php
$decodedPath = urldecode((string)(parse url($ REQUEST[$value], PHP URL PATH) ?? ''));
if (strpos($decodedPath, '..') !== false) {
unset($ REQUEST[$value]);
}This only inspects the path component of the URL. For a URL like
http://TARGET/x?a=/videos/../../etc/passwd, parse url() returns /x as the path — no .. is found.The URL then passes through
isValidURL() (objects/functions.php:4203) which accepts it because FILTER VALIDATE URL considers .. in query strings valid per RFC 3986.It also passes
isSSRFSafeURL() (objects/functions.php:4264) because the host matches webSiteRootURL, causing an early return at line 4294.The URL reaches
url get contents() (objects/functions.php:1938) which calls try get contents from local() (objects/functionsFile.php:214):php
function try get contents from local($url)
{
// ...
$parts = explode('/videos/', $url);
if (!empty($parts[1])) {
// ...
$tryFile = "{$global['systemRootPath']}{$encoder}videos/{$parts[1]}";
if (file exists($tryFile)) {
return file get contents($tryFile);
}
}
return false;
}explode('/videos/', $url) operates on the entire URL string including the query string. For the malicious URL, $parts[1] becomes ../../../../../../etc/passwd, constructing a path like /var/www/html/AVideo/Encoder/videos/../../../../../../etc/passwd which PHP's filesystem functions resolve to /etc/passwd.The file content is returned to the caller and written to the video's thumbnail path via
file put contents(). All four downloadURL * parameters (downloadURL image, downloadURL gifimage, downloadURL webpimage, downloadURL spectrumimage) are affected.PoC
Prerequisites: An authenticated AVideo user account with upload permission and an existing video they own (with known
videos id).-
Identify the AVideo instance's domain (e.g.,
https://avideo.example.com). -
Send a POST request to the ReceiveImage endpoint with the traversal payload in the query string:
bash
curl -s -b "PHPSESSID=<session cookie>"
"https://avideo.example.com/objects/aVideoEncoderReceiveImage.json.php"
-d "videos id=<YOUR VIDEO ID>"
-d "downloadURL image=http://avideo.example.com/x?a=/videos/../../../../../../etc/passwd"-
The response will include
jpgDestSizeindicating the file was read and written (confirming file existence and revealing file size). -
For files that pass image validation (e.g., other users' uploaded images at known paths), the content persists at the video's thumbnail URL and can be retrieved:
bash
curl -s "https://avideo.example.com/videos/<videoFileName>.jpg"- Non-image files (e.g.,
/etc/passwd, configuration files) are written but then deleted bydeleteInvalidImage(). However, file existence and size are still leaked, and a race condition exists between the write and the deletion.
Impact
- Arbitrary file read: An authenticated user with upload permission can read any file on the server filesystem that the web server process has access to. Files that pass image validation (PNG/JPEG/GIF) are fully exfiltrable via the video thumbnail URL.
- Information disclosure: For non-image files, file existence and size are leaked through the
jpgDestSizeresponse field. - Configuration exposure: Server configuration files, database credentials (
videos/configuration.php), and other sensitive data can be targeted. While PHP files would be deleted bydeleteInvalidImage, there is a race window between write and deletion. - Bypass of security fix: This directly bypasses the path traversal mitigation added in commit 2375eb5e0.
Recommended Fix
The
.. check in aVideoEncoderReceiveImage.json.php should inspect the full URL (after URL-decoding), not just the path component. Additionally, try get contents from local() should validate its derived path.Fix 1 — Check the full URL in ReceiveImage (objects/aVideoEncoderReceiveImage.json.php:49):
php
// Replace:
$decodedPath = urldecode((string)(parse url($ REQUEST[$value], PHP URL PATH) ?? ''));
if (strpos($decodedPath, '..') !== false) {
// With:
$decodedFull = urldecode((string)$ REQUEST[$value]);
if (strpos($decodedFull, '..') !== false) {Fix 2 — Add path validation in try get contents from local (objects/functionsFile.php:229):
php
$tryFile = "{$global['systemRootPath']}{$encoder}videos/{$parts[1]}";
// Add traversal check:
$realTryFile = realpath($tryFile);
$videosDir = realpath("{$global['systemRootPath']}{$encoder}videos/");
if ($realTryFile === false || !str starts with($realTryFile, $videosDir)) {
return false;
}
if (file exists($tryFile)) {
return file get contents($tryFile);
}Fix
Path traversal
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Wwbn Avideo