Null sessions on domain controllers: confirm, then close the enumeration
Anonymous enumeration of Active Directory over SMB null sessions: sometimes a genuine legacy leftover, sometimes by-design noise about pipes a DC needs to keep. Confirm what actually answers, then close the data leak without breaking the domain.
01 The Big PictureFor leadership · no jargon
Okay, so the null session: a vulnerability old enough to legally drink (CVE-1999-0519 was published in 1997), and one that makes a server holding the master list of every account in the organisation talk to complete strangers. No username, no password, just questions answered.
A null session is the network equivalent of walking up to the office front desk and, without giving your name, asking who's on shift and what the door policy is. A sensible building asks who you are first, a vulnerable one reads out the staff directory: every username, the groups, the password rules. That directory is a shopping list. An attacker with zero credentials learns which accounts exist and how many guesses they get before lockout, which is everything a password-spraying run needs to get started. My hit rate for this vulnerability is about half of all engagements, but it's not a number that should make anyone comfortable.
The twist, and the reason this guide exists: on modern Windows none of this is default, and on domain controllers a slice of it is by design. So the work comes in two parts. First, confirm what your DC actually answers to a stranger, because a real leak and by-design noise need very different responses. Second, close the actual enumeration: a handful of policy settings, plus one AD group membership that has quietly survived every in-place upgrade since Server 2003. The settings take minutes per DC; auditing which legacy box still relies on anonymous lookups before you enforce is the real timescale.
02 Technical BreakdownFor engineers
A null session is an SMB session to the IPC$ share with an empty username and password. Windows builds a token for it containing the ANONYMOUS LOGON SID (S-1-5-7), and what that token can then do depends entirely on how generous the machine's configuration is. Back in the Windows 2000 era the answer was "very": the Everyone group included anonymous users by default, so a null session could enumerate SAM accounts, shares and domain information over the samr and lsarpc named pipes. This is what enum4linux and rpcclient automate.
Modern Windows tightened the defaults considerably: RestrictNullSessAccess=1 confines null sessions to an allow-list of pipes and shares, EveryoneIncludesAnonymous=0 keeps the Everyone SID out of anonymous tokens, and anonymous SAM enumeration is off by default on workstations. Domain controllers are the deliberate exception: their NullSessionPipes list contains netlogon, samr and lsarpc, because downlevel clients, trust setup and secure-channel traffic genuinely use them. Microsoft's own FileCab article is blunt about it: DCs allow null sessions to those pipes, and pentesters that treat the connection itself as the vulnerability are "probably wrong". An anonymous *connection* to a DC is expected. Anonymous *answers* are not.
So when the finding is real, where's the leak? Almost always one of these. The "Pre-Windows 2000 Compatible Access" group (S-1-5-32-554) containing ANONYMOUS LOGON: the relic of picking "permissions compatible with pre-Windows 2000 servers" at dcpromo, carried faithfully through every in-place upgrade since. "Allow anonymous SID/Name translation" left Enabled, which was the default on Server 2003 R2 and earlier. Someone setting everyoneincludesanonymous=1 for a legacy app and never mentioning it. Or the seventh character of the dsHeuristics attribute set to 2, which opens anonymous LDAP reads on top of the SMB side. There's a lovely 2017 system admin war story where a pentest flagged anonymous enumeration on DCs with "all GPOs correct": a brand-new DC couldn't be enumerated, a freshly rebuilt one could, and the difference turned out to be that pipe allow-list doing its by-design thing. Know which situation you're in before touching anything.
03 Affected
Domains that were stood up on Server 2003 or earlier and upgraded in place (the compatible-permissions choice is forever), anywhere someone loosened EveryoneIncludesAnonymous or SID/Name translation for a legacy app, and DCs with a modified dsHeuristics. Also, awkwardly, anywhere a well-meaning admin previously "fixed" the finding by loosening the wrong thing.
Member servers can leak too if the policies are off, but the finding names DCs, and DCs are where the blast radius lives.
04 Detection
Before we change anything, let's find out which situation we're actually in: real leak or by-design noise. Golden rule first: test from a machine with no domain credentials, a non-domain-joined Linux box is ideal. If you test from your own domain laptop, Windows will silently use your credentials and your "anonymous" test isn't; Microsoft's FileCab article calls out exactly this false positive.
rpcclient -U "" -N <dc-ip> -c enumdomusersThe moment of truth. A list of domain users means the leak is real. NT_STATUS_ACCESS_DENIED means enumeration is closed, even if the session itself connected.
enum4linux -a <dc-ip>The full works: users, groups, shares, password policy. Empty or denied sections are the fixed state. Note that "only" the password policy coming back still counts: lockout threshold and minimum length are genuinely useful to an attacker.
net use \\<dc-ip>\IPC$ "" /user:From a Windows box, with explicit empty credentials: modern Windows refuses this outright. If it succeeds, something has been loosened. (If you ran it without the explicit empties and it worked, that was your own credentials, not a null session.)
On the DC itself, the registry:
Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' | Select-Object RestrictAnonymous, RestrictAnonymousSAM, everyoneincludesanonymousWant: RestrictAnonymousSAM=1, RestrictAnonymous=1, everyoneincludesanonymous=0. Missing values mean defaults; on a flagged DC, set them explicitly and stop relying on defaults.
(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters').NullSessionPipesnetlogon, samr and lsarpc on a DC is the expected, by-design set (and it reappears even on freshly rebuilt DCs, as r/sysadmin learned the hard way). Anything beyond that, spoolss, browser, winreg, deserves a hard question.
Now the usual suspect:
Get-ADGroupMember -Identity 'S-1-5-32-554' | Select-Object Name, SIDPre-Windows 2000 Compatible Access. ANONYMOUS LOGON (S-1-5-7) or Everyone (S-1-1-0) in here is the classic root cause of real enumeration; Authenticated Users is normal. The SID-based lookup is deliberate: the group's display name is localised.
And the LDAP side:
(Get-ADObject 'CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=contoso,DC=com' -Properties dsHeuristics).dsHeuristicsAbsent or empty is the default, and means anonymous LDAP is denied beyond rootDSE. If the seventh character is 2, anonymous clients can read whatever the ACLs allow.
ldapsearch -x -H ldap://<dc-ip> -b 'DC=contoso,DC=com' '(objectClass=user)' sAMAccountNameFrom your credential-free Linux box: users coming back means anonymous LDAP reads are live. rootDSE answering anonymously is normal and not the finding.
Finally, before you remediate the wrong thing, check which plugin actually fired. If it's Nessus: 26920 (severity High) and 162529 (severity Info, DC-specific) are very different conversations.
05 Remediation
01 Audit before you enforce
Find your anonymous consumers first. Enable audit mode for remote SAM calls and watch who would break:
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name RestrictRemoteSamAuditOnlyMode -Value 1 -Type DWordAudit-only mode for "Restrict clients allowed to make remote calls to SAM": events 16962 to 16969 (Directory-Service-SAM) in the System log show every call that enforcement would have denied. Give it a week. Storage filers, old print servers and mystery service accounts are the usual sightings.
Worth saying out loud while we're here: do not set RestrictAnonymous=2, anywhere, ever. That's the Windows 2000-era sledgehammer from KB 246261; it breaks downlevel netlogon, expired-password changes, trusts and the Browser service. Anyone still suggesting it is quoting documentation older than some of their own engineers.
02 The policy stack (Domain Controllers OU)
All of these live under Computer Configuration > Windows Settings > Security Settings > Local Policies > Security Options, in a GPO linked to the Domain Controllers OU (Default Domain Controllers Policy is the usual home). Two gotchas straight from the remediation trenches: linking the GPO to a security group instead of the OU does nothing, silently, and the LSA changes want a reboot before they bite.
- "Network access: Do not allow anonymous enumeration of SAM accounts" = Enabled
- "Network access: Do not allow anonymous enumeration of SAM accounts and shares" = Enabled
- "Network access: Let Everyone permissions apply to anonymous users" = Disabled
- "Network access: Allow anonymous SID/Name translation" = Disabled (Enabled was the default on 2003 R2 and earlier, so upgraded domains may still carry it)
- "Network access: Restrict anonymous access to Named Pipes and Shares" = Enabled (the default anyway; we're confirming, not discovering)
Honest caveat: Microsoft's own docs say the first two have no impact on domain controllers (they govern the local SAM, which a DC barely uses). Set them anyway as baseline hygiene, but know the AD-level fixes below are the ones that close real enumeration on a DC. Registry equivalents, for spot-fixes:
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name RestrictAnonymousSAM -Value 1 -Type DWord
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name RestrictAnonymous -Value 1 -Type DWord
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name everyoneincludesanonymous -Value 0 -Type DWordGPO wins on refresh, so prefer policy where you can; registry for the odd snowflake server. Reboot for LSA changes. And once more with feeling: RestrictAnonymous stays at 1, never 2.
03 Evict ANONYMOUS LOGON from Pre-Windows 2000 Compatible Access
This is the fix for the classic in-place-upgraded domain:
Remove-ADGroupMember -Identity 'S-1-5-32-554' -Members 'S-1-5-7' -Confirm:$falseRemoves ANONYMOUS LOGON. If Everyone (S-1-1-0) is in there too, it goes as well. What legitimately breaks: genuinely ancient kit (NT4-era apps, some old RAS and SQL setups). If your audit phase was quiet, you're fine; if it wasn't, see Rollback for the least-bad compromise.
04 Close anonymous LDAP (dsHeuristics)
If Detection showed the seventh character at 2:
Set-ADObject 'CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=contoso,DC=com' -Replace @{dsHeuristics='0000000'}Sets the seventh character back to 0: forest-wide, replicates on its own, no restart needed. Only touch that character; the others control unrelated directory behaviour, so read the current value first and edit surgically. The true default is the attribute being absent entirely.
05 Optional: restrict remote SAM calls properly
The modern control is "Network access: Restrict clients allowed to make remote calls to SAM" (RestrictRemoteSam). CIS and STIG both want it locked to administrators only (O:BAG:BAD:(A;;RC;;;BA)), and since Microsoft's DC default is deliberately everyone-can-read for compatibility, this is real hardening rather than box-ticking. Microsoft documents computer renames failing with 0x5 when it's locked down, and Defender for Identity needs its service account granted read access. Audit mode first (above), then enforce with the exceptions you actually found.
06 Things that don't work
Worth listing, because they circulate. RestrictAnonymous=2: covered above, just don't. Pushing this stack domain-wide to workstations to be thorough: "it seems to break Outlook on workstations" is the cautionary tale, keep it in the DCs OU. Emptying NullSessionPipes on a DC: netlogon, samr and lsarpc are load-bearing for trusts and downlevel clients, Microsoft warns against it, and the list grows back on rebuilds anyway. And expecting a green re-test: the by-design pipes keep Info-level findings alive forever, which brings us neatly to proof.
06 Verification
Settings in and strangers evicted? Let's prove it, from that credential-free Linux box again:
rpcclient -U "" -N <dc-ip> -c enumdomusersNT_STATUS_ACCESS_DENIED is the pass mark.
enum4linux -a <dc-ip>Users, groups, shares and policy all empty or denied. Session connects, nothing answers: that's a DC behaving exactly as designed.
ldapsearch -x -H ldap://<dc-ip> -b 'DC=contoso,DC=com' '(objectClass=user)' sAMAccountNameNo users back. rootDSE still answering is fine and expected.
gpresult /r /scope:computerOn the DC: confirm your GPO actually applied. Linked to the Domain Controllers OU, not to a group; the silent no-op is the most popular configuration error in every Reddit thread this guide is built from.
Then re-test, twice if you can: once unauthenticated, once with credentials. If the DC-specific test still fires on netlogon/samr/lsarpc after all of the above: that's the by-design noise. Document it as accepted through your pentest's exception process and spend your energy elsewhere.
07 Rollback
If the audit missed a consumer and something legacy breaks (storage filers are the repeat offenders):
Add-ADGroupMember -Identity 'S-1-5-32-554' -Members 'S-1-5-11'Authenticated Users (S-1-5-11) back into Pre-Windows 2000 Compatible Access: the least-bad middle ground when something genuinely needs the read access. ANONYMOUS LOGON stays evicted; a device that can't authenticate at all is a finding, not a dependency.
For the GPOs, set the options back to Not Configured (or delete the registry values you spot-fixed) and reboot. For dsHeuristics, put the seventh character back to what it was, which you noted down before changing it, right?
And the honest fallback: if a business-critical box genuinely cannot survive without anonymous access and can't be replaced, scope the exception as narrowly as possible, document it with your security team as accepted risk, and monitor the pipes for new friends. Not every pentest finding ends in a fix; some end in a spreadsheet with an owner.
08 References
- Microsoft FileCab: SMB and Null Sessions: Why Your Pen Test is Probably Wrong
- Microsoft: Network access, do not allow anonymous enumeration of SAM accounts (note the DC caveat)
- Microsoft KB 326690: anonymous LDAP operations and dsHeuristics
- Microsoft: Restrict clients allowed to make remote calls to SAM
- KB 246261, the RestrictAnonymous=2 breakage catalogue (archived)
- NVD: CVE-1999-0519
- r/sysadmin: the 2017 anonymous enumeration war story
- r/sysadmin: full GPO stack applied, Nessus still flags
- r/nessus: the plugin 10399 severity-flip incident
If I've got anything wrong here, or you've hit an edge case I haven't covered, do say: corrections and questions are always welcome :)
Ship the fix. Prove it’s closed.
If this runbook doesn’t fit your stack, send the CVE — an operator writes a verified path, usually within a business day. Or contribute your own and get co-authored credit.