Skip to content
ADscan Docs

🧠 Hardening Detection

How ADscan automatically detects defensive controls (NTLM disabled, AES-only Kerberos, LDAP signing, channel binding, SMB signing, LDAPS) and adapts in real time

ADscan's Hardening Detection is a posture-aware authentication layer that learns the defensive controls of every domain it touches and adapts on the fly. Instead of speculatively retrying every credential and transport combination on every operation, ADscan observes the first signal a Domain Controller emits, persists the conclusion in the workspace, and skips impossible attempts for the rest of the engagement.

The result: fewer retries, faster scans, cleaner output, and a tangible artefact for the client showing exactly which hardening controls their environment enforces.

Why this matters

Most AD pentest tooling hardcodes a "just keep trying" retry chain: NTLM first, fall back to Kerberos; LDAPS first, fall back to LDAP; password first, fall back to hash. Against a hardened environment that doesn't allow any of those, you get long timeouts, ambiguous error messages (SEC_E_LOGON_DENIED, invalidCredentials), and a scan that takes 4× longer than it should.

ADscan does the opposite. It treats the environment's behavior as evidence to be remembered, not noise to be retried through.

Controls ADscan detects

ControlDetection signalWhat ADscan changes
NTLM authentication disabledDC rejects an NTLM bind with SEC_E_LOGON_DENIED after a valid Kerberos TGT, refuses the bind with SEC_E_UNSUPPORTED_FUNCTION / 0x80090302 / STATUS_NOT_SUPPORTED / STATUS_NTLM_BLOCKED, or SMB returns STATUS_LOGON_FAILURE with NTLM markersAll subsequent operations skip NTLM bind attempts
AES-only Kerberos / RC4 disabledKDC returns KDC_ERR_ETYPE_NOTSUPP for RC4NT-hash-as-Kerberos-key paths are pruned
Non-default Kerberos saltETYPE-INFO2 advertises a non-standard AES saltEtype probe applied automatically before every TGT request
LDAPS availableLDAPS bind on port 636 succeedsLDAPS becomes the preferred transport for that domain
LDAPS unavailableTLS handshake or transport failure on port 636LDAP plain (with signing where required) is used directly — no LDAPS attempts
LDAP signing requiredDC returns LDAP_STRONG_AUTH_REQUIRED / strongerAuthRequiredPlain-LDAP binds force sign=true
LDAP channel binding requiredLDAPS bind without CBT rejectedLDAPS binds enable CBT automatically
SMB signing requiredNegotiate response advertises signing required, or bind raises a signing exceptionRelay vectors flagged as not viable in the report

Each detection has an associated confidence level (low / medium / high). Only high-confidence evidence is allowed to prune retries — low-confidence signals enrich the audit trail without changing behavior.

What you see when ADscan detects hardening

The first time a control is detected during a scan, the Intelligence Update panel appears once per (domain, control) pair:

╭─────────────────── 🧠  ADscan Intelligence Update ───────────────────╮
│                                                                      │
│   Detected security hardening on garfield.htb                        │
│                                                                      │
│   Constraint  NTLM authentication disabled                           │
│   Evidence    DC rejected NTLM bind with SEC_E_LOGON_DENIED while    │
│               a Kerberos TGT was valid                               │
│   Source      ldap_transport · garfield.htb                          │
│   Action      ⚡ Switching to Kerberos-only auth for this domain     │
│                                                                      │
│   This is good defensive posture — your client has hardened their    │
│   domain against legacy authentication.                              │
│                                                                      │
│   📌 Learned   posture saved · no further retries this scan          │
│   📊 Report    will appear in "Detected Defensive Posture"           │
│                                                                      │
╰──────────────────────────────────────────────────────────────────────╯

Three things happen at that moment:

  1. The decision is persisted in the workspace at ~/.adscan/workspaces/<workspace>/domains/<domain>/<workspace_data>.json under auth_posture.constraints.<category>. Any subsequent operation in the same scan reads this and skips impossible attempts.

  2. The scan adapts. A trust enumeration that was about to attempt LDAPS+NTLM (and would have returned SEC_E_LOGON_DENIED after a 30-second timeout) now goes straight to LDAP+Kerberos. Same for the LDAP collector, the SMB share enumeration, and every other LDAP-touching operation.

  3. The discovery is logged for the report. When the engagement finishes, the PDF report includes a Detected Defensive Posture section before the findings, listing every control ADscan validated empirically against the live DC.

