CVE-2026-6249: Vvveb CMS 1.0.8 RCE via .phtml Extension Bypass
Vvveb CMS 1.0.8 media upload handler fails to block .phtml extensions, allowing authenticated attackers to upload PHP webshells and achieve full server compromise via HTTP request.
Vvveb CMS is a popular tool that helps people build and manage websites without coding. A serious security flaw has been discovered in how it handles file uploads — specifically, when users upload images and media to their sites.
Here's the problem: The system tries to block dangerous files by checking file extensions (the ".jpg" or ".pdf" at the end of filenames). But it has a blind spot. It fails to block files ending in ".phtml," which is a format that acts like executable code on web servers. Think of it like a bank that checks if you're sneaking in a gun, but forgets to check if you're carrying a crossbow.
An attacker with a user account can exploit this by uploading a disguised malicious file with a .phtml extension. Once uploaded, they can trigger it to run commands on the entire web server — potentially stealing data, installing malware, or taking over the site completely. It's the digital equivalent of someone gaining the master key to your building.
Who's at risk? Anyone running Vvveb CMS version 1.0.8 with user accounts is vulnerable. Website owners using this platform should treat this as urgent.
What you should do: First, update Vvveb CMS immediately if a patch is available. Second, review who has upload permissions on your site and remove accounts you don't recognize. Third, check your site's media folder for any suspicious .phtml files. If you're not comfortable doing this yourself, contact your hosting provider or a technical expert — this isn't the time to wait and see.
Want the full technical analysis? Click "Technical" above.
CVE-2026-6249 is an authenticated remote code execution vulnerability in Vvveb CMS 1.0.8. The media upload handler enforces file type restrictions via a deny-list of dangerous extensions, but the list is incomplete: .phtml is not present. On any Apache or PHP-FPM configuration that maps .phtml to the PHP interpreter — which is the default on most distributions — an attacker with editor-level credentials can upload an arbitrary PHP webshell, receive a public URL for the file, and trigger execution by issuing a single HTTP GET request.
CVSS 8.8 (HIGH) reflects the low privilege requirement (authenticated, any role with media access) and the direct path to OS command execution with web server process permissions.
Affected Component
The vulnerable logic lives in the media upload controller. Based on static analysis of the 1.0.8 release the relevant file is app/controller/admin/media.php, specifically the upload() method of the MediaController class. The uploaded file is stored under the web root at a predictable path beneath public/files/, making the upload directly addressable over HTTP with no secondary access control.
Root Cause Analysis
The upload handler builds an extension deny-list as a plain PHP array and compares the lowercased incoming extension against it. The comparison is exact-match only — no MIME-type inspection, no content sniffing, no allowlist. The deny-list omits every non-canonical PHP extension that stock Apache and PHP-FPM will still parse.
// app/controller/admin/media.php — MediaController::upload()
// Reconstructed from source; variable names preserved.
public function upload() {
$file = $_FILES['file'];
$name = $file['name']; // attacker-controlled
$tmp = $file['tmp_name'];
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
// BUG: deny-list is incomplete; .phtml / .phar / .php5 etc. are absent
$denied = ['php', 'php3', 'php4', 'exe', 'sh', 'bat', 'py', 'pl'];
if (in_array($ext, $denied)) {
return $this->error('File type not allowed'); // never reached for .phtml
}
$dest = PUBLIC_PATH . 'files/' . basename($name); // web-accessible path
move_uploaded_file($tmp, $dest); // file lands under webroot
return $this->json(['url' => '/files/' . basename($name)]);
}
Root cause: The extension deny-list in MediaController::upload() omits .phtml (and other PHP-parsed extensions), allowing PHP execution of attacker-controlled content placed directly under the web root.
Apache's default MIME configuration in mime.types maps application/x-httpd-php to the extensions php php3 php4 php5 phtml. Because the deny-list only blocks four of these, any of the omitted variants bypasses the check. .phtml is the most portable choice — it is enabled by default without any additional AddHandler directives on Debian, Ubuntu, and RHEL Apache packages.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker authenticates to Vvveb admin panel (any account with media upload access).
2. Craft a multipart POST to /admin/media/upload:
filename: "shell.phtml"
content-type: "image/jpeg" (not validated server-side)
body:
3. Server performs strtolower("phtml") -> "phtml".
in_array("phtml", $denied) returns FALSE.
move_uploaded_file() writes payload to PUBLIC_PATH/files/shell.phtml.
4. Server responds with JSON: {"url":"/files/shell.phtml"}
5. Attacker issues:
GET /files/shell.phtml?cmd=id HTTP/1.1
Host: target.example.com
6. Apache matches .phtml -> PHP handler, interprets file.
system("id") executes as www-data (or equivalent web process user).
Response body: "uid=33(www-data) gid=33(www-data) groups=33(www-data)"
7. Pivot to persistent access: write SSH key, drop cron reverse shell,
or read /etc/passwd + application database credentials.
This is a logic vulnerability rather than a memory corruption primitive, so no heap diagram applies. The relevant "layout" is the filesystem state after successful exploitation:
FILESYSTEM STATE — pre-upload:
/var/www/vvveb/public/files/
├── image001.jpg
└── logo.png
FILESYSTEM STATE — post-upload (shell landed):
/var/www/vvveb/public/files/
├── image001.jpg
├── logo.png
└── shell.phtml <-- PHP-executable, world-readable, owned by www-data
APACHE HANDLER RESOLUTION for /files/shell.phtml:
[DocumentRoot]/files/shell.phtml
│
└─► mod_php / php-fpm via .phtml MIME mapping
│
└─► PHP engine executes file content as PHP source
│
└─► system($_GET['cmd']) -> fork/exec -> OS shell
HTTP REQUEST → OS COMMAND PATH:
Client GET /files/shell.phtml?cmd=whoami
→ Apache worker reads file
→ PHP engine: system("whoami")
→ popen("whoami", "r")
→ kernel execve("/bin/sh", ["-c","whoami"], envp)
→ stdout captured → HTTP response body
Patch Analysis
The correct fix is to invert the validation model: replace the deny-list with an allowlist of known-safe media extensions, and independently verify MIME type against file magic bytes. A minimal diff against the vulnerable code:
// BEFORE (vulnerable — deny-list, 1.0.8):
$denied = ['php', 'php3', 'php4', 'exe', 'sh', 'bat', 'py', 'pl'];
if (in_array($ext, $denied)) {
return $this->error('File type not allowed');
}
move_uploaded_file($tmp, $dest);
// AFTER (patched — allowlist + MIME validation):
$allowed_ext = [
'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg',
'mp4', 'webm', 'ogg', 'mp3',
'pdf', 'zip', 'tar', 'gz',
];
$allowed_mime = [
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
'video/mp4', 'audio/mpeg', 'application/pdf',
];
if (!in_array($ext, $allowed_ext)) {
return $this->error('File type not allowed');
}
// Secondary: validate magic bytes via finfo, not $_FILES['type']
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$detected = $finfo->file($tmp);
if (!in_array($detected, $allowed_mime)) {
return $this->error('MIME type mismatch');
}
// Strip original extension and assign a safe one
$safe_name = bin2hex(random_bytes(8)) . '.' . $ext;
$dest = PUBLIC_PATH . 'files/' . $safe_name;
move_uploaded_file($tmp, $dest);
Beyond the PHP-level fix, the public/files/ directory should carry an .htaccess or equivalent Nginx rule that explicitly prevents PHP execution regardless of extension:
Defence-in-depth: configure the upload directory as a separate vhost or serve it from a domain that carries no PHP handler at all (static.example.com), eliminating the class of vulnerability entirely for that origin.
Detection and Indicators
Web server access logs — look for POST requests to the media upload endpoint followed by a GET to a .phtml (or .php5, .phar, .shtml) file under /files/:
POST /admin/media/upload HTTP/1.1 [any authenticated session]
GET /files/[a-z0-9]{1,32}\.phtml [execution trigger]
Filesystem — any PHP-parseable file in public/files/ is anomalous:
Web application firewall — block multipart uploads whose filename parameter matches /\.(php[0-9]?|phtml|phar|shtml|htaccess)$/i at the WAF layer as a compensating control pending patch deployment.
SIEM rule (Sigma-style) — correlate cs-uri-stem containing /files/ and cs-uri-stem ending in .phtml with sc-status 200 within 60 seconds of a POST to /admin/media/upload from the same source IP.
Remediation
Immediate: Deploy .htaccess with php_flag engine off inside public/files/ to break the execution chain without requiring a code change.
Short-term: Upgrade to a patched release once available from the Vvveb project; confirm the patch replaces the deny-list with an allowlist as described above.
Medium-term: Serve all user-uploaded content from a separate origin with no PHP handler; randomize stored filenames to prevent enumeration.
Audit scope: Any Vvveb installation running 1.0.8 or earlier with media upload enabled for non-superadmin roles is affected. Installations where uploads are restricted to superadmin only reduce the effective attacker surface but are not immune to session hijacking or CSRF-assisted upload.