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.

CheckWhat it showsResult
WMI (Win32_NetworkAdapterConfiguration)NetBT setting per IP-enabled adapterHyper-V vEthernet set to 2 (disabled)
Registry (NetBT\Parameters\Interfaces)Every interface, including hidden onesAll interfaces set to 2
nbtstat -nThe local NetBIOS name tableNode 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:

  1. 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.
  2. There are two distinct enumeration paths. User enumeration through the Net* APIs is reported as a separate, companion finding. SID brute-forcing through LsarLookupSids is 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
SettingExpectedFound
RestrictAnonymous11
RestrictAnonymousSAM11
EveryoneIncludesAnonymous00
RestrictNullSessAccess11
NullSessionPipesMinimal or emptynetlogon, samr, lsarpc
NullSessionSharesEmptyCOMCFG

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:

PolicySetting
Network access: Named Pipes that can be accessed anonymouslyDefined: netlogon, samr
Network access: Shares that can be accessed anonymouslyDefined and empty
Network access: Allow anonymous SID/Name translationDisabled
Network access: Restrict anonymous access to Named Pipes and SharesEnabled
Network access: Do not allow anonymous enumeration of SAM accounts and sharesEnabled

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

  1. Read the full finding. Scanner titles and categories can mislead; here, “NetBIOS” meant SMB on 445.
  2. Deployed is not the same as effective. A single exception list undid most of the hardening.
  3. 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.
  4. Reproduce the technique yourself. It is the fastest way to confirm a fix or prove a false positive.
  5. Check dependencies in the logs, and document deviations. Your future self, and your assessor, will thank you.

References