Security Considerations When Using Doom: A Complete Guide to Safe ADCS Enumeration

Doom is a Python-based Active Directory Certificate Services (ADCS) enumeration tool that requires careful handling of credentials, LDAP connections, and TLS validation to prevent credential exposure and man-in-the-middle attacks.

Doom, developed by 000pp, is an open-source utility designed for security professionals conducting ADCS reconnaissance. While it provides valuable insights into certificate template configurations, several implementation details in the codebase introduce significant security considerations when using Doom in production environments. Understanding these risks is essential for maintaining operational security during penetration testing or red team exercises.

Credential Handling Risks in Doom

Clear-Text Password Storage in Memory

The authentication flow in Doom begins at src/doom/screens/login_screen.py, where the UI captures credentials using Input(..., password=True). While this masks the display, the password is stored as a clear-text string in a plain dictionary within the LoadingScreen class before being passed to the LDAP connector.

This implementation means passwords exist in memory in an unencrypted state and may be written to logs if an exception occurs during the connection phase. The current codebase does not implement secure memory clearing after authentication attempts.

Mitigation Strategies for Secure Credential Management

To reduce exposure when using Doom, implement the following practices:

  • Clear variables immediately after use: Explicitly delete password variables from memory using del password after the LDAP connection is established.
  • Integrate secure credential vaults: Replace raw input methods with Windows Credential Manager or similar secure storage solutions instead of passing clear-text passwords through the UI layer.

LDAP Connection Security Vulnerabilities

Unencrypted LDAP Fallback on Port 389

The get_ldap_connection() function in src/doom/protocols/ldap.py attempts an unencrypted LDAP bind on port 389 before falling back to LDAPS on port 636. The code explicitly sets use_ssl=False for the initial connection attempt.

If the Active Directory server permits simple LDAP on port 389, credentials are transmitted in clear text across the network, exposing them to packet sniffers and network-level attackers. This fallback behavior creates an unnecessary attack surface.

Insecure TLS Configuration and Certificate Validation

When LDAPS is used, the TLS configuration in src/doom/protocols/ldap.py (lines 15-18) disables critical security checks:

validate=ssl.CERT_NONE
ciphers="ALL:@SECLEVEL=0"

Setting validate=ssl.CERT_NONE disables server certificate verification, making the tool vulnerable to man-in-the-middle (MITM) attacks. The cipher override "ALL:@SECLEVEL=0" enables insecure cryptographic algorithms that modern security standards have deprecated.

To secure these connections, modify the code to use validate=ssl.CERT_REQUIRED and supply a proper CA bundle, removing the insecure cipher override entirely.

Password Pre-Hash Detection Logic

The LDAP implementation contains logic that detects if a supplied password is a 32-character hexadecimal string. If detected, the code prefixes it with the NTLM hash marker aad3b435b51404eeaad3b435b51404ee (found in src/doom/protocols/ldap.py, lines 12-14).

This behavior introduces two risks: an attacker could intentionally supply a hash to bypass NTLM challenges, or the code could unintentionally treat a user-chosen password as a hash, weakening authentication strength. The tool should require an explicit flag for "hashed password mode" and validate that hashes truly originate from NTLM computations.

Information Disclosure Risks

Verbose Error Messages and LDAP Information Leakage

Error handling in src/doom/protocols/ldap.py re-raises exceptions with raw error text: raise Exception(f"LDAP bind error: {e}"). Detailed LDAP error messages may reveal sensitive configuration details, such as which authentication mechanisms are permitted or the internal structure of the directory.

Operators should modify error handling to log detailed messages at a debug level only, presenting generic error messages to end users.

ADCS Template Enumeration Output

The enumerate_templates() function in src/doom/modules/enumerate_templates.py returns all raw LDAP attributes, including potentially sensitive flags like msPKI-Enrollment-Flag. While this is the tool's intended purpose, the output provides attackers with a complete map of privileged certificate templates, facilitating privilege escalation attacks.

Run Doom only in controlled, authorized environments. Consider adding filters to hide high-risk attributes unless explicitly requested by the operator.

Supply Chain and Dependency Security

