Skip to content

Commit 367fc76

Browse files
ktstraderrvazarkar
andauthored
BED-5435 AddSelf, BED-5988 Mandatory Filters (#209)
* fix: properly account for AddSelf edges where the RightsGuid is AllGuid, which is a valid attack for this scenario fix: Make our ldap filters actually respect mandatory filters properly https://specterops.atlassian.net/browse/BED-5435 https://specterops.atlassian.net/browse/BED-5988 * chore: add unit tests to check that AddSelf edge is added when appropriate and not added if not * chore: added unit test for BED-5988 --------- Co-authored-by: Rohan Vazarkar <rvazarkar@specterops.io>
1 parent 6b83bf3 commit 367fc76

4 files changed

Lines changed: 109 additions & 2 deletions

File tree

src/CommonLib/LdapQueries/LdapFilter.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,21 @@ public string GetFilter() {
255255
return filterPartsDistinct;
256256
}
257257

258+
private string MergeFilter(string filterA, string filterB) {
259+
return $"(&{filterA}{filterB})";
260+
}
261+
258262
public IEnumerable<string> GetFilterList() {
259-
return _filterParts.Distinct();
263+
foreach (var filter in _filterParts.Distinct())
264+
{
265+
if (_mandatory.Count > 0) {
266+
foreach (var mandatory in _mandatory) {
267+
yield return MergeFilter(filter, mandatory);
268+
}
269+
} else {
270+
yield return filter;
271+
}
272+
}
260273
}
261274
}
262275
}

src/CommonLib/Processors/ACLProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ public async IAsyncEnumerable<ACE> ProcessACL(byte[] ntSecurityDescriptor, strin
407407
if (aceRights.HasFlag(ActiveDirectoryRights.Self) &&
408408
!aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) &&
409409
!aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group &&
410-
aceType == ACEGuids.WriteMember)
410+
aceType is ACEGuids.WriteMember or ACEGuids.AllGuid)
411411
aces.Add(new ACE
412412
{
413413
PrincipalType = resolvedPrincipal.ObjectType,

test/unit/ACLProcessorTest.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,78 @@ public async Task ACLProcessor_ProcessACL_Self() {
734734
Assert.False(actual.IsInherited);
735735
Assert.Equal(actual.RightName, expectedRightName);
736736
}
737+
738+
[Fact]
739+
public async Task ACLProcessor_ProcessACL_Self_AllGuid() {
740+
var expectedPrincipalType = Label.Group;
741+
var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
742+
var expectedRightName = EdgeNames.AddSelf;
743+
744+
var mockLDAPUtils = new Mock<ILdapUtils>();
745+
var mockSecurityDescriptor = new Mock<ActiveDirectorySecurityDescriptor>(MockBehavior.Loose, null);
746+
var mockRule = new Mock<ActiveDirectoryRuleDescriptor>(MockBehavior.Loose, null);
747+
var collection = new List<ActiveDirectoryRuleDescriptor>();
748+
mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow);
749+
mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);
750+
mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID);
751+
mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self);
752+
mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid));
753+
collection.Add(mockRule.Object);
754+
755+
mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Type>()))
756+
.Returns(collection);
757+
mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny<Type>())).Returns((string)null);
758+
mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object);
759+
mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
760+
.ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
761+
var mockData = new[] { LdapResult<IDirectoryObject>.Fail() };
762+
mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
763+
.Returns(mockData.ToAsyncEnumerable());
764+
765+
var processor = new ACLProcessor(mockLDAPUtils.Object);
766+
var bytes = Utils.B64ToBytes(AddMemberSecurityDescriptor);
767+
var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArrayAsync();
768+
769+
Assert.Single(result);
770+
var actual = result.First();
771+
Assert.Equal(actual.PrincipalType, expectedPrincipalType);
772+
Assert.Equal(actual.PrincipalSID, expectedPrincipalSID);
773+
Assert.False(actual.IsInherited);
774+
Assert.Equal(actual.RightName, expectedRightName);
775+
}
776+
777+
[Fact]
778+
public async Task ACLProcessor_ProcessACL_NoAddSelfEdge() {
779+
var expectedPrincipalType = Label.Group;
780+
var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
781+
782+
var mockLDAPUtils = new Mock<ILdapUtils>();
783+
var mockSecurityDescriptor = new Mock<ActiveDirectorySecurityDescriptor>(MockBehavior.Loose, null);
784+
var mockRule = new Mock<ActiveDirectoryRuleDescriptor>(MockBehavior.Loose, null);
785+
var collection = new List<ActiveDirectoryRuleDescriptor>();
786+
mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow);
787+
mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);
788+
mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID);
789+
mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self);
790+
mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct));
791+
collection.Add(mockRule.Object);
792+
793+
mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Type>()))
794+
.Returns(collection);
795+
mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny<Type>())).Returns((string)null);
796+
mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object);
797+
mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
798+
.ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
799+
var mockData = new[] { LdapResult<IDirectoryObject>.Fail() };
800+
mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
801+
.Returns(mockData.ToAsyncEnumerable());
802+
803+
var processor = new ACLProcessor(mockLDAPUtils.Object);
804+
var bytes = Utils.B64ToBytes(AddMemberSecurityDescriptor);
805+
var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArrayAsync();
806+
807+
Assert.Empty(result);
808+
}
737809

738810
[Fact]
739811
public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() {

test/unit/LDAPFilterTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ public void LDAPFilter_GetFilterList()
7373
i++;
7474
}
7575
}
76+
77+
[Fact]
78+
public void LDAPFilter_GetFilterList_MergeFilter()
79+
{
80+
var test = new LdapFilter();
81+
test.AddUsers();
82+
test.AddComputers();
83+
string mergeFilter = "(objectclass=*)";
84+
test.AddFilter(mergeFilter, true);
85+
86+
IEnumerable<string> filters = test.GetFilterList();
87+
88+
int i = 0;
89+
string computerFilter = "(samaccounttype=805306369)";
90+
string userFilter = "(|(samaccounttype=805306368)(samaccounttype=805306370))";
91+
string[] expected = {$"(&{userFilter}{mergeFilter})", $"(&{computerFilter}{mergeFilter})"};
92+
93+
foreach (var filter in filters) {
94+
Assert.Equal(expected[i], filter);
95+
i++;
96+
}
97+
}
7698

7799
#endregion
78100
}

0 commit comments

Comments
 (0)