Skip to content

Commit

Permalink
added CanAbuseWeakCertBinding edge for ADCS ESC6 (#300)
Browse files Browse the repository at this point in the history
* added CanAbuseWeakCertBinding edge for ADCS ESC6

* schemagen for CanAbuseUPNCertMapping and CanAbuseWeakCertBinding

* better error handling for weakCertBinding and CanAbuseUPNCertMapping

* added integration test for CanAbuseWeakCertBinding

* added integration test for CanAbuseUPNCertMapping

* performance optimization for TestIssuedSignedBy

* prevent connection exhaustion
  • Loading branch information
irshadaj authored Jan 12, 2024
1 parent 31e9b6b commit 4e9e5b8
Show file tree
Hide file tree
Showing 34 changed files with 869 additions and 55 deletions.
139 changes: 130 additions & 9 deletions cmd/api/src/analysis/ad/adcs_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,100 @@ func TestGoldenCert(t *testing.T) {

}

func TestCanAbuseUPNCertMapping(t *testing.T) {
testContext := integration.NewGraphTestContext(t)
testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
harness.WeakCertBindingAndUPNCertMappingHarness.Setup(testContext)
}, func(harness integration.HarnessDetails, db graph.Database) error {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - CanAbuseUPNCertMapping")

if enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA); err != nil {
t.Logf("failed fetching enterpriseCA nodes: %v", err)
} else if err := ad2.PostCanAbuseUPNCertMapping(context.Background(), db, operation, enterpriseCertAuthorities); err != nil {
t.Logf("failed post processing for %s: %v", ad.CanAbuseUPNCertMapping.String(), err)
}

operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.And(
query.Kind(query.Relationship(), ad.CanAbuseUPNCertMapping),
query.KindIn(query.Start(), ad.EnterpriseCA),
query.KindIn(query.End(), ad.Computer),
)
})); err != nil {
t.Fatalf("error fetching CanAbuseUPNCertMapping relationships; %v", err)
} else {
assert.True(t, len(results) == 2)

// Positive Cases
assert.True(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.EnterpriseCA1))
assert.True(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.EnterpriseCA2))

// Negative Cases
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer1))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer2))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer3))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer4))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer5))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Domain1))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Domain2))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Domain3))
}
return nil
})
return nil
})
}

func TestCanAbuseWeakCertBinding(t *testing.T) {
testContext := integration.NewGraphTestContext(t)
testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
harness.WeakCertBindingAndUPNCertMappingHarness.Setup(testContext)
}, func(harness integration.HarnessDetails, db graph.Database) error {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - CanAbuseWeakCertBinding")

if enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA); err != nil {
t.Logf("failed fetching enterpriseCA nodes: %v", err)
} else if err := ad2.PostCanAbuseWeakCertBinding(context.Background(), db, operation, enterpriseCertAuthorities); err != nil {
t.Logf("failed post processing for %s: %v", ad.CanAbuseWeakCertBinding.String(), err)
}

operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.And(
query.Kind(query.Relationship(), ad.CanAbuseWeakCertBinding),
query.KindIn(query.Start(), ad.EnterpriseCA),
query.KindIn(query.End(), ad.Computer),
)
})); err != nil {
t.Fatalf("error fetching CanAbuseWeakCertBinding relationships; %v", err)
} else {
assert.True(t, len(results) == 1)

// Positive Cases
assert.True(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.EnterpriseCA1))

// Negative Cases
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.EnterpriseCA2))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer1))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer2))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer3))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer4))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Computer5))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Domain1))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Domain2))
assert.False(t, results.Contains(harness.WeakCertBindingAndUPNCertMappingHarness.Domain3))
}
return nil
})
return nil
})
}

func TestIssuedSignedBy(t *testing.T) {
testContext := integration.NewGraphTestContext(t)
testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
Expand All @@ -197,21 +291,48 @@ func TestIssuedSignedBy(t *testing.T) {
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.IssuedSignedBy)
if results1, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.And(
query.Kind(query.Relationship(), ad.IssuedSignedBy),
query.KindIn(query.Start(), ad.EnterpriseCA),
query.KindIn(query.End(), ad.EnterpriseCA),
)
})); err != nil {
t.Fatalf("error fetching IssuedSignedBy relationships; %v", err)
t.Fatalf("error fetching ECA to ECA IssuedSignedBy relationships; %v", err)
} else if results2, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.And(
query.Kind(query.Relationship(), ad.IssuedSignedBy),
query.KindIn(query.Start(), ad.EnterpriseCA),
query.KindIn(query.End(), ad.RootCA),
)
})); err != nil {
t.Fatalf("error fetching ECA to RootCA IssuedSignedBy relationships; %v", err)
} else if results3, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.And(
query.Kind(query.Relationship(), ad.IssuedSignedBy),
query.KindIn(query.Start(), ad.RootCA),
query.KindIn(query.End(), ad.RootCA),
)
})); err != nil {
t.Fatalf("error fetching RootCA to RootCA IssuedSignedBy relationships; %v", err)
} else {
assert.True(t, len(results) == 3)
assert.True(t, len(results1) == 1)
assert.True(t, len(results2) == 1)
assert.True(t, len(results3) == 1)

// Positive Cases
assert.True(t, results.Contains(harness.IssuedSignedByHarness.RootCA2))
assert.True(t, results.Contains(harness.IssuedSignedByHarness.EnterpriseCA1))
assert.True(t, results.Contains(harness.IssuedSignedByHarness.EnterpriseCA2))
assert.True(t, results3.Contains(harness.IssuedSignedByHarness.RootCA2))
assert.True(t, results2.Contains(harness.IssuedSignedByHarness.EnterpriseCA1))
assert.True(t, results1.Contains(harness.IssuedSignedByHarness.EnterpriseCA2))

// Negative Cases
assert.False(t, results.Contains(harness.IssuedSignedByHarness.RootCA1))
assert.False(t, results.Contains(harness.IssuedSignedByHarness.EnterpriseCA3))
assert.False(t, results1.Contains(harness.IssuedSignedByHarness.RootCA1))
assert.False(t, results2.Contains(harness.IssuedSignedByHarness.RootCA1))
assert.False(t, results3.Contains(harness.IssuedSignedByHarness.RootCA1))

assert.False(t, results1.Contains(harness.IssuedSignedByHarness.EnterpriseCA3))
assert.False(t, results2.Contains(harness.IssuedSignedByHarness.EnterpriseCA3))
assert.False(t, results3.Contains(harness.IssuedSignedByHarness.EnterpriseCA3))
}
return nil
})
Expand Down
16 changes: 16 additions & 0 deletions cmd/api/src/database/migration/migrations/v5.5.0.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
-- Copyright 2023 Specter Ops, Inc.
--
-- Licensed under the Apache License, Version 2.0
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- SPDX-License-Identifier: Apache-2.0

-- Add new columns for audit_logs
ALTER TABLE audit_logs
ADD COLUMN IF NOT EXISTS actor_email VARCHAR(330) DEFAULT NULL,
Expand Down
74 changes: 74 additions & 0 deletions cmd/api/src/test/integration/harnesses.go
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,79 @@ func (s *ADCSGoldenCertHarness) Setup(graphTestContext *GraphTestContext) {

}

type WeakCertBindingAndUPNCertMappingHarness struct {
EnterpriseCA1 *graph.Node
EnterpriseCA2 *graph.Node
Computer1 *graph.Node
Computer2 *graph.Node
Computer3 *graph.Node
Computer4 *graph.Node
Computer5 *graph.Node
Domain1 *graph.Node
Domain2 *graph.Node
Domain3 *graph.Node
}

func (s *WeakCertBindingAndUPNCertMappingHarness) Setup(graphTestContext *GraphTestContext) {
domainSid1 := "S-1-5-21-2697957641-2271029196-387917394"
domainSid2 := "S-1-5-21-2697957641-2271029196-387917395"
domainSid3 := "S-1-5-21-2697957641-2271029196-387917396"

// Set up ECA nodes
s.EnterpriseCA1 = graphTestContext.NewActiveDirectoryEnterpriseCAWithThumbprint("EnterpriseCA1", domainSid1, "a")
s.EnterpriseCA2 = graphTestContext.NewActiveDirectoryEnterpriseCAWithThumbprint("EnterpriseCA2", domainSid3, "b")

// Set up Domain nodes
s.Domain1 = graphTestContext.NewActiveDirectoryDomain("Domain1", domainSid1, false, true)
s.Domain2 = graphTestContext.NewActiveDirectoryDomain("Domain2", domainSid2, false, true)
s.Domain3 = graphTestContext.NewActiveDirectoryDomain("Domain3", domainSid3, false, true)

// Set up Computer nodes
s.Computer1 = graphTestContext.NewActiveDirectoryComputer("Computer1", domainSid1)
s.Computer1.Properties.Set(ad.CertificateMappingMethodsRaw.String(), []string{"4"})
s.Computer1.Properties.Set(ad.StrongCertificateBindingEnforcementRaw.String(), []string{"1"})
graphTestContext.UpdateNode(s.Computer1)

s.Computer2 = graphTestContext.NewActiveDirectoryComputer("Computer2", domainSid2)
s.Computer2.Properties.Set(ad.CertificateMappingMethodsRaw.String(), []string{"11"})
s.Computer2.Properties.Set(ad.StrongCertificateBindingEnforcementRaw.String(), []string{"0"})
graphTestContext.UpdateNode(s.Computer2)

s.Computer3 = graphTestContext.NewActiveDirectoryComputer("Computer3", domainSid2)
s.Computer3.Properties.Set(ad.CertificateMappingMethodsRaw.String(), []string{"31"})
s.Computer3.Properties.Set(ad.StrongCertificateBindingEnforcementRaw.String(), []string{"2"})
graphTestContext.UpdateNode(s.Computer3)

s.Computer4 = graphTestContext.NewActiveDirectoryComputer("Computer4", domainSid2)
s.Computer4.Properties.Set(ad.CertificateMappingMethodsRaw.String(), nil)
s.Computer4.Properties.Set(ad.StrongCertificateBindingEnforcementRaw.String(), nil)
graphTestContext.UpdateNode(s.Computer4)

s.Computer5 = graphTestContext.NewActiveDirectoryComputer("Computer5", domainSid3)
s.Computer5.Properties.Set(ad.CertificateMappingMethodsRaw.String(), []string{"15"})
s.Computer5.Properties.Set(ad.StrongCertificateBindingEnforcementRaw.String(), []string{"2"})
graphTestContext.UpdateNode(s.Computer5)

// Set up edges from ECA nodes
graphTestContext.NewRelationship(s.EnterpriseCA1, s.Computer1, ad.CanAbuseUPNCertMapping)
graphTestContext.NewRelationship(s.EnterpriseCA1, s.Computer1, ad.CanAbuseWeakCertBinding)
graphTestContext.NewRelationship(s.EnterpriseCA1, s.Computer2, ad.CanAbuseWeakCertBinding)
graphTestContext.NewRelationship(s.EnterpriseCA1, s.Computer3, ad.CanAbuseUPNCertMapping)

graphTestContext.NewRelationship(s.EnterpriseCA2, s.Computer5, ad.CanAbuseUPNCertMapping)

// Set up edges from Computer nodes
graphTestContext.NewRelationship(s.Computer1, s.Domain1, ad.DCFor)
graphTestContext.NewRelationship(s.Computer2, s.Domain2, ad.DCFor)
graphTestContext.NewRelationship(s.Computer3, s.Domain2, ad.DCFor)
graphTestContext.NewRelationship(s.Computer4, s.Domain2, ad.DCFor)
graphTestContext.NewRelationship(s.Computer5, s.Domain3, ad.DCFor)

// Set up edges from Domain nodes
graphTestContext.NewRelationship(s.Domain1, s.Domain2, ad.TrustedBy, graph.AsProperties(graph.PropertyMap{ad.TrustType: "ParentChild"}))
graphTestContext.NewRelationship(s.Domain2, s.Domain3, ad.TrustedBy, graph.AsProperties(graph.PropertyMap{ad.TrustType: "External"}))
}

type IssuedSignedByHarness struct {
RootCA1 *graph.Node
RootCA2 *graph.Node
Expand Down Expand Up @@ -1541,6 +1614,7 @@ type HarnessDetails struct {
EnrollOnBehalfOfHarnessTwo EnrollOnBehalfOfHarnessTwo
ADCSGoldenCertHarness ADCSGoldenCertHarness
IssuedSignedByHarness IssuedSignedByHarness
WeakCertBindingAndUPNCertMappingHarness WeakCertBindingAndUPNCertMappingHarness
TrustedForNTAuthHarness TrustedForNTAuthHarness
NumCollectedActiveDirectoryDomains int
AZInboundControlHarness AZInboundControlHarness
Expand Down
Loading

0 comments on commit 4e9e5b8

Please sign in to comment.