In the customer report

Just before the findings, the report shows:

Detected Defensive Posture

corp.local

This domain demonstrates the following defensive posture, validated empirically by ADscan during the engagement (not from configuration review):

Hardening controlStatus
NTLM authentication✓ Disabled
Kerberos RC4 encryption✓ Disabled
LDAP channel binding✓ Required
SMB signing✓ Required

This is good defensive posture. The findings in the following sections were obtained despite this hardening, which means they represent residual risk that policy alone does not mitigate.

Detection method

ADscan validated each control empirically during the engagement. The evidence trail below is auditable — your blue/red team can replay each observation against the same DC.

  • NTLM authentication: DC rejected NTLM bind with SEC_E_LOGON_DENIED while a Kerberos TGT was valid. Source: ldap_transport. Signal: NTLM_REJECTED_VIA_LDAP.
  • Kerberos RC4 encryption: KDC rejected requested encryption type — domain enforces AES-only Kerberos. Source: kerberos_transport. Signal: KDC_ERR_ETYPE_NOTSUPP.

This section is omitted entirely when no high-confidence hardening was detected — ADscan never fills the report with "Unknown" placeholders.

How the planner adapts

When the workspace already has high-confidence posture for a domain (from earlier in the same scan or a previous run against the same target), ADscan's LDAP authentication planner consults it and emits an ordered list of attempts to try, pruning impossible combinations.

Example: a domain with LDAPS_AVAILABLE = DISABLED (high) and NTLM_AUTHENTICATION = DISABLED (high). The conservative chain (4 attempts) is reduced to 1:

Conservative chain (no posture):
  1. LDAPS + Kerberos
  2. LDAPS + password (NTLM)
  3. LDAP + Kerberos
  4. LDAP + password (NTLM)

Posture-driven plan:
  1. LDAP + Kerberos

When all viable attempts are pruned (e.g. NT hash credential against an NTLM-disabled, AES-only domain), ADscan surfaces a clear NoViableLDAPAuthError instead of running a futile bind.

Behavior in non-restrictive environments

In labs and permissive environments (NTLM enabled, RC4 allowed, no signing, no CBT), ADscan does not fabricate posture data. The workspace simply has no auth_posture.constraints block, the planner uses the conservative chain, and the report omits the Detected Defensive Posture section.

The same code path runs in GOAD, hardened customer DCs, and HTB boxes — the difference is in what the workspace remembers, not in how ADscan operates.

Re-running ADscan against the same target

The posture persists in the workspace JSON. Re-running ADscan picks up where the last scan left off:

  • If the customer's GPO changed between runs (e.g. NTLM was re-enabled), the next high-confidence signal from the new run upgrades the posture; ADscan re-evaluates the planner with the updated state.
  • To force a fresh probe (e.g. after a policy change you know happened), delete the auth_posture block from the workspace JSON or start a new workspace.

What's persisted, what's not

Persisted across scans:

  • auth_posture.constraints.<category>.state (likely_enabled / likely_disabled / likely_required / unknown)
  • auth_posture.constraints.<category>.confidence (low / medium / high)
  • auth_posture.constraints.<category>.evidence[] — the audit trail (capped at the last 20 observations per control)
  • auth_posture.constraints.<category>.updated_at

Ephemeral (in-memory, per-scan only):

  • Clock skew with the DC
  • Per-credential auth retries (e.g. TGT staleness refresh)
  • Network-level timeouts and reachability

See also

  • Best Practices — engagement preparation, including credential and scope verification
  • Troubleshooting — common authentication errors and how to interpret them
  • Pro Report Outputs — full report structure including the Detected Defensive Posture section
Find this useful?
Pass it to the next pentester running an AD engagement
Running 2+ AD engagements/year?
Get PRO free — beta access·Free in exchange for feedback
Automated PDF reports. Save ≥1 day per engagement.

ADscan — AD pentest automation for security consultants

🧠 Hardening Detection | ADscan