Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CommonLib/Enums/EdgeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public static class EdgeNames
public const string SQLAdmin = "SQLAdmin";
public const string WriteAccountRestrictions = "WriteAccountRestrictions";
public const string WriteGPLink = "WriteGPLink";
public const string WriteAltSecurityIdentities = "WriteAltSecurityIdentities";
public const string WritePublicInformation = "WritePublicInformation";

//CertAbuse edges
public const string WritePKIEnrollmentFlag = "WritePKIEnrollmentFlag";
Expand Down
3 changes: 2 additions & 1 deletion src/CommonLib/Processors/ACEGuids.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public class ACEGuids
public const string UserAccountRestrictions = "4c164200-20c0-11d0-a768-00aa006e0529";
public const string WriteGPLink = "f30e3bbe-9ff0-11d1-b603-0000f80367c1";
public const string WriteTitle = "bf967a55-0de6-11d0-a285-00aa003049e2"; // Not an edge, just used for testing

public const string WriteAltSecurityIdentities = "00fbf30c-91fe-11d1-aebc-0000f80367c1";
public const string WritePublicInformation = "e48d0154-bcf8-11d1-8702-00c04fb96050";

//Cert abuse ACEs
public const string PKINameFlag = "ea1dddc4-60ff-416e-8cc0-17cee534bce7";
Expand Down
21 changes: 21 additions & 0 deletions src/CommonLib/Processors/ACLProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,27 @@ or Label.NTAuthStore
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.WriteAltSecurityIdentities)
yield return new ACE {
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.WriteAltSecurityIdentities,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.WritePublicInformation)
yield return new ACE
{
PrincipalType = resolvedPrincipal.ObjectType,
PrincipalSID = resolvedPrincipal.ObjectIdentifier,
IsInherited = inherited,
RightName = EdgeNames.WritePublicInformation,
InheritanceHash = aceInheritanceHash,
IsPermissionForOwnerRightsSid = isPermissionForOwnerRightsSid,
IsInheritedPermissionForOwnerRightsSid = isInheritedPermissionForOwnerRightsSid,
};
else if (objectType is Label.CertTemplate) {
if (aceType == ACEGuids.PKIEnrollmentFlag)
yield return new ACE {
Expand Down
152 changes: 152 additions & 0 deletions test/unit/ACLProcessorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2129,5 +2129,157 @@ public async Task ACLProcessor_ProcessACL_EnterpriseCA_Enroll()
Assert.False(actual.IsInherited);
Assert.Equal(actual.RightName, expectedRightName);
}

[Fact]
public async Task ACLProcessor_ProcessACL_GenericWrite_User_WriteAltSecurityIdentities() {
var expectedPrincipalType = Label.User;
var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
var expectedRightName = EdgeNames.WriteAltSecurityIdentities;

var mockLDAPUtils = new Mock<ILdapUtils>();
var mockSecurityDescriptor = new Mock<ActiveDirectorySecurityDescriptor>(MockBehavior.Loose, null);
var mockRule = new Mock<ActiveDirectoryRuleDescriptor>(MockBehavior.Loose, null);
var collection = new List<ActiveDirectoryRuleDescriptor>();
mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow);
mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);
mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID);
mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite);
mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAltSecurityIdentities));
collection.Add(mockRule.Object);

mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Type>()))
.Returns(collection);
mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny<Type>())).Returns((string)null);
mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object);
mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
.Returns(Array.Empty<LdapResult<IDirectoryObject>>().ToAsyncEnumerable);

var processor = new ACLProcessor(mockLDAPUtils.Object);
var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor);
var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArrayAsync();

Assert.Single(result);
var actual = result.First();
Assert.Equal(actual.PrincipalType, expectedPrincipalType);
Assert.Equal(actual.PrincipalSID, expectedPrincipalSID);
Assert.False(actual.IsInherited);
Assert.Equal(actual.RightName, expectedRightName);
}

[Fact]
public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAltSecurityIdentities() {
var expectedPrincipalType = Label.Computer;
var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
var expectedRightName = EdgeNames.WriteAltSecurityIdentities;

var mockLDAPUtils = new Mock<ILdapUtils>();
var mockSecurityDescriptor = new Mock<ActiveDirectorySecurityDescriptor>(MockBehavior.Loose, null);
var mockRule = new Mock<ActiveDirectoryRuleDescriptor>(MockBehavior.Loose, null);
var collection = new List<ActiveDirectoryRuleDescriptor>();
mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow);
mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);
mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID);
mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite);
mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAltSecurityIdentities));
collection.Add(mockRule.Object);

mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Type>()))
.Returns(collection);
mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny<Type>())).Returns((string)null);
mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object);
mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
.Returns(Array.Empty<LdapResult<IDirectoryObject>>().ToAsyncEnumerable);

var processor = new ACLProcessor(mockLDAPUtils.Object);
var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor);
var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync();

Assert.Single(result);
var actual = result.First();
Assert.Equal(actual.PrincipalType, expectedPrincipalType);
Assert.Equal(actual.PrincipalSID, expectedPrincipalSID);
Assert.False(actual.IsInherited);
Assert.Equal(actual.RightName, expectedRightName);
}

[Fact]
public async Task ACLProcessor_ProcessACL_GenericWrite_User_WritePublicInformation() {
var expectedPrincipalType = Label.User;
var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
var expectedRightName = EdgeNames.WritePublicInformation;

var mockLDAPUtils = new Mock<ILdapUtils>();
var mockSecurityDescriptor = new Mock<ActiveDirectorySecurityDescriptor>(MockBehavior.Loose, null);
var mockRule = new Mock<ActiveDirectoryRuleDescriptor>(MockBehavior.Loose, null);
var collection = new List<ActiveDirectoryRuleDescriptor>();
mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow);
mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);
mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID);
mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite);
mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WritePublicInformation));
collection.Add(mockRule.Object);

mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Type>()))
.Returns(collection);
mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny<Type>())).Returns((string)null);
mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object);
mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
.Returns(Array.Empty<LdapResult<IDirectoryObject>>().ToAsyncEnumerable);

var processor = new ACLProcessor(mockLDAPUtils.Object);
var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor);
var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArrayAsync();

Assert.Single(result);
var actual = result.First();
Assert.Equal(actual.PrincipalType, expectedPrincipalType);
Assert.Equal(actual.PrincipalSID, expectedPrincipalSID);
Assert.False(actual.IsInherited);
Assert.Equal(actual.RightName, expectedRightName);
}

[Fact]
public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WritePublicInformation() {
var expectedPrincipalType = Label.Computer;
var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
var expectedRightName = EdgeNames.WritePublicInformation;

var mockLDAPUtils = new Mock<ILdapUtils>();
var mockSecurityDescriptor = new Mock<ActiveDirectorySecurityDescriptor>(MockBehavior.Loose, null);
var mockRule = new Mock<ActiveDirectoryRuleDescriptor>(MockBehavior.Loose, null);
var collection = new List<ActiveDirectoryRuleDescriptor>();
mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow);
mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny<string>())).Returns(true);
mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID);
mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite);
mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WritePublicInformation));
collection.Add(mockRule.Object);

mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Type>()))
.Returns(collection);
mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny<Type>())).Returns((string)null);
mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object);
mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
.Returns(Array.Empty<LdapResult<IDirectoryObject>>().ToAsyncEnumerable);

var processor = new ACLProcessor(mockLDAPUtils.Object);
var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor);
var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync();

Assert.Single(result);
var actual = result.First();
Assert.Equal(actual.PrincipalType, expectedPrincipalType);
Assert.Equal(actual.PrincipalSID, expectedPrincipalSID);
Assert.False(actual.IsInherited);
Assert.Equal(actual.RightName, expectedRightName);
}
}
}
Loading