diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index 5b367205..9bb7368e 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -1159,5 +1159,635 @@ public void Test_ACLProcessor_IsACLProtected_NotProtected() { var result = processor.IsACLProtected(Array.Empty()); Assert.False(result); } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_SetSecurityDescriptorBinaryForm_Catch() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.SetSecurityDescriptorBinaryForm(It.IsAny())).Throws(new OverflowException()); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_CertTemplate_PKINameFlag() + { + var expectedPrincipalType = Label.CertTemplate; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.WritePKINameFlag; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.PKINameFlag)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.CertTemplate, 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_ExtendedRight_Domain_DSReplicationGetChangesInFilteredSet() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GetChangesInFilteredSet; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesInFilteredSet)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).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_WriteSPN() + { + var expectedPrincipalType = Label.User; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.WriteSPN; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.WriteSPN)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().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_UserAccountRestrictions() + { + var expectedPrincipalType = Label.Computer; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.WriteAccountRestrictions; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.UserAccountRestrictions)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().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_Domain_WriteGPLink() + { + var expectedPrincipalType = Label.Domain; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.WriteGPLink; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.WriteGPLink)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, 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_OU_WriteGPLink() + { + var expectedPrincipalType = Label.OU; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.WriteGPLink; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.WriteGPLink)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.OU, 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_AddKeyPrincipal() + { + var expectedPrincipalType = Label.User; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddKeyCredentialLink; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.AddKeyPrincipal)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().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_AddKeyPrincipal() + { + var expectedPrincipalType = Label.Computer; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddKeyCredentialLink; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.AddKeyPrincipal)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().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_CertTemplate_PKIEnrollmentFlag() + { + var expectedPrincipalType = Label.CertTemplate; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.WritePKIEnrollmentFlag; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.PKIEnrollmentFlag)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.CertTemplate, 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_LAPS_CertTemplate_AllGuid() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + //Return a directory object from pagedquery for the schemaid to simulate LAPS + var searchResults = new[] + { + LdapResult.Ok(new MockDirectoryObject( + "abc123" + , new Dictionary() + { + {LDAPProperties.Name, LDAPProperties.LegacyLAPSPassword} + }, null,null)), + }; + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(searchResults.ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.CertTemplate, 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_LAPS_CertTemplate_Enroll() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.Enroll; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.Enroll)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + //Return a directory object from pagedquery for the schemaid to simulate LAPS + var searchResults = new[] + { + LdapResult.Ok(new MockDirectoryObject( + "abc123" + , new Dictionary() + { + {LDAPProperties.Name, LDAPProperties.LegacyLAPSPassword} + }, null,null)), + }; + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(searchResults.ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.CertTemplate, 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_EnterpriseCA() + { + var expectedPrincipalType = Label.EnterpriseCA; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.Enroll; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).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.Enroll)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.EnterpriseCA, 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_EnterpriseCA_ManageCA() + { + var expectedPrincipalType = Label.EnterpriseCA; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.ManageCA; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns((ActiveDirectoryRights)CertificationAuthorityRights.ManageCA); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.EnterpriseCA, 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_EnterpriseCA_ManageCertificates() + { + var expectedPrincipalType = Label.EnterpriseCA; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.ManageCertificates; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns((ActiveDirectoryRights)CertificationAuthorityRights.ManageCertificates); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.EnterpriseCA, 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_EnterpriseCA_Enroll() + { + var expectedPrincipalType = Label.EnterpriseCA; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.Enroll; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns((ActiveDirectoryRights)CertificationAuthorityRights.Enroll); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.EnterpriseCA, 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); + } } } \ No newline at end of file diff --git a/test/unit/CertAbuseProcessorTest.cs b/test/unit/CertAbuseProcessorTest.cs index 3a5ce818..fe54e783 100644 --- a/test/unit/CertAbuseProcessorTest.cs +++ b/test/unit/CertAbuseProcessorTest.cs @@ -1,4 +1,12 @@ using System; +using System.DirectoryServices; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using Newtonsoft.Json; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Processors; +using Xunit; using Xunit.Abstractions; namespace CommonLibTest { @@ -77,26 +85,27 @@ public void Dispose() { // Assert.Empty(results); // } - // [Fact] - // public void CertAbuseProcessor_ProcessCAPermissions_NullSecurity_ReturnsNull() - // { - // var mockUtils = new Mock(); - // var processor = new CertAbuseProcessor(mockUtils.Object); + [Fact] + public async Task CertAbuseProcessor_ProcessCAPermissions_NullSecurity_ReturnsNull() + { + var processor = new CertAbuseProcessor(new MockLdapUtils()); - // var results = processor.ProcessRegistryEnrollmentPermissions(null, null, "test"); + var results = await processor.ProcessRegistryEnrollmentPermissions(null, "DUMPSTER.FIRE", null, "test"); - // Assert.Empty(results); - // } + Assert.Equal("Value cannot be null. (Parameter 'machineName')", results.FailureReason); + Assert.False(results.Collected); + Assert.Empty(results.Data); + } // [WindowsOnlyFact] // public void CertAbuseProcessor_ProcessCAPermissions_ReturnsCorrectValues() // { - // var mockUtils = new Mock(); + // var mockUtils = new Mock(); // var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); // mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); // var processor = new CertAbuseProcessor(mockUtils.Object); // var bytes = Helpers.B64ToBytes(CASecurityFixture); - + // // var results = processor.ProcessCAPermissions(bytes, "TESTLAB.LOCAL", "test", false); // _testOutputHelper.WriteLine(JsonConvert.SerializeObject(results, Formatting.Indented)); // Assert.Contains(results, diff --git a/test/unit/Facades/LSAMocks/ErrorCaseMocks/MockFailLSAPolicy_GetLocalDomainInformation.cs b/test/unit/Facades/LSAMocks/ErrorCaseMocks/MockFailLSAPolicy_GetLocalDomainInformation.cs new file mode 100644 index 00000000..03441e0b --- /dev/null +++ b/test/unit/Facades/LSAMocks/ErrorCaseMocks/MockFailLSAPolicy_GetLocalDomainInformation.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades.LSAMocks.WorkstationMocks +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailLSAPolicy_GetLocalDomainInformation : ILSAPolicy + { + public Result<(string Name, string Sid)> GetLocalDomainInformation() + { + return NtStatus.StatusAccessDenied; + } + + public Result> GetPrincipalsWithPrivilege(string userRight) + { + throw new NotImplementedException(); + } + + public Result> + GetResolvedPrincipalsWithPrivilege(string userRight) + { + return new List<(SecurityIdentifier sid, string Name, SharedEnums.SidNameUse Use, string Domain)> + { + (new SecurityIdentifier("S-1-5-32-555"), "Remote Desktop Users", SharedEnums.SidNameUse.Alias, "abc"), + (new SecurityIdentifier("S-1-5-32-544"), "Administrators", SharedEnums.SidNameUse.Alias, "abc"), + (new SecurityIdentifier($"{Consts.MockWorkstationMachineSid}-1000"), "John", SharedEnums.SidNameUse.User, "abc"), + (new SecurityIdentifier($"{Consts.MockWorkstationMachineSid}-1001"), "TestGroup", SharedEnums.SidNameUse.Alias, "abc"), + }; + } + + public Result<(string Name, SharedEnums.SidNameUse Use, string Domains)> LookupSid(SecurityIdentifier sid) + { + throw new NotImplementedException(); + } + + public Result> + LookupSids(SecurityIdentifier[] sids) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/LSAMocks/ErrorCaseMocks/MockFailLSAPolicy_GetResolvedPrincipalsWithPrivilege.cs b/test/unit/Facades/LSAMocks/ErrorCaseMocks/MockFailLSAPolicy_GetResolvedPrincipalsWithPrivilege.cs new file mode 100644 index 00000000..0ba1dfa5 --- /dev/null +++ b/test/unit/Facades/LSAMocks/ErrorCaseMocks/MockFailLSAPolicy_GetResolvedPrincipalsWithPrivilege.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades.LSAMocks.WorkstationMocks +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailLSAPolicy_GetResolvedPrincipalsWithPrivilege : ILSAPolicy + { + public Result<(string Name, string Sid)> GetLocalDomainInformation() + { + return ("WIN10", Consts.MockWorkstationMachineSid); + } + + public Result> GetPrincipalsWithPrivilege(string userRight) + { + throw new NotImplementedException(); + } + + public Result> + GetResolvedPrincipalsWithPrivilege(string userRight) + { + return NtStatus.StatusAccessDenied; + } + + public Result<(string Name, SharedEnums.SidNameUse Use, string Domains)> LookupSid(SecurityIdentifier sid) + { + throw new NotImplementedException(); + } + + public Result> + LookupSids(SecurityIdentifier[] sids) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailAliasAdministrators_PreviouslyCached.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailAliasAdministrators_PreviouslyCached.cs new file mode 100644 index 00000000..175a9b84 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailAliasAdministrators_PreviouslyCached.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailAliasAdministrators_PreviouslyCached : ISAMAlias + { + public Result> GetMembers() + { + return new List() + { + new("S-1-5-21-321011808-3761883066-353627080-1000"), + new("S-1-5-21-321011808-3761883066-353627080-1000"), + new("S-1-5-21-4243161961-3815211218-2888324771-512"), + }; + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailAlias_PreviouslyCached.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailAlias_PreviouslyCached.cs new file mode 100644 index 00000000..5406aeb9 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailAlias_PreviouslyCached.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailAlias_PreviouslyCached : ISAMAlias + { + public Result> GetMembers() + { + return new List() + { + new("S-1-5-21-321011808-3761883066-353627080-1003"), + new("S-1-5-21-321011808-3761883066-353627080-1003"), + new("S-1-5-32-544"), + }; + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_GetAliases.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_GetAliases.cs new file mode 100644 index 00000000..bbf61b4a --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_GetAliases.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + public class MockFailDomainBuiltIn_GetAliases : ISAMDomain + { + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalByRid(int rid) + { + throw new System.NotImplementedException(); + } + + public Result> GetAliases() + { + // var results = new List<(string, int)> + // { + // ("Administrators", 544), + // ("Users", 545) + // }; + // return results; + return NtStatus.StatusAccessDenied; + } + + public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) + { + switch (rid) + { + case 544: + return new MockDCAliasAdministrators(); + case 545: + return new MockDCAliasUsers(); + default: + throw new IndexOutOfRangeException(); + } + } + + public Result OpenAlias(string name) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_GetMembers.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_GetMembers.cs new file mode 100644 index 00000000..3efdfce9 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_GetMembers.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + public class MockFailDomainBuiltIn_GetMembers : ISAMDomain + { + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalByRid(int rid) + { + throw new System.NotImplementedException(); + } + + public Result> GetAliases() + { + var results = new List<(string, int)> + { + ("Users", 545) + }; + return results; + } + + public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) + { + switch (rid) + { + case 545: + return new MockFailSAMAliasUsers_GetMembers(); + default: + throw new IndexOutOfRangeException(); + } + } + + public Result OpenAlias(string name) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_OpenAlias.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_OpenAlias.cs new file mode 100644 index 00000000..f17cc861 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_OpenAlias.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + public class MockFailDomainBuiltIn_OpenAlias : ISAMDomain + { + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalByRid(int rid) + { + throw new System.NotImplementedException(); + } + + public Result> GetAliases() + { + var results = new List<(string, int)> + { + ("Administrators", 544) + }; + return results; + } + + public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) + { + // switch (rid) + // { + // case 544: + // return new MockDCAliasAdministrators(); + // case 545: + // return new MockDCAliasUsers(); + // default: + // throw new IndexOutOfRangeException(); + // } + return NtStatus.StatusAccessDenied; + } + + public Result OpenAlias(string name) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_PreviouslyCached.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_PreviouslyCached.cs new file mode 100644 index 00000000..3a5961c6 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailDomainBuiltIn_PreviouslyCached.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + public class MockFailDomainBuiltIn_PreviouslyCached : ISAMDomain + { + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalByRid(int rid) + { + throw new System.NotImplementedException(); + } + + public Result> GetAliases() + { + var result = new List<(string, int)> + { + ("administrators", 544), + ("remote desktop users", 555) + }; + return result; + } + + public Result OpenAlias(int rid, SAMEnums.AliasOpenFlags desiredAccess = SAMEnums.AliasOpenFlags.ListMembers) + { + if (rid == 544) + { + return new MockFailAliasAdministrators_PreviouslyCached(); + } + if (rid == 555) + { + return new MockFailAlias_PreviouslyCached(); + } + + throw new NotImplementedException(); + } + + public Result OpenAlias(string name) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMAliasUsers_GetMembers.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMAliasUsers_GetMembers.cs new file mode 100644 index 00000000..dae83c1e --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMAliasUsers_GetMembers.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using SharpHoundRPC; +using SharpHoundRPC.Wrappers; +using System.Security.Principal; + +namespace CommonLibTest.Facades +{ + public class MockFailSAMAliasUsers_GetMembers : ISAMAlias + { + public Result> GetMembers() + { + return NtStatus.StatusAccessDenied; + } + } +} + diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetAliases.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetAliases.cs new file mode 100644 index 00000000..3400e9fe --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetAliases.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_GetAliases : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("BUILTIN", 1) + }; + return domains; + } + + public virtual Result LookupDomain(string name) + { + throw new System.NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + throw new System.NotImplementedException(); + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase)) + { + return new MockFailDomainBuiltIn_GetAliases(); + } + + throw new NotImplementedException(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetDomains.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetDomains.cs new file mode 100644 index 00000000..96d0c51e --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetDomains.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_GetDomains : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + // var domains = new List<(string, int)> + // { + // ("BUILTIN", 1) + // }; + // return domains; + + return Result>.Fail(NtStatus.StatusAccessDenied); + } + + public virtual Result LookupDomain(string name) + { + throw new System.NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + throw new System.NotImplementedException(); + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase)) + { + return new MockDCDomainBuiltIn(); + } + + throw new NotImplementedException(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetMachineSid.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetMachineSid.cs new file mode 100644 index 00000000..3179e1de --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetMachineSid.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_GetMachineSid : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("BUILTIN", 1) + }; + return domains; + } + + public virtual Result LookupDomain(string name) + { + throw new System.NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + // var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + // return Result.Ok(securityIdentifier); + + return Result.Fail(NtStatus.StatusAccessDenied); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + throw new System.NotImplementedException(); + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase)) + { + return new MockDCDomainBuiltIn(); + } + + throw new NotImplementedException(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetMembers.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetMembers.cs new file mode 100644 index 00000000..79a16931 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_GetMembers.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_GetMembers : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("BUILTIN", 1) + }; + return domains; + } + + public virtual Result LookupDomain(string name) + { + throw new System.NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + throw new System.NotImplementedException(); + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase)) + { + return new MockFailDomainBuiltIn_GetMembers(); + } + + throw new NotImplementedException(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_LookupPrincipalBySid.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_LookupPrincipalBySid.cs new file mode 100644 index 00000000..adf14877 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_LookupPrincipalBySid.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_LookupPrincipalBySid : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("WIN10", 0), + ("BUILTIN", 1) + }; + return domains; + } + + public Result LookupDomain(string name) + { + throw new NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + // switch (securityIdentifier.Value) + // { + // case "S-1-5-21-321011808-3761883066-353627080-500": + // return ("Administrator", SharedEnums.SidNameUse.User); + // case "S-1-5-21-321011808-3761883066-353627080-1000": + // return ("DefaultUser", SharedEnums.SidNameUse.User); + // case "S-1-5-21-321011808-3761883066-353627080-1003": + // return ("TestGroup", SharedEnums.SidNameUse.Alias); + // default: + // throw new IndexOutOfRangeException(); + // } + return NtStatus.StatusAccessDenied; + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("win10", StringComparison.OrdinalIgnoreCase)) + { + return new MockWorkstationDomainWIN10(); + } + return new MockWorkstationDomainBuiltIn(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + return new MockWorkstationDomainBuiltIn(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_OpenAlias.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_OpenAlias.cs new file mode 100644 index 00000000..6c32e81d --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_OpenAlias.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_OpenAlias : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("BUILTIN", 1) + }; + return domains; + } + + public virtual Result LookupDomain(string name) + { + throw new System.NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + throw new System.NotImplementedException(); + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase)) + { + return new MockFailDomainBuiltIn_OpenAlias(); + } + + throw new NotImplementedException(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_OpenDomain.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_OpenDomain.cs new file mode 100644 index 00000000..3a21f8a3 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_OpenDomain.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_OpenDomain : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("BUILTIN", 1) + }; + return domains; + } + + public virtual Result LookupDomain(string name) + { + throw new System.NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + throw new System.NotImplementedException(); + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + // if (domainName.Equals("builtin", StringComparison.OrdinalIgnoreCase)) + // { + // return new MockDCDomainBuiltIn(); + // } + + return NtStatus.StatusAccessDenied; + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_PreviouslyCached.cs b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_PreviouslyCached.cs new file mode 100644 index 00000000..20371103 --- /dev/null +++ b/test/unit/Facades/SAMMocks/ErrorCaseMocks/LocalGroupProcessorErrorCases/MockFailSAMServer_PreviouslyCached.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; +using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Shared; +using SharpHoundRPC.Wrappers; + +namespace CommonLibTest.Facades +{ + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public class MockFailSAMServer_PreviouslyCached : ISAMServer + { + public bool IsNull { get; } + public Result> GetDomains() + { + var domains = new List<(string, int)> + { + ("WIN10", 0), + ("BUILTIN", 1) + }; + return domains; + } + + public Result LookupDomain(string name) + { + throw new NotImplementedException(); + } + + public Result GetMachineSid(string testName = null) + { + var securityIdentifier = new SecurityIdentifier(Consts.MockWorkstationMachineSid); + return Result.Ok(securityIdentifier); + } + + public Result<(string Name, SharedEnums.SidNameUse Type)> LookupPrincipalBySid(SecurityIdentifier securityIdentifier) + { + switch (securityIdentifier.Value) + { + case "S-1-5-21-321011808-3761883066-353627080-500": + return ("Administrator", SharedEnums.SidNameUse.User); + case "S-1-5-21-321011808-3761883066-353627080-1000": + return ("DefaultUser", SharedEnums.SidNameUse.User); + case "S-1-5-21-321011808-3761883066-353627080-1003": + return ("TestGroup", SharedEnums.SidNameUse.Alias); + default: + throw new IndexOutOfRangeException(); + } + } + + public Result OpenDomain(string domainName, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + if (domainName.Equals("win10", StringComparison.OrdinalIgnoreCase)) + { + return new MockWorkstationDomainWIN10(); + } + return new MockFailDomainBuiltIn_PreviouslyCached(); + } + + public Result OpenDomain(SecurityIdentifier securityIdentifier, + SAMEnums.DomainAccessMask requestedDomainAccess = SAMEnums.DomainAccessMask.ListAccounts | SAMEnums.DomainAccessMask.Lookup) + { + return new MockFailDomainBuiltIn_PreviouslyCached(); + } + } +} \ No newline at end of file diff --git a/test/unit/GroupProcessorTest.cs b/test/unit/GroupProcessorTest.cs index c8f621e7..2a670bce 100644 --- a/test/unit/GroupProcessorTest.cs +++ b/test/unit/GroupProcessorTest.cs @@ -31,6 +31,11 @@ public class GroupProcessorTest "CN=NonExistent,CN=Users,DC=testlab,DC=local" }; + private readonly Result[] _testMembershipReturnFail = + { + Result.Fail() + }; + private readonly ITestOutputHelper _testOutputHelper; private GroupProcessor _baseProcessor; @@ -134,5 +139,25 @@ public async Task GroupProcessor_ReadGroupMembers_ReturnsCorrectMembers() Assert.Equal(4, results.Length); Assert.Equal(expected, results); } + + [Fact] + public async Task GroupProcessor_ReadGroupMembers_EmptyMembers_FailRangedRetrieval() + { + var mockUtils = new Mock(); + mockUtils.Setup(x => x.RangedRetrieval(It.IsAny(), It.IsAny(), It.IsAny())).Returns(_testMembershipReturnFail.ToAsyncEnumerable()); + var processor = new GroupProcessor(mockUtils.Object); + + var results = await processor + .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", Array.Empty()).ToArrayAsync(); + foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); + Assert.Empty(results); + } + + [Fact] + public void GroupProcessor_GetPrimaryGroupInfo_NullObjectID_ReturnsNull() + { + var result = GroupProcessor.GetPrimaryGroupInfo("513", null); + Assert.Null(result); + } } } \ No newline at end of file diff --git a/test/unit/LdapPropertyTests.cs b/test/unit/LdapPropertyTests.cs index 485dd174..eb4e048c 100644 --- a/test/unit/LdapPropertyTests.cs +++ b/test/unit/LdapPropertyTests.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.DirectoryServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.Threading.Tasks; using CommonLibTest.Facades; +using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; @@ -989,5 +992,657 @@ public void LDAPPropertyProcessor_ParseAllProperties_GUID() { Assert.True(hasGuid); Assert.Equal(guidExpected.ToString(), guidActual); } + + [Fact] + public void LDAPPropertyProcessor_ReadACAProperties() { + var ecdsa = ECDsa.Create(); + var req = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256); + var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5)); + + var bytes = cert.Export(X509ContentType.Cert, "abc"); + var mock = new MockDirectoryObject( + "CN\u003dDUMPSTER-DC01-CA,CN\u003dAIA,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + {LDAPProperties.CACertificate, bytes} + }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); + + var test = LdapPropertyProcessor.ReadRootCAProperties(mock); + var keys = test.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + + Assert.Contains("whencreated", keys); + Assert.Contains("certthumbprint", keys); + Assert.Contains("certname", keys); + Assert.Contains("certchain", keys); + Assert.Contains("hasbasicconstraints", keys); + Assert.Contains("basicconstraintpathlength", keys); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestExpirePassword() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.ExpirePasswordsOnSmartCardOnlyAccounts, "True"} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("expirepasswordsonsmartcardonlyaccounts", test.Keys); + Assert.Equal(true, test["expirepasswordsonsmartcardonlyaccounts"]); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestMachineAccountQuota() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.MachineAccountQuota, "4"} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("machineaccountquota", test.Keys); + Assert.Equal(4, test["machineaccountquota"] as long?); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestMinPwdLength() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.MinPwdLength, "4"} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("minpwdlength", test.Keys); + Assert.Equal(4, test["minpwdlength"] as long?); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestPwdProperties() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.PwdProperties, "4"} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("pwdproperties", test.Keys); + Assert.Equal(4, test["pwdproperties"] as long?); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestPwdHistoryLength() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.PwdHistoryLength, "4"} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("pwdhistorylength", test.Keys); + Assert.Equal(4, test["pwdhistorylength"] as long?); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestLockoutThreshold() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.LockoutThreshold, "4"} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("lockoutthreshold", test.Keys); + Assert.Equal(4, test["lockoutthreshold"] as long?); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestMinPwdAge() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.MinPwdAge, long.MinValue} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("minpwdage", test.Keys); + Assert.Equal("Forever", test["minpwdage"] as string); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestMaxPwdAge() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.MaxPwdAge, long.MinValue} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("maxpwdage", test.Keys); + Assert.Equal("Forever", test["maxpwdage"] as string); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestLockoutDuration() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.LockoutDuration, long.MinValue} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("lockoutduration", test.Keys); + Assert.Equal("Forever", test["lockoutduration"] as string); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_TestLockOutObservationWindow() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.LockOutObservationWindow, long.MinValue} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("lockoutobservationwindow", test.Keys); + Assert.Equal(long.MinValue, test["lockoutobservationwindow"] as long?); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadUserProperties_TestLastLogonNull() + { + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "66048"}, + {"lastlogontimestamp", "132670318095676525"}, + {"homedirectory", @"\\win10\testdir"}, + {"mail", "test@testdomain.com"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC/win10" + } + }, + {"admincount", "1"}, + { + "sidhistory", new[] + { + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + {"pwdlastset", "132131667346106691"} + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("lastlogon", keys); + Assert.Equal(-1, (long)props["lastlogon"]); + + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_ConvertNanoDuration_TestReadableString() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.MaxPwdAge, -11211100000000} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.Contains("maxpwdage", test.Keys); + Assert.Equal("12 days, 23 hours, 25 minutes, 10 seconds", test["maxpwdage"] as string); + } + + [Fact] + public async void LDAPPropertyProcessor_ReadDomainProperties_ConvertNanoDuration_TestNull() + { + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {LDAPProperties.MaxPwdAge, 100} + }, "S-1-5-21-3130019616-2776909439-2417379446",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadDomainProperties(mock,"testlab.local"); + Assert.DoesNotContain("maxpwdage", test.Keys); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadUserProperties_TestLastLogonTimeStampNull() + { + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "66048"}, + {"homedirectory", @"\\win10\testdir"}, + {"mail", "test@testdomain.com"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC/win10" + } + }, + {"admincount", "1"}, + { + "sidhistory", new[] + { + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + {"pwdlastset", "132131667346106691"} + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("lastlogontimestamp", keys); + Assert.Equal(-1, (long)props["lastlogontimestamp"]); + + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadUserProperties_TestPasswordLastSetNull() + { + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "66048"}, + {"homedirectory", @"\\win10\testdir"}, + {"mail", "test@testdomain.com"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC/win10" + } + }, + {"admincount", "1"}, + { + "sidhistory", new[] + { + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("lastlogontimestamp", keys); + Assert.Equal(-1, (long)props["lastlogontimestamp"]); + + } + + [Fact] + public async Task LDAPPropertyProcessor_ReadUserProperties_TestDelegatesNull() + { + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1000000.ToString()}, + {LDAPProperties.LastLogon, "132673011142753043"}, + {LDAPProperties.LastLogonTimestamp, "132670318095676525"}, + {"homedirectory", @"\\win10\testdir"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC\\win10" + } + }, + {"admincount", "1"}, + { + "sidhistory", new[] + { + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + {"pwdlastset", "132131667346106691"}, + { + "msds-allowedtodelegateto", new[] + { + null, + "rdpman/win10" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", ""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("allowedtodelegate", keys); + var atd = props["allowedtodelegate"] as string[]; + Assert.Equal(2, atd.Length); + //Assert.Contains("host/primary", atd); + Assert.Contains("rdpman/win10", atd); + + var atdr = test.AllowedToDelegate; + Assert.Equal(1, atdr.Length); + var expected = new TypedPrincipal[] + { + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1104", + ObjectType = Label.Computer + } + }; + Assert.Equal(expected, atdr); + } + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDelegatesNull() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "sidhistory", new[] + { + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + { + "msds-allowedtodelegateto", new[] + { + null, + "ldap/PRIMARY.testlab.local", + "ldap/PRIMARY" + } + }, + {"pwdlastset", "132131667346106691"}, + { + "serviceprincipalname", new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("allowedtodelegate", keys); + var atd = props["allowedtodelegate"] as string[]; + Assert.Equal(3, atd.Length); + + //AllowedToDelegate + Assert.Single(test.AllowedToDelegate); + Assert.Contains(new TypedPrincipal + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", + ObjectType = Label.Computer + }, test.AllowedToDelegate); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_AllowedToActOnBehalfOfOtherIdentity() + { + var mockUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedPrincipalType = Label.CertTemplate; + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-allowedtoactonbehalfofotheridentity", + + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); + mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); + mockSecurityDescriptor.Setup(m => m.SetSecurityDescriptorBinaryForm(It.IsAny())).Throws(new OverflowException()); + mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + collection.Add(mockRule.Object); + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + + var processor = new LdapPropertyProcessor(mockUtils.Object); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + + //AllowedToAct + Assert.Single(test.AllowedToAct); + Assert.Contains(new TypedPrincipal + { + ObjectIdentifier = expectedPrincipalSID, + ObjectType = expectedPrincipalType + }, test.AllowedToAct); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ConvertEncryptionTypes_SupportedEncrypTionTypes_0() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-supportedencryptiontypes", "0" + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("supportedencryptiontypes", keys); + + Assert.Equal(new List(["Not defined"]), (List)props["supportedencryptiontypes"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_ConvertEncryptionTypes_DES_CBC_CRC() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-supportedencryptiontypes", "1" + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("supportedencryptiontypes", keys); + + Assert.Equal(new List(["DES-CBC-CRC"]), (List)props["supportedencryptiontypes"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_ConvertEncryptionTypes_DES_CBC_MD5() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-supportedencryptiontypes", "2" + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("supportedencryptiontypes", keys); + + Assert.Equal(new List(["DES-CBC-MD5"]), (List)props["supportedencryptiontypes"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_ConvertEncryptionTypes_RC4_HMAC_MD5() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-supportedencryptiontypes", "4" + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("supportedencryptiontypes", keys); + + Assert.Equal(new List(["RC4-HMAC-MD5"]), (List)props["supportedencryptiontypes"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_ConvertEncryptionTypes_AES128_CTS_HMAC_SHA1_96() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-supportedencryptiontypes", "8" + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("supportedencryptiontypes", keys); + + Assert.Equal(new List(["AES128-CTS-HMAC-SHA1-96"]), (List)props["supportedencryptiontypes"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_ConvertEncryptionTypes_AES256_CTS_HMAC_SHA1_96() + { + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "msds-supportedencryptiontypes", "16" + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); + + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("supportedencryptiontypes", keys); + + Assert.Equal(new List(["AES256-CTS-HMAC-SHA1-96"]), (List)props["supportedencryptiontypes"]); + } } } diff --git a/test/unit/LocalGroupProcessorTest.cs b/test/unit/LocalGroupProcessorTest.cs index 01d68d67..fcdf95e3 100644 --- a/test/unit/LocalGroupProcessorTest.cs +++ b/test/unit/LocalGroupProcessorTest.cs @@ -9,6 +9,8 @@ using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; using SharpHoundRPC; +using SharpHoundRPC.SAMRPCNative; +using SharpHoundRPC.Wrappers; using Xunit; using Xunit.Abstractions; @@ -64,6 +66,7 @@ public async Task LocalGroupProcessor_TestDomainController() var mockProcessor = new Mock(new MockLdapUtils(), null); var mockSamServer = new MockDCSAMServer(); mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) @@ -113,6 +116,59 @@ public async Task LocalGroupProcessor_ResolveGroupName_DC() ; Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", result.ObjectId); } + + [Fact] + public async Task LocalGroupProcessor_ResolveGroupName_NullComputerDomainSid_DC_NotBuiltIn() + { + var mockUtils = new Mock(); + var proc = new LocalGroupProcessor(mockUtils.Object); + + var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", + new object[] + { + "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", null, "TESTLAB.LOCAL", 544, true, false + }); + + var result = await resultTask; + + Assert.Equal(null, result); + } + + [Fact] + public async Task LocalGroupProcessor_ResolveGroupName_NullComputerDomainSid_NonDC() + { + var mockUtils = new Mock(); + var proc = new LocalGroupProcessor(mockUtils.Object); + + var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", + new object[] + { + "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", null, "TESTLAB.LOCAL", 544, false, false + }); + + var result = await resultTask; + + Assert.Equal(null, result); + } + + [Fact] + public async Task LocalGroupProcessor_ResolveGroupName_DC_NotBuiltIn() + { + var mockUtils = new Mock(); + var proc = new LocalGroupProcessor(mockUtils.Object); + + var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", + new object[] + { + "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", "S-1-5-32-123-123-1000", "TESTLAB.LOCAL", 544, true, false + }); + + var result = await resultTask; + + Assert.Equal("IGNOREME", result.PrincipalName); + + Assert.Equal("S-1-5-32-123-123-1000-544", result.ObjectId); + } [Fact] public async Task LocalGroupProcessor_TestTimeout() { @@ -136,5 +192,198 @@ public async Task LocalGroupProcessor_TestTimeout() { var status = receivedStatus[0]; Assert.Equal("Timeout", status.Status); } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_GetMachineSidResultFailed() { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_GetMachineSid(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + Assert.Empty(results); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("GetMachineSid", status.Task); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_GetDomainsResultFailed() { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_GetDomains(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + Assert.Empty(results); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("GetDomains", status.Task); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_OpenDomainResultFailed() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_OpenDomain(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + Assert.Empty(results); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("OpenDomain - BUILTIN", status.Task); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_GetAliasesFailed() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_GetAliases(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + Assert.Empty(results); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("GetAliases - BUILTIN", status.Task); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_OpenAliasFailed() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_OpenAlias(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + + var failureReason = results[0]; + Assert.Contains("StatusAccessDenied", failureReason.FailureReason); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("OpenAlias - Administrators", status.Task); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_GetMembersFailed() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_GetMembers(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + + var failureReason = results[0]; + Assert.Contains("StatusAccessDenied", failureReason.FailureReason); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("GetMembersInAlias - Users", status.Task); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_LookupPrincipalBySid() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_LookupPrincipalBySid(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", false) + .ToArrayAsync(); + + Assert.Equal(3, results.Length); + var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); + Assert.Single(adminGroup.Results); + Assert.Equal($"{machineDomainSid}-544", adminGroup.ObjectIdentifier); + Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); + var rdpGroup = results.First(x => x.ObjectIdentifier.EndsWith("-555")); + Assert.Equal(1, rdpGroup.Results.Length); + Assert.Collection(rdpGroup.Results, + principal => + { + Assert.Equal($"{machineDomainSid}-544", principal.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, principal.ObjectType); + }); + } + + [Fact] + public async Task LocalGroupProcessor_GetLocalGroups_PreviouslyCached() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockSamServer = new MockFailSAMServer_PreviouslyCached(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1001"; + var results = await processor.GetLocalGroups("win10.testlab.local", machineDomainSid, "TESTLAB.LOCAL", false) + .ToArrayAsync(); + + Assert.Equal(3, results.Length); + var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); + + Assert.Equal($"{machineDomainSid}-544", adminGroup.ObjectIdentifier); + Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[1].ObjectIdentifier); + var rdpGroup = results.First(x => x.ObjectIdentifier.EndsWith("-555")); + Assert.Equal(3, rdpGroup.Results.Length); + Assert.Collection(rdpGroup.Results, + principal => + { + Assert.Equal($"{machineDomainSid}-1003", principal.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, principal.ObjectType); + + }, principal => + { + Assert.Equal($"{Consts.MockWorkstationMachineSid}-1003", principal.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, principal.ObjectType); + }, principal => + { + Assert.Equal($"{machineDomainSid}-544", principal.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, principal.ObjectType); + }); + } } } \ No newline at end of file diff --git a/test/unit/UserRightsAssignmentProcessorTest.cs b/test/unit/UserRightsAssignmentProcessorTest.cs index 90a5a352..0f4f80cf 100644 --- a/test/unit/UserRightsAssignmentProcessorTest.cs +++ b/test/unit/UserRightsAssignmentProcessorTest.cs @@ -88,5 +88,57 @@ public async Task UserRightsAssignmentProcessor_TestTimeout() { var status = receivedStatus[0]; Assert.Equal("Timeout", status.Status); } + + [WindowsOnlyFact] + public async Task UserRightsAssignmentProcessor_TestGetLocalDomainInformationFail() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockLSAPolicy = new MockFailLSAPolicy_GetLocalDomainInformation(); + mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(()=> { + Task.Delay(100).Wait(); + return NtStatus.StatusAccessDenied; + }); + mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockDomainSid}-1001"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetUserRightsAssignments("win10.testlab.local", machineDomainSid, "testlab.local", false) + .ToArrayAsync(); + + Assert.Empty(results); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("LSAGetMachineSID", status.Task); + } + + [WindowsOnlyFact] + public async Task UserRightsAssignmentProcessor_TestGetResolvedPrincipalsWithPrivilegeFail() + { + var mockProcessor = new Mock(new MockLdapUtils(), null); + var mockLSAPolicy = new MockFailLSAPolicy_GetResolvedPrincipalsWithPrivilege(); + mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockDomainSid}-1001"; + var receivedStatus = new List(); + processor.ComputerStatusEvent += async status => { + receivedStatus.Add(status); + }; + var results = await processor.GetUserRightsAssignments("win10.testlab.local", machineDomainSid, "testlab.local", false) + .ToArrayAsync(); + + Assert.Single(results); + + var result = results[0]; + Assert.False(result.Collected); + Assert.Equal("LSAEnumerateAccountsWithUserRights returned StatusAccessDenied", result.FailureReason); + Assert.Single(receivedStatus); + var status = receivedStatus[0]; + Assert.Equal("StatusAccessDenied", status.Status); + Assert.Equal("LSAEnumerateAccountsWithUserRight", status.Task); + } } } \ No newline at end of file