The project declares dependencies in pyproject.toml and pulls code from external sources, including Certipy structs. Compromised third-party packages could introduce backdoors or malicious code into the enumeration workflow.

Mitigate supply chain risks by pinning exact dependency versions in pyproject.toml, verifying package integrity with cryptographic hashes, and using reproducible build environments such as containerized deployments.

Secure Implementation Examples

Replace the default insecure LDAP connection with this hardened implementation:

import ssl
import ldap3

def get_secure_ldap_connection(host: str, username: str, password: str, domain: str):
    """Enforce LDAPS with certificate validation."""
    user = f"{domain}\\{username}"

    # Force LDAPS only

    tls = ldap3.Tls(
        validate=ssl.CERT_REQUIRED,
        # Use system default CA bundle

        ca_certs_file=ssl.get_default_verify_paths().cafile,
        version=ssl.PROTOCOL_TLS_CLIENT,
    )
    ldaps_server = ldap3.Server(
        f"ldaps://{host}",
        port=636,
        use_ssl=True,
        get_info=ldap3.ALL,
        tls=tls,
    )
    conn = ldap3.Connection(
        server=ldaps_server,
        user=user,
        password=password,
        authentication=ldap3.NTLM,
        auto_bind=True,
        raise_exceptions=True,
    )
    base_dn = conn.server.info.other.get("defaultNamingContext", [""])[0]
    return conn, base_dn

Clear sensitive data from memory immediately after authentication:

password = self.query_one("#password-input").value
login_data = {"ip": ip, "domain": domain, "username": username, "password": password}

# ... use login_data ...

del password   # remove clear‑text password from locals

Summary

  • Credential exposure: Doom stores passwords in clear text in memory during the authentication flow in src/doom/screens/login_screen.py and src/doom/screens/loading_screen.py.
  • Unencrypted transmission: The default get_ldap_connection() function attempts unencrypted LDAP on port 389 before trying LDAPS, risking clear-text credential exposure.
  • Disabled TLS validation: The current implementation uses validate=ssl.CERT_NONE and insecure ciphers, making connections vulnerable to MITM attacks.
  • Information leakage: Verbose error messages and raw LDAP attribute output in src/doom/modules/enumerate_templates.py may reveal sensitive ADCS configuration details.
  • Supply chain risks: External dependencies in pyproject.toml require version pinning and integrity verification to prevent backdoor injection.

Frequently Asked Questions

Is Doom safe to use in production Active Directory environments?

Doom is designed for authorized security assessments, but its default configuration introduces significant risks for production environments. The tool transmits credentials over potentially unencrypted channels and stores passwords in clear text in memory. Only use Doom in isolated lab environments or with explicit authorization, and apply the security hardening measures described in this guide before connecting to production domains.

How does Doom handle password storage securely?

Currently, Doom does not handle password storage securely. The login_screen.py module captures passwords using Input(..., password=True), which only masks the UI display, then stores the value in a plain dictionary passed to LoadingScreen. The password remains in memory as a clear-text string until the Python garbage collector reclaims it. Users should manually clear password variables using del password immediately after authentication and consider integrating Windows Credential Manager or similar secure vaults instead of manual entry.

What are the risks of using Doom over unencrypted LDAP?

The get_ldap_connection() function in src/doom/protocols/ldap.py attempts a connection to port 389 with use_ssl=False before falling back to LDAPS on port 636. If the Active Directory server accepts simple LDAP binds, this transmits credentials in clear text across the network, exposing them to packet sniffers and network-level attackers. Additionally, the TLS configuration disables certificate validation with validate=ssl.CERT_NONE, making the tool vulnerable to man-in-the-middle attacks even when LDAPS is used.

How can I verify the integrity of Doom's dependencies?

Doom relies on external packages including ldap3 and code from Certipy structs, declared in pyproject.toml. To verify integrity, pin exact dependency versions in your pyproject.toml or requirements.txt using hash verification. Use tools like pip install --require-hashes or containerized environments with reproducible builds to ensure that compromised third-party packages cannot introduce backdoors into your ADCS enumeration workflow.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →