Bug: CoerceAndRelay edges not created when RestrictSendingNTLMTraffic registry key is absent (default Windows config)
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 84/100
Research direction
Start in packages/go/ein/ad.go at the RestrictOutboundNTLM property assignment, then read packages/go/analysis/ad/ntlm.go:96-101 to understand how the value controls UnprotectedComputersCache. Reproduce the absent-key case with NTLMRegistryData.Collected true and verify that RestrictOutboundNTLM is treated as false and the four CoerceAndRelay edge types can be created.
Written by the indexing model from the issue text.
Description
Description:
CoerceAndRelay edges (LDAP, LDAPS, ADCS, SMB) are never created when the RestrictSendingNTLMTraffic registry key does not exist on target computers, which is the default on every Windows machine where the "Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers" policy has never been configured.
This is a regression introduced by #1787. Before that PR, the NTLMRegistryInfo fields were non-pointer uint types, so an absent registry key defaulted to 0, and 0 == 2 evaluated to false (not restricted), which is correct. After #1787 changed the fields to *uint pointers to support nullable values, an absent key becomes nil, which causes the ingestion to set RestrictOutboundNTLM to nil on the graph node. The analysis in PostNTLM (packages/go/analysis/ad/ntlm.go:96-101) then fails to read the property as a boolean and skips the computer entirely (fail-closed), excluding it from UnprotectedComputersCache and blocking all four edge types.
Per Microsoft's documentation:
"This state of being not defined is the same as Allow all, and the device will allow all NTLM authentication requests when the policy is deployed."
Since most environments never configure this policy, this effectively prevents CoerceAndRelay edges from being created in the majority of deployments.
Are you intending to fix this bug?
Yes. I can submit a PR, but the guidelines says to create an issue first. I'd like to get your take on this before I submit the PR.
Component(s) Affected:
- API
Steps to Reproduce:
- Collect data with SharpHound (
--collectionmethods all) as Domain Admin against an environment where theRestrictSendingNTLMTrafficregistry key has never been configured (the Windows default) - Ingest data into BloodHound CE with
ntlm_post_processingfeature flag enabled - Verify prerequisites are met: SMB signing disabled on some computers, WebClient running, LDAP signing not enforced on DCs, vulnerable ADCS templates
- Run the built-in "All coerce and NTLM relay edges" query or:
MATCH p = (n:Base)-[:CoerceAndRelayNTLMToLDAP|CoerceAndRelayNTLMToLDAPS|CoerceAndRelayNTLMToADCS|CoerceAndRelayNTLMToSMB]->(:Base) RETURN p LIMIT 500 - Zero results returned despite all prerequisites being met
Expected Behavior:
When NTLMRegistryData.Collected is true and RestrictSendingNtlmTraffic is nil (key absent), RestrictOutboundNTLM should be set to false (not restricted), reflecting the Windows default of "Allow all." CoerceAndRelay edges should be created for computers meeting the other prerequisites.
Actual Behavior:
RestrictOutboundNTLM is set to nil in packages/go/ein/ad.go:110. The analysis in packages/go/analysis/ad/ntlm.go:96-101 fails to read the property as a boolean, treats the computer as protected (fail-closed), and excludes it from UnprotectedComputersCache. No CoerceAndRelay edges are created for any of the four types.
The registry key simply does not exist on a default Windows Server 2025 installation:
C:\>reg query HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
Auth132 REG_SZ IISSUBA
NtlmMinClientSec REG_DWORD 0x20000000
NtlmMinServerSec REG_DWORD 0x20000000
Auth2 REG_SZ RASSFM
SharpHound correctly reports this as NTLMRegistryData.Collected: true with RestrictSendingNtlmTraffic: null.
Environment Information:
BloodHound: CE v8.7.0 (current main)
Collector: SharpHound 2.11.0
OS: Windows Server 2025 Standard (target DC/computers), Ubuntu 25.10 (BH host)
Docker: Docker Compose deployment
Potential Solution (optional):
In packages/go/ein/ad.go, change line 110 from:
itemProps[ad.RestrictOutboundNTLM.String()] = nil
to:
itemProps[ad.RestrictOutboundNTLM.String()] = false
This only affects the case where registry data was successfully collected (Collected=true) but the key is absent. Unreachable computers where collection failed remain unaffected (property stays unset, analysis still skips them with fail-closed behavior preserved).
I was also worried there may a scenario where the key is absent but NTLM is restricted. However, all I've found is that absence of the key is definitively "Allow All" per Microsoft. The only way to restrict is to explicitly create the key with value 1 or 2. The nil default in #1787 was arguably a bit conservative for this specific property. It makes sense for other registry values where absence of the key might be ambiguous, but RestrictSendingNTLMTraffic has a documented default.
Note that #1440 proposed a more comprehensive approach with a UI-configurable default value, but was closed in favor of #1787.
Related Issues:
- #1787 — The PR that introduced this regression by changing
NTLMRegistryInfofields to nullable pointers - #1440 — A previously proposed fix that included a UI-configurable default value for
RestrictOutboundNTLM(closed
without merge)
Contributor Checklist:
- I have searched the issue tracker to ensure this bug hasn't been reported before or is not already being
addressed. - I have provided clear steps to reproduce the issue.
- I have included relevant environment information details.
- I have attached necessary supporting documents.
- I have checked that any JSON files I am attempting to upload to BloodHound are valid.
Disclosure: I used LLM/AI to assist me in tracing down the issue in the code. I've reviewed the proposed fix, understand everything about it, done my own research on this issue, and feel confident about sharing this.
- Dominant language
- Go
- Stars
- 3.4k
- Forks
- 376
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 97
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from SpecterOps/BloodHound
-
bug triage
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
SpecterOps/BloodHound#2806 · 1 comment ·
-
bug triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SpecterOps/BloodHound#2709 ·
-
Bug: Service Principal App ID node property displaying Service Principal Object ID instead of App ID Openbug triage
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
SpecterOps/BloodHound#1846 ·
-
bug needs more info triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
SpecterOps/BloodHound#1692 · 3 comments ·
-
Bug: UI renders Unicode bidirectional override characters in Distinguished Names (visual spoofing) Openbug triage
Difficulty 3/5 1-2 days Newbie friendliness 68/100
SpecterOps/BloodHound#3270 · 7 comments ·
All issues in SpecterOps/BloodHound
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 84/100
-
enhancement needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
kind/cleanup
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
kubernetes-sigs/kueue#15947 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
sympozium-ai/sympozium#627 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100