The finding
A PCI-relevant vulnerability scan reported a Windows host for null session / IPC$ access (CVE-2000-1200). Both the title and the category referenced NetBIOS, on a host where NetBIOS over TCP/IP (NetBT) was supposed to be disabled.
The natural assumption was that NetBT was still enabled somewhere: a hidden adapter, a virtual switch, or a binding nobody had checked. That assumption turned out to be wrong, but it was still worth ruling out properly. “I assumed” is not an answer an assessor accepts.
Ruling out NetBIOS over TCP/IP
The host was a Hyper-V server running on KVM, with a VirtIO NIC bound to the virtual switch. That meant several adapters to account for: physical, virtual, ISATAP and Teredo. I validated NetBT state three independent ways.
| Check | What it shows | Result |
|---|---|---|
WMI (Win32_NetworkAdapterConfiguration) | NetBT setting per IP-enabled adapter | Hyper-V vEthernet set to 2 (disabled) |
Registry (NetBT\Parameters\Interfaces) | Every interface, including hidden ones | All interfaces set to 2 |
nbtstat -n | The local NetBIOS name table | Node IP 0.0.0.0, empty table |
# Per adapter (0 = DHCP, 1 = enabled, 2 = disabled)
Get-CimInstance Win32_NetworkAdapterConfiguration |
Select-Object Index, Description, IPEnabled, TcpipNetbiosOptions
# Every interface in the registry, including hidden and virtual
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces' |
ForEach-Object {
[pscustomobject]@{
Interface = $_.PSChildName
NetbiosOptions = (Get-ItemProperty $_.PSPath).NetbiosOptions
}
}
# Local name table: no names means NetBT is not bound
nbtstat -n
# Listeners on the NetBIOS and SMB ports
Get-NetTCPConnection -LocalPort 139,445 -State Listen -ErrorAction SilentlyContinue |
Select-Object LocalAddress, LocalPort, OwningProcess
Get-NetUDPEndpoint -LocalPort 137,138 -ErrorAction SilentlyContinue
Blank WMI values on the ISATAP, Teredo and VirtIO adapters are expected. WMI only reports this property for IP-enabled adapters, and the VirtIO NIC carried no IP of its own once bound to the vSwitch.
NetBT was off and port 139 was closed. The finding had to be coming from somewhere else.
Reading the finding in full
The answer was in the finding’s full description, not its title:
- The session was anonymous SMB to
IPC$over TCP 445. The scanner uses “NetBIOS” loosely, as a label for Windows file and printer sharing in general. - There are two distinct enumeration paths. User enumeration through the
Net*APIs is reported as a separate, companion finding. SID brute-forcing throughLsarLookupSidsis reported as this one. Only the SID-lookup finding fired.
That distinction matters, because the classic RestrictAnonymous controls address the first path but not the second.
The hardening was in place, mostly
Every control you would expect had been deployed:
Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' |
Select-Object RestrictAnonymous, RestrictAnonymousSAM, EveryoneIncludesAnonymous
Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters' |
Select-Object RestrictNullSessAccess, NullSessionPipes, NullSessionShares
| Setting | Expected | Found |
|---|---|---|
RestrictAnonymous | 1 | 1 |
RestrictAnonymousSAM | 1 | 1 |
EveryoneIncludesAnonymous | 0 | 0 |
RestrictNullSessAccess | 1 | 1 |
NullSessionPipes | Minimal or empty | netlogon, samr, lsarpc |
NullSessionShares | Empty | COMCFG |
The last two rows are the problem. RestrictNullSessAccess only blocks anonymous access to pipes and shares that are not on the exception lists. lsarpc was on the list, and lsarpc is exactly the pipe LsarLookupSids uses. A legacy COMCFG share was also still exposed to anonymous users.
The hardening had been deployed and was largely effective. One exception list left open the exact path the scanner was testing.
Why Group Policy didn’t catch it
The GPO setting Network access: Named Pipes that can be accessed anonymously was effectively not defined, so the local default persisted.
There is a useful tell here. Ticking Define this policy setting in the editor pre-fills a Windows 2000-era list (COMNAP, COMNODE, SQL\QUERY, LLSRPC, BROWSER, netlogon, samr). That list did not match the registry. When the registry doesn’t match what the GPO would write, the GPO isn’t what’s setting it.
The related setting Network access: Allow anonymous SID/Name translation is worth checking at the same time:
secedit /export /cfg "$env:TEMP\sec.cfg" /areas SECURITYPOLICY | Out-Null
Select-String -Path "$env:TEMP\sec.cfg" -Pattern 'LSAAnonymousNameLookup' # expect 0
Checking dependencies first
Before tightening an anonymous access list, establish who actually relies on anonymous logons. The Security log answers that directly. Run this on the affected server and on your domain controllers:
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4624 } -MaxEvents 20000 |
Where-Object { $_.Properties[5].Value -eq 'ANONYMOUS LOGON' } |
Group-Object { $_.Properties[18].Value } |
Sort-Object Count -Descending |
Select-Object Count, Name
In event 4624, Properties[5] is the target user name and Properties[18] is the source IP, so the output is a ranked list of systems making anonymous logons.
The usual suspect in our case was a domain-joined Synology NAS. Current DSM releases authenticate with the device’s machine account through winbind and shouldn’t need anonymous access, but “shouldn’t” is not evidence. Check the logs.
On domain controllers, also confirm that ANONYMOUS LOGON is not a member of Pre-Windows 2000 Compatible Access:
(Get-CimInstance Win32_ComputerSystem).DomainRole # 4 or 5 = domain controller
Get-ADGroupMember 'Pre-Windows 2000 Compatible Access' | Select-Object Name, SID
Remediation through Group Policy
A manual registry edit lasts until something reapplies the default, and it leaves no audit trail. Make the change in Group Policy under Computer Configuration › Policies › Windows Settings › Security Settings › Local Policies › Security Options:
| Policy | Setting |
|---|---|
| Network access: Named Pipes that can be accessed anonymously | Defined: netlogon, samr |
| Network access: Shares that can be accessed anonymously | Defined and empty |
| Network access: Allow anonymous SID/Name translation | Disabled |
| Network access: Restrict anonymous access to Named Pipes and Shares | Enabled |
| Network access: Do not allow anonymous enumeration of SAM accounts and shares | Enabled |
Retaining samr is acceptable here because RestrictAnonymousSAM = 1 already blocks the Net* API enumeration path. On member servers, the CIS baseline goes further and expects the anonymous pipes list to be empty.
To prove the theory in a lab before changing the GPO, the lists can be cleared locally. Restarting the Server service drops active SMB sessions, so keep this out of production:
$p = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
Set-ItemProperty $p -Name NullSessionPipes -Value ([string[]]@()) -Type MultiString
Set-ItemProperty $p -Name NullSessionShares -Value ([string[]]@()) -Type MultiString
Restart-Service LanmanServer -Force
Verifying the fix
Confirm that the policy applied and that the effective value on the host changed:
gpupdate /force
gpresult /h "$env:TEMP\gp.html"; Start-Process "$env:TEMP\gp.html"
Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters' |
Select-Object NullSessionPipes
Microsoft documents no restart requirement for the anonymous pipes policy. If the rescan still sees the pipe, restart the Server service during a maintenance window. Then reproduce the scanner’s test from a Linux host with Impacket, using an empty username. That is what a null session is; anonymous is sent as a literal account name and fails for reasons that have nothing to do with the fix:
# Empty username = null session
lookupsid.py ''@<ip> -no-pass
# Kali packaging: impacket-lookupsid ''@<ip> -no-pass
Run it before the change as well, so the later failure means something. Before the fix, this walks the SID range and returns account names. Afterward, it should fail with access denied. Once you have seen that yourself, the rescan is a formality, and you have evidence on file if the finding ever resurfaces.
Caveats
There is also no complete system-level fix for LsarLookupSids. The scanner vendor’s own guidance acknowledges that Microsoft treats it as required functionality. Where the pipe cannot be removed, a legitimate outcome is to:
- restrict TCP 445 to trusted management and server segments,
- document the risk acceptance alongside the compensating controls,
- and track it through your normal exception process.
A finding that has been understood, scoped and formally accepted is a better outcome than a change that breaks authentication the following week.
Key takeaways
- Read the full finding. Scanner titles and categories can mislead; here, “NetBIOS” meant SMB on 445.
- Deployed is not the same as effective. A single exception list undid most of the hardening.
- Validate on the host, not just in the GPO. If the registry doesn’t match what the policy would write, the policy isn’t in control.
- Reproduce the technique yourself. It is the fastest way to confirm a fix or prove a false positive.
- Check dependencies in the logs, and document deviations. Your future self, and your assessor, will thank you.
References
- CVE-2000-1200 (NVD)
- IPC$ share and null session behavior in Windows (Microsoft Learn)
- Network access: Named Pipes that can be accessed anonymously (Microsoft Learn)
- Network access: Restrict anonymous access to Named Pipes and Shares (Microsoft Learn)
- Network access: Allow anonymous SID/Name translation (Microsoft Learn)
- [MS-LSAT] LsarLookupSids (Opnum 15) (Microsoft Learn)
- 4624(S): An account was successfully logged on (Microsoft Learn)
- Impacket lookupsid.py (GitHub)
- MITRE ATT&CK T1087.002: Account Discovery: Domain Account