Python · Active Directory

AD Recon Lite — Lightweight Active Directory enumeration and dangerous-config detector

A focused Python tool that connects to a domain controller over LDAP and flags the AD misconfigurations attackers actually target during enumeration

← Back to projects

AD Recon Lite

A small, fully readable AD audit tool built for home lab work and as a study aid for why these misconfigurations matter.


The Goal

The big AD recon tools — BloodHound, PingCastle, the original ADRecon — are excellent, but they're enormous. I wanted something I could fully reason about line by line, run against my home lab without a heavyweight setup, and use as a reference when studying for offensive security certs and CTF prep.

The other requirement was an offline mode. I wanted to be able to demo the detection logic and run the full test suite without having a live domain controller attached. That constraint turned out to be a useful design forcing function — it kept the code cleanly separated between the LDAP transport layer and the analysis layer.


How It Works

The tool connects to a domain controller over LDAP (or LDAPS), pulls user and computer records, and runs each record through a set of detection functions. Every function operates on a normalized list of Python dicts — the same format whether the records came from a live DC or a local JSON fixture.

The userAccountControl bitmask is where most of the interesting flags live. Windows encodes account state as a single integer, and AD misconfigurations are almost always just specific bits set or not set. I wrote a dedicated uac.py module to decode that bitmask into named flags and expose clean boolean helpers:

def asrep_roastable(uac: int | str | None) -> bool:
    return has_flag(uac, "DONT_REQ_PREAUTH")
 
def trusted_for_delegation(uac: int | str | None) -> bool:
    return has_flag(uac, "TRUSTED_FOR_DELEGATION")
 
def password_not_required(uac: int | str | None) -> bool:
    return has_flag(uac, "PASSWD_NOTREQD")

The detection layer (findings.py) calls these helpers, filters out disabled accounts automatically, and returns a sorted list of Finding dataclasses ranked by severity.

The CLI supports live LDAP, LDAPS, and a --json flag for piping output into a SOAR pipeline or just saving a structured audit log.


What It Enumerates

Finding Severity Why it matters
AS-REP roasting candidate high DONT_REQ_PREAUTH set — TGT can be requested without preauthentication and cracked offline
Kerberoastable user high User account with a registered SPN — service ticket can be requested and cracked offline
Unconstrained delegation critical (medium on DC) Any TGT from a user authenticating to this host can be forwarded anywhere
Constrained delegation medium Account can impersonate users to specific services
Constrained delegation with protocol transition high Same, but bypasses the requirement for the user to initiate authentication
Resource-based constrained delegation (RBCD) high msDS-AllowedToActOnBehalfOfOtherIdentity populated — classic RBCD attack path
Password not required critical PASSWD_NOTREQD set — account accepts empty password authentication
Password never expires low Common on legacy admin accounts — credential rotation not happening

Disabled accounts are excluded from all checks. A dangerous flag on a disabled account is not an active attack surface.


Tech & Tools

  • Python 3.10+ — dataclasses, type hints throughout
  • ldap3 — LDAP / LDAPS transport and record retrieval
  • pyproject.toml — installed as a proper package with an adrecon CLI entry point
  • unittest + unittest.mock — full test suite covering detection logic and UAC flag parsing
  • Offline mode — JSON fixture files let the tool run without a live DC, which made CI and demos much easier

The architecture is split cleanly: client.py handles LDAP connection and record fetching, uac.py handles bitmask decoding, findings.py has all the detection logic, and output.py / cli.py handle presentation. Nothing in the detection layer knows or cares whether the records came from a real DC.


What I Learned

On Active Directory internals:

  • The userAccountControl bitmask packs a lot of security-relevant state into one integer. Understanding which bit maps to which behavior made the Kerberos attack paths much more concrete — AS-REP roasting and Kerberoasting aren't abstract concepts when you're looking at the exact flag that enables them.
  • Unconstrained delegation on a non-DC is genuinely alarming. If an attacker can compromise that host, every TGT that's been handed to it is potentially reusable. The DC carve-out in the detection logic was a deliberate choice — flagging every DC as critical would just create noise.
  • RBCD is easy to miss because it lives in a separate attribute (msDS-AllowedToActOnBehalfOfOtherIdentity) rather than in the UAC bitmask, so tools that only check UAC will silently skip it.

On tool design:

  • Separating transport from analysis made the offline mode essentially free. The detection functions don't care where the dicts came from.
  • Exit codes matter for automation. Returning 1 on any critical or high finding means the tool can gate a pipeline without parsing output.
  • Building a smaller, fully readable version of an existing tool category is a solid way to actually understand what the bigger tools are doing under the hood.

Use Case

This was built primarily for home lab use — running against a Windows Server 2019 evaluation VM with intentionally misconfigured test accounts, and against GOAD (Game Of Active Directory) for a more realistic multi-domain scenario. The offline mode means I can also demo the full detection pipeline without spinning up a VM, which is useful for walkthroughs and writeups.