Fix unstable node IDs by namespacing with account SID (#23)#24
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked Issue
Closes #23
Root Cause
KeyCredential node ids were built as
<Identifier>.<distinguishedName>. AdistinguishedNameis mutable — it changes on a rename or an OU move — while BloodHound merges nodes idempotently on theirid. As a result, re-collecting a principal after it was renamed or relocated produced a brand-new id and therefore a duplicate KeyCredential node (and, since the old node is no longer re-emitted, an orphan). The accountobjectSid, which the collector already reads to build theHasKeyCredentialedge, is immutable for the principal's lifetime and is the correct stable namespace.Fix Description
objectSidonce per LDAP entry at the top of the outer loop (it is identical for every credential on the account), and skip the entry if the SID is missing or fails to parse.accountSid.String()instead ofdistinguishedName:<SID>.<Identifier>(or<SID>.Unknown-<n>when the credential has no identifier).HasKeyCredentialedge; the edge now uses the hoistedaccountSid.The key-material node id is intentionally left keyed on
kc.Identifier(the public-key hash) so credentials that share the same key still converge on a single key-material node — the basis of the shared-key query.How Verified
Static:
keyCredentialNodeIdis now derived solely fromaccountSid(immutable) andkc.Identifier/positional fallback, so re-collection after a rename or OU move yields the same id and merges.go build ./...,go vet ./..., andgofmt -l parse.go(no output) all pass.Test Coverage
None:
ParseResultsconsumes*ldap.Entryvalues and parsedmsDS-KeyCredentialLinkblobs from a live directory; no fixture harness exists in the repo. The change is limited to id derivation and the relocation of SID extraction.Scope of Change
parse.goobjectSidis missing/unparseable is skipped entirely (previously its nodes were still emitted but left unlinked to the principal). This is a direct consequence of needing the SID before node creation; a KeyCredential that cannot be linked to its principal has no useful standalone meaning.Risk and Rollout
Local to
parse.go; changes the id scheme, so a one-time re-ingest will create the new SID-based nodes alongside any previously ingested DN-based nodes until the old source-kind data is cleared. Safe to merge.