From ee83e042248dec36d4bca55a3c9c7066a443eb8f Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Wed, 13 Nov 2024 16:14:04 -0500 Subject: [PATCH 1/2] fix: make enterprise domain controllers group spit ou the correct sid Closes: https://specterops.atlassian.net/issues/BED-4846 --- src/CommonLib/LdapUtils.cs | 19 +++++++++++-------- test/unit/LDAPUtilsTest.cs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index e29defc7..a9166aff 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -315,7 +315,7 @@ public IAsyncEnumerable> PagedQuery(LdapQueryParame return (false, null); } - public async Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid) { + public virtual async Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid) { string domainSid; try { domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper(); @@ -408,7 +408,7 @@ public IAsyncEnumerable> PagedQuery(LdapQueryParame return (false, string.Empty); } - public async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { + public virtual async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); try { @@ -938,7 +938,7 @@ public async IAsyncEnumerable GetWellKnownPrincipalOutput() { OutputBase output = principal.ObjectType switch { Label.User => new User(), Label.Computer => new Computer(), - Label.Group => new OutputTypes.Group(), + Label.Group => new Group(), Label.GPO => new GPO(), Label.Domain => new OutputTypes.Domain(), Label.OU => new OU(), @@ -961,7 +961,7 @@ public async IAsyncEnumerable GetWellKnownPrincipalOutput() { yield return entdc; } } - + private async IAsyncEnumerable GetEnterpriseDCGroups() { var grouped = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); var forestSidToName = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -972,7 +972,7 @@ await GetForest(domainName) is (true, var forestName) && await GetDomainSidFromDomainName(forestName) is (true, var forestDomainSid)) { forestSidToName.TryAdd(forestDomainSid, forestName); if (!grouped.ContainsKey(forestDomainSid)) { - grouped[forestDomainSid] = new List(); + grouped[forestDomainSid] = []; } foreach (var k in domainSid) { @@ -982,10 +982,13 @@ await GetDomainSidFromDomainName(forestName) is (true, var forestDomainSid)) { } foreach (var f in grouped) { - var group = new Group { ObjectIdentifier = $"{f.Key}-S-1-5-9" }; - group.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forestSidToName[f.Key]}".ToUpper()); + if (!forestSidToName.TryGetValue(f.Key, out var forestName)) { + continue; + } + var group = new Group { ObjectIdentifier = $"{forestName}-S-1-5-9" }; + group.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forestName}".ToUpper()); group.Properties.Add("domainsid", f.Key); - group.Properties.Add("domain", forestSidToName[f.Key]); + group.Properties.Add("domain", forestName); group.Members = f.Value.Select(x => new TypedPrincipal(x, Label.Computer)).ToArray(); yield return group; } diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index 2b08cc3d..41dc3b0a 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -6,6 +6,7 @@ using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; using Xunit; using Xunit.Abstractions; @@ -231,5 +232,35 @@ public async Task Test_ResolveHostToSid_BlankHost() { var (success, sid) = await utils.ResolveHostToSid(spn, ""); Assert.False(success); } + + [WindowsOnlyFact] + public async Task EnterpriseDomainControllersGroup_CorrectValues() { + var utilsMock = new Mock(); + + //We're going to say TESTLAB.LOCAL is forest root, and SECONDARY is a child domain underneath TESTLAB.LOCAL + + utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379446-2105")) + .ReturnsAsync((true, "TESTLAB.LOCAL")); + utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379446-2106")) + .ReturnsAsync((true, "TESTLAB.LOCAL")); + utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379447-2105")) + .ReturnsAsync((true, "SECONDARY.TESTLAB.LOCAL")); + + utilsMock.Setup(x => x.GetForest("TESTLAB.LOCAL")).ReturnsAsync((true, "TESTLAB.LOCAL")); + utilsMock.Setup(x => x.GetForest("SECONDARY.TESTLAB.LOCAL")).ReturnsAsync((true, "TESTLAB.LOCAL")); + + utilsMock.Setup(x => x.GetDomainSidFromDomainName("TESTLAB.LOCAL")).ReturnsAsync((true, "S-1-5-21-3130019616-2776909439-2417379446")); + + var utils = utilsMock.Object; + utils.AddDomainController("S-1-5-21-3130019616-2776909439-2417379446-2105"); + utils.AddDomainController("S-1-5-21-3130019616-2776909439-2417379446-2106"); + utils.AddDomainController("S-1-5-21-3130019616-2776909439-2417379447-2105"); + + var result = await utils.GetWellKnownPrincipalOutput().ToArrayAsync(); + Assert.Single(result); + var entDCGroup = result[0] as Group; + Assert.Equal("TESTLAB.LOCAL-S-1-5-9", entDCGroup.ObjectIdentifier); + Assert.Equal(3, entDCGroup.Members.Length); + } } } \ No newline at end of file From 3e8a3ad5c39f8912d2ab06e558f42c7f1e9d282b Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Wed, 13 Nov 2024 16:26:07 -0500 Subject: [PATCH 2/2] chore: fix test --- test/unit/LDAPUtilsTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index 41dc3b0a..0641aa6b 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -239,11 +239,9 @@ public async Task EnterpriseDomainControllersGroup_CorrectValues() { //We're going to say TESTLAB.LOCAL is forest root, and SECONDARY is a child domain underneath TESTLAB.LOCAL - utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379446-2105")) + utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379446")) .ReturnsAsync((true, "TESTLAB.LOCAL")); - utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379446-2106")) - .ReturnsAsync((true, "TESTLAB.LOCAL")); - utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379447-2105")) + utilsMock.Setup(x => x.GetDomainNameFromSid("S-1-5-21-3130019616-2776909439-2417379447")) .ReturnsAsync((true, "SECONDARY.TESTLAB.LOCAL")); utilsMock.Setup(x => x.GetForest("TESTLAB.LOCAL")).ReturnsAsync((true, "TESTLAB.LOCAL"));