diff --git a/cmd/api/src/analysis/ad/adcs_integration_test.go b/cmd/api/src/analysis/ad/adcs_integration_test.go index 8c3f05bab0..259783e3d1 100644 --- a/cmd/api/src/analysis/ad/adcs_integration_test.go +++ b/cmd/api/src/analysis/ad/adcs_integration_test.go @@ -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) { @@ -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 }) diff --git a/cmd/api/src/database/migration/migrations/v5.5.0.sql b/cmd/api/src/database/migration/migrations/v5.5.0.sql index d42d8b3f7d..b3dbbf5b5e 100644 --- a/cmd/api/src/database/migration/migrations/v5.5.0.sql +++ b/cmd/api/src/database/migration/migrations/v5.5.0.sql @@ -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, diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index 543cfd52aa..01f46e365f 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -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 @@ -1541,6 +1614,7 @@ type HarnessDetails struct { EnrollOnBehalfOfHarnessTwo EnrollOnBehalfOfHarnessTwo ADCSGoldenCertHarness ADCSGoldenCertHarness IssuedSignedByHarness IssuedSignedByHarness + WeakCertBindingAndUPNCertMappingHarness WeakCertBindingAndUPNCertMappingHarness TrustedForNTAuthHarness TrustedForNTAuthHarness NumCollectedActiveDirectoryDomains int AZInboundControlHarness AZInboundControlHarness diff --git a/cmd/api/src/test/integration/harnesses/WeakCertBindingAndUPNCertMappingHarness.json b/cmd/api/src/test/integration/harnesses/WeakCertBindingAndUPNCertMappingHarness.json new file mode 100644 index 0000000000..cac37ebcf7 --- /dev/null +++ b/cmd/api/src/test/integration/harnesses/WeakCertBindingAndUPNCertMappingHarness.json @@ -0,0 +1,314 @@ +{ + "style": { + "font-family": "sans-serif", + "background-color": "#ffffff", + "background-image": "", + "background-size": "100%", + "node-color": "#ffffff", + "border-width": 4, + "border-color": "#000000", + "radius": 50, + "node-padding": 5, + "node-margin": 2, + "outside-position": "auto", + "node-icon-image": "", + "node-background-image": "", + "icon-position": "inside", + "icon-size": 64, + "caption-position": "inside", + "caption-max-width": 200, + "caption-color": "#000000", + "caption-font-size": 50, + "caption-font-weight": "normal", + "label-position": "inside", + "label-display": "pill", + "label-color": "#000000", + "label-background-color": "#ffffff", + "label-border-color": "#000000", + "label-border-width": 4, + "label-font-size": 40, + "label-padding": 5, + "label-margin": 4, + "directionality": "directed", + "detail-position": "inline", + "detail-orientation": "parallel", + "arrow-width": 5, + "arrow-color": "#000000", + "margin-start": 5, + "margin-end": 5, + "margin-peer": 20, + "attachment-start": "normal", + "attachment-end": "normal", + "relationship-icon-image": "", + "type-color": "#000000", + "type-background-color": "#ffffff", + "type-border-color": "#000000", + "type-border-width": 0, + "type-font-size": 16, + "type-padding": 5, + "property-position": "outside", + "property-alignment": "colon", + "property-color": "#000000", + "property-font-size": 16, + "property-font-weight": "normal" + }, + "nodes": [ + { + "id": "n0", + "position": { + "x": 190.72994242649474, + "y": -254 + }, + "caption": "Computer1", + "style": { + "node-color": "#d33115" + }, + "labels": [], + "properties": { + "certificatemappingmethodsraw": "4", + "strongcertificatebindingenforcementraw": "1" + } + }, + { + "id": "n1", + "position": { + "x": 190.72994242649474, + "y": -4.000000000000028 + }, + "caption": "Computer2", + "labels": [], + "properties": { + "certificatemappingmethodsraw": "11", + "strongcertificatebindingenforcementraw": "0" + }, + "style": { + "node-color": "#d33115" + } + }, + { + "id": "n2", + "position": { + "x": 190.72994242649474, + "y": 200.31730677356032 + }, + "caption": "Computer3", + "labels": [], + "properties": { + "certificatemappingmethodsraw": "31", + "strongcertificatebindingenforcementraw": "2" + }, + "style": { + "node-color": "#d33115" + } + }, + { + "id": "n3", + "position": { + "x": 588.032349294211, + "y": -254 + }, + "caption": "Domain1", + "style": { + "node-color": "#73d8ff" + }, + "labels": [], + "properties": { + "objectid": "S-1-5-21-2697957641-2271029196-387917394" + } + }, + { + "id": "n4", + "position": { + "x": 838.032349294211, + "y": -4.000000000000028 + }, + "caption": "Domain2", + "labels": [], + "properties": { + "objectid": "S-1-5-21-2697957641-2271029196-387917395" + }, + "style": { + "node-color": "#73d8ff" + } + }, + { + "id": "n6", + "position": { + "x": -242.28275946572413, + "y": -89.00000000000003 + }, + "caption": "EnterpriseCA1", + "style": { + "node-color": "#aea1ff" + }, + "labels": [], + "properties": { + "domainsid": "S-1-5-21-2697957641-2271029196-387917394" + } + }, + { + "id": "n7", + "position": { + "x": 190.72994242649474, + "y": 593.076549478154 + }, + "caption": "Computer5", + "labels": [], + "properties": { + "certificatemappingmethodsraw": "15", + "strongcertificatebindingenforcementraw": "2" + }, + "style": { + "node-color": "#d33115" + } + }, + { + "id": "n8", + "position": { + "x": 588.032349294211, + "y": 593.076549478154 + }, + "caption": "Domain3", + "labels": [], + "properties": { + "objectid": "S-1-5-21-2697957641-2271029196-387917396" + }, + "style": { + "node-color": "#73d8ff" + } + }, + { + "id": "n9", + "position": { + "x": -242.28275946572413, + "y": 593.076549478154 + }, + "caption": "EnterpriseCA2", + "labels": [], + "properties": { + "domainsid": "S-1-5-21-2697957641-2271029196-387917396" + }, + "style": { + "node-color": "#aea1ff" + } + }, + { + "id": "n10", + "position": { + "x": 190.72994242649474, + "y": 396.6969281258572 + }, + "caption": "Computer4", + "labels": [], + "properties": { + "certificatemappingmethodsraw": "null (not collected)", + "strongcertificatebindingenforcementraw": "null (not collected)" + }, + "style": { + "node-color": "#d33115" + } + } + ], + "relationships": [ + { + "id": "n0", + "type": "DCFor", + "style": {}, + "properties": {}, + "fromId": "n1", + "toId": "n4" + }, + { + "id": "n1", + "type": "DCFor", + "style": {}, + "properties": {}, + "fromId": "n2", + "toId": "n4" + }, + { + "id": "n2", + "type": "DCFor", + "style": {}, + "properties": {}, + "fromId": "n0", + "toId": "n3" + }, + { + "id": "n3", + "type": "TrustedBy", + "style": {}, + "properties": { + "trusttype": "ParentChild" + }, + "fromId": "n3", + "toId": "n4" + }, + { + "id": "n4", + "type": "CanAbuseUPNCertMapping", + "style": {}, + "properties": {}, + "fromId": "n6", + "toId": "n0" + }, + { + "id": "n5", + "type": "CanAbuseUPNCertMapping", + "style": {}, + "properties": {}, + "fromId": "n6", + "toId": "n2" + }, + { + "id": "n6", + "type": "TrustedBy", + "style": {}, + "properties": { + "trusttype": "External" + }, + "fromId": "n4", + "toId": "n8" + }, + { + "id": "n7", + "type": "DCFor", + "style": {}, + "properties": {}, + "fromId": "n7", + "toId": "n8" + }, + { + "id": "n8", + "type": "CanAbuseWeakCertBinding", + "style": {}, + "properties": {}, + "fromId": "n6", + "toId": "n0" + }, + { + "id": "n9", + "type": "CanAbuseWeakCertBinding", + "style": {}, + "properties": {}, + "fromId": "n6", + "toId": "n1" + }, + { + "id": "n10", + "type": "CanAbuseUPNCertMapping", + "style": {}, + "properties": {}, + "fromId": "n9", + "toId": "n7" + }, + { + "id": "n11", + "type": "DCFor", + "style": {}, + "properties": {}, + "fromId": "n10", + "toId": "n4" + } + ] +} \ No newline at end of file diff --git a/cmd/api/src/test/integration/harnesses/WeakCertBindingAndUPNCertMappingHarness.svg b/cmd/api/src/test/integration/harnesses/WeakCertBindingAndUPNCertMappingHarness.svg new file mode 100644 index 0000000000..ba4d4dfde0 --- /dev/null +++ b/cmd/api/src/test/integration/harnesses/WeakCertBindingAndUPNCertMappingHarness.svg @@ -0,0 +1,18 @@ + +DCForDCForDCForTrustedBytrusttype:ParentChildCanAbuseUPNCertMappingCanAbuseWeakCertBindingCanAbuseUPNCertMappingTrustedBytrusttype:ExternalDCForCanAbuseWeakCertBindingCanAbuseUPNCertMappingDCForComputer1certificatemappingmethodsraw:4strongcertificatebindingenforcementraw:1Computer2certificatemappingmethodsraw:11strongcertificatebindingenforcementraw:0Computer3certificatemappingmethodsraw:31strongcertificatebindingenforcementraw:2Domain1objectid:S-1-5-21-2697957641-2271029196-387917394Domain2objectid:S-1-5-21-2697957641-2271029196-387917395EnterpriseCA1domainsid:S-1-5-21-2697957641-2271029196-387917394Computer5certificatemappingmethodsraw:15strongcertificatebindingenforcementraw:2Domain3objectid:S-1-5-21-2697957641-2271029196-387917396EnterpriseCA2domainsid:S-1-5-21-2697957641-2271029196-387917396Computer4certificatemappingmethodsraw:null (not collected)strongcertificatebindingenforcementraw:null (not collected) diff --git a/cmd/api/src/test/integration/harnesses/issuedsignedbyharness.svg b/cmd/api/src/test/integration/harnesses/issuedsignedbyharness.svg index b76c46cd17..c423d6fca1 100644 --- a/cmd/api/src/test/integration/harnesses/issuedsignedbyharness.svg +++ b/cmd/api/src/test/integration/harnesses/issuedsignedbyharness.svg @@ -1 +1,18 @@ -IssuedSignedByIssuedSignedByIssuedSignedByEnterpriseCA2EnterpriseCA3EnterpriseCA1RootCA2RootCA1 \ No newline at end of file + +IssuedSignedByIssuedSignedByIssuedSignedByEnterpriseCA2EnterpriseCA3EnterpriseCA1RootCA2RootCA1 diff --git a/examples/helm/Chart.yaml b/examples/helm/Chart.yaml index e3b740e462..cefd43aad2 100644 --- a/examples/helm/Chart.yaml +++ b/examples/helm/Chart.yaml @@ -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 + apiVersion: v2 name: BloodhoundAD description: A Helm Chart for deploying BloodHoundAD on Kubernetes diff --git a/examples/helm/templates/deployappdb.yaml b/examples/helm/templates/deployappdb.yaml index b495736bbb..467fe4db78 100644 --- a/examples/helm/templates/deployappdb.yaml +++ b/examples/helm/templates/deployappdb.yaml @@ -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 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/examples/helm/templates/deploybh.yaml b/examples/helm/templates/deploybh.yaml index fdd31cfd65..434f7291b9 100644 --- a/examples/helm/templates/deploybh.yaml +++ b/examples/helm/templates/deploybh.yaml @@ -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 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/examples/helm/templates/deploygraphdb.yaml b/examples/helm/templates/deploygraphdb.yaml index 604a315a70..0beb7daab2 100644 --- a/examples/helm/templates/deploygraphdb.yaml +++ b/examples/helm/templates/deploygraphdb.yaml @@ -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 + apiVersion: apps/v1 kind: Deployment metadata: diff --git a/examples/helm/templates/ingressbh.yaml b/examples/helm/templates/ingressbh.yaml index 7c3ca8dba1..381e8e4224 100644 --- a/examples/helm/templates/ingressbh.yaml +++ b/examples/helm/templates/ingressbh.yaml @@ -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 + {{- if .Values.bloodhound.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/examples/helm/templates/nsbloodhound.yaml b/examples/helm/templates/nsbloodhound.yaml index ed7fed91a1..e2b8d34a33 100644 --- a/examples/helm/templates/nsbloodhound.yaml +++ b/examples/helm/templates/nsbloodhound.yaml @@ -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 + apiVersion: v1 kind: Namespace metadata: diff --git a/examples/helm/templates/pvcappdb.yaml b/examples/helm/templates/pvcappdb.yaml index fe67c9a75c..2ed7a4e43d 100644 --- a/examples/helm/templates/pvcappdb.yaml +++ b/examples/helm/templates/pvcappdb.yaml @@ -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 + {{- if .Values.appdb.volumes.pvcEnabled }} apiVersion: v1 kind: PersistentVolumeClaim diff --git a/examples/helm/templates/pvcgraphdb.yaml b/examples/helm/templates/pvcgraphdb.yaml index dc71fdbb3c..7daa00194d 100644 --- a/examples/helm/templates/pvcgraphdb.yaml +++ b/examples/helm/templates/pvcgraphdb.yaml @@ -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 + {{- if .Values.graphdb.volumes.pvcEnabled }} apiVersion: v1 kind: PersistentVolumeClaim diff --git a/examples/helm/templates/svcappdb.yaml b/examples/helm/templates/svcappdb.yaml index 5b1d916435..a2f6007a55 100644 --- a/examples/helm/templates/svcappdb.yaml +++ b/examples/helm/templates/svcappdb.yaml @@ -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 + {{- if .Values.appdb.service.enabled }} apiVersion: v1 kind: Service diff --git a/examples/helm/templates/svcbh.yaml b/examples/helm/templates/svcbh.yaml index 912633187e..b3b6e822ed 100644 --- a/examples/helm/templates/svcbh.yaml +++ b/examples/helm/templates/svcbh.yaml @@ -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 + {{- if .Values.bloodhound.service.enabled }} apiVersion: v1 kind: Service diff --git a/examples/helm/templates/svcgraphdb.yaml b/examples/helm/templates/svcgraphdb.yaml index 16e22d4189..8fc700d7bc 100644 --- a/examples/helm/templates/svcgraphdb.yaml +++ b/examples/helm/templates/svcgraphdb.yaml @@ -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 + {{- if .Values.graphdb.service.enabled }} apiVersion: v1 kind: Service diff --git a/examples/helm/values.yaml b/examples/helm/values.yaml index e2a1753b49..945ae73a53 100644 --- a/examples/helm/values.yaml +++ b/examples/helm/values.yaml @@ -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 + namespace: bloodhoundad appdb: diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 16e089807c..5d15104fa8 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -911,6 +911,11 @@ CanAbuseUPNCertMapping: types.#Kind & { schema: "active_directory" } +CanAbuseWeakCertBinding: types.#Kind & { + symbol: "CanAbuseWeakCertBinding" + schema: "active_directory" +} + IssuedSignedBy: types.#Kind & { symbol: "IssuedSignedBy" schema: "active_directory" @@ -1009,6 +1014,7 @@ RelationshipKinds: [ TrustedForNTAuth, EnterpriseCAFor, CanAbuseUPNCertMapping, + CanAbuseWeakCertBinding, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, diff --git a/packages/go/analysis/ad/adcs.go b/packages/go/analysis/ad/adcs.go index cbb0db87d4..346ab3b568 100644 --- a/packages/go/analysis/ad/adcs.go +++ b/packages/go/analysis/ad/adcs.go @@ -82,7 +82,8 @@ func PostADCSESC1(ctx context.Context, tx graph.Transaction, outC chan<- analysi } else { for _, certTemplate := range publishedCertTemplates.Slice() { if validationProperties, err := getValidatePublishedCertTemplateForEsc1PropertyValues(certTemplate); err != nil { - log.Errorf("error getting published certtemplate validation properties, %v", err) + + log.Errorf("error getting published cert template validation properties, %w", err) continue } else if !validatePublishedCertTemplateForEsc1(validationProperties) { continue @@ -225,7 +226,11 @@ func postADCSPreProcessStep1(ctx context.Context, db graph.Database, enterpriseC operation.Done() return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed post processing for %s: %w", ad.EnterpriseCAFor.String(), err) } else if err = PostCanAbuseUPNCertMapping(ctx, db, operation, enterpriseCertAuthorities); err != nil { + operation.Done() return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed post processing for %s: %w", ad.CanAbuseUPNCertMapping.String(), err) + } else if err = PostCanAbuseWeakCertBinding(ctx, db, operation, enterpriseCertAuthorities); err != nil { + operation.Done() + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed post processing for %s: %w", ad.CanAbuseWeakCertBinding.String(), err) } else { return &operation.Stats, operation.Done() } diff --git a/packages/go/analysis/ad/esc6.go b/packages/go/analysis/ad/esc6.go index de30512ef2..b6cd70b3de 100644 --- a/packages/go/analysis/ad/esc6.go +++ b/packages/go/analysis/ad/esc6.go @@ -24,33 +24,40 @@ import ( "github.com/specterops/bloodhound/dawgs/ops" "github.com/specterops/bloodhound/dawgs/query" "github.com/specterops/bloodhound/dawgs/util/channels" + "github.com/specterops/bloodhound/errors" "github.com/specterops/bloodhound/graphschema/ad" ) func PostCanAbuseUPNCertMapping(_ context.Context, _ graph.Database, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob], enterpriseCertAuthorities []*graph.Node) error { operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + collector := errors.ErrorCollector{} for _, eca := range enterpriseCertAuthorities { if ecaDomainSID, err := eca.Properties.Get(ad.DomainSID.String()).String(); err != nil { - return err + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to find domainsid for node ID %v: %v", eca.ID, err)) + continue } else if ecaDomain, err := analysis.FetchNodeByObjectID(tx, ecaDomainSID); err != nil { - return err + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to find node corresponding to domainsid %v: %v", ecaDomainSID, err)) + continue } else if trustedByNodes, err := fetchNodesWithTrustedByParentChildRelationship(tx, ecaDomain); err != nil { - return err + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to fetch TrustedBy nodes: %v", err)) + continue } else { for _, trustedByDomain := range trustedByNodes { if dcForNodes, err := fetchNodesWithDCForEdge(tx, trustedByDomain); err != nil { - return err + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to fetch DCFor nodes: %v", err)) + continue } else { for _, dcForNode := range dcForNodes { if cmmrProperty, err := dcForNode.Properties.Get(ad.CertificateMappingMethodsRaw.String()).Int(); err != nil { - return err + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to fetch %v property for node ID %v: %v", ad.StrongCertificateBindingEnforcementRaw.String(), dcForNode.ID, err)) + continue } else if cmmrProperty&0x04 == 0x04 { if !channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ FromID: eca.ID, ToID: dcForNode.ID, Kind: ad.CanAbuseUPNCertMapping, }) { - return fmt.Errorf("context timed out while creating CanAbuseUPNCert edge") + return fmt.Errorf("context timed out while creating CanAbuseUPNCertMapping edge") } } } @@ -58,7 +65,49 @@ func PostCanAbuseUPNCertMapping(_ context.Context, _ graph.Database, operation a } } } - return nil + return collector.Return() + }) + return nil +} + +func PostCanAbuseWeakCertBinding(_ context.Context, _ graph.Database, operation analysis.StatTrackedOperation[analysis.CreatePostRelationshipJob], enterpriseCertAuthorities []*graph.Node) error { + operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + collector := errors.ErrorCollector{} + for _, eca := range enterpriseCertAuthorities { + if ecaDomainSID, err := eca.Properties.Get(ad.DomainSID.String()).String(); err != nil { + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to find domainsid for node ID %v: %v", eca.ID, err)) + continue + } else if ecaDomain, err := analysis.FetchNodeByObjectID(tx, ecaDomainSID); err != nil { + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to find node corresponding to domainsid %v: %v", ecaDomainSID, err)) + continue + } else if trustedByNodes, err := fetchNodesWithTrustedByParentChildRelationship(tx, ecaDomain); err != nil { + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to fetch TrustedBy nodes: %v", err)) + continue + } else { + for _, trustedByDomain := range trustedByNodes { + if dcForNodes, err := fetchNodesWithDCForEdge(tx, trustedByDomain); err != nil { + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to fetch DCFor nodes: %v", err)) + continue + } else { + for _, dcForNode := range dcForNodes { + if strongCertBindingEnforcement, err := dcForNode.Properties.Get(ad.StrongCertificateBindingEnforcementRaw.String()).Int(); err != nil { + collector.Collect(fmt.Errorf("error in PostCanAbuseWeakCertBinding: unable to fetch %v property for node ID %v: %v", ad.StrongCertificateBindingEnforcementRaw.String(), dcForNode.ID, err)) + continue + } else if strongCertBindingEnforcement == 0 || strongCertBindingEnforcement == 1 { + if !channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{ + FromID: eca.ID, + ToID: dcForNode.ID, + Kind: ad.CanAbuseWeakCertBinding, + }) { + return fmt.Errorf("context timed out while creating CanAbuseWeakCertBinding edge") + } + } + } + } + } + } + } + return collector.Return() }) return nil } diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 8d21eae125..348916f363 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -91,6 +91,7 @@ var ( TrustedForNTAuth = graph.StringKind("TrustedForNTAuth") EnterpriseCAFor = graph.StringKind("EnterpriseCAFor") CanAbuseUPNCertMapping = graph.StringKind("CanAbuseUPNCertMapping") + CanAbuseWeakCertBinding = graph.StringKind("CanAbuseWeakCertBinding") IssuedSignedBy = graph.StringKind("IssuedSignedBy") GoldenCert = graph.StringKind("GoldenCert") EnrollOnBehalfOf = graph.StringKind("EnrollOnBehalfOf") @@ -602,7 +603,7 @@ func Nodes() []graph.Kind { return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate} } func Relationships() []graph.Kind { - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, DCFor, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, RootCAFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, CanAbuseUPNCertMapping, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6, ADCSESC7} + return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, DCFor, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, RootCAFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, CanAbuseUPNCertMapping, CanAbuseWeakCertBinding, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6, ADCSESC7} } func ACLRelationships() []graph.Kind { return []graph.Kind{AllExtendedRights, ForceChangePassword, AddMember, AddAllowedToAct, GenericAll, WriteDACL, WriteOwner, GenericWrite, ReadLAPSPassword, ReadGMSAPassword, Owns, AddSelf, WriteSPN, AddKeyCredentialLink, GetChanges, GetChangesAll, GetChangesInFilteredSet, WriteAccountRestrictions, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag} diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx index f5b9d0ac7a..73bc3bf8ab 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/LinuxAbuse.tsx @@ -18,7 +18,6 @@ import { FC } from 'react'; import { Link, Typography } from '@mui/material'; import { EdgeInfoProps } from '../index'; - const LinuxAbuse: FC = ({ sourceName, sourceType, @@ -207,7 +206,6 @@ const LinuxAbuse: FC = ( This ticket can then be used with Pass-the-Ticket, and could grant access to the file system of the TARGETCOMPUTER. - Shadow Credentials attack To abuse this permission, use{' '} diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/Opsec.tsx index 4c2d092446..864f4ea424 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/Opsec.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/Opsec.tsx @@ -20,8 +20,8 @@ import { Typography } from '@mui/material'; const Opsec: FC = () => { return ( - This depends on the target object and how to take advantage of this permission. Opsec considerations for each - abuse primitive are documented on the specific abuse edges and on the BloodHound wiki. + This depends on the target object and how to take advantage of this permission. Opsec considerations for + each abuse primitive are documented on the specific abuse edges and on the BloodHound wiki. ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx index e61ec0223b..7c3aa47598 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericAll/WindowsAbuse.tsx @@ -46,9 +46,9 @@ const WindowsAbuse: FC = - To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into your - agent session or into a PowerShell instance at the console. You may need to authenticate to the - Domain Controller as{' '} + To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into + your agent session or into a PowerShell instance at the console. You may need to authenticate to + the Domain Controller as{' '} {sourceType === 'User' ? `${sourceName} if you are not running a process as that user` : `a member of ${sourceName} if you are not running a process as a member`} @@ -439,8 +439,8 @@ const WindowsAbuse: FC = If you want to be more targeted with your approach, it is possible to specify precisely what right you want to apply to precisely which kinds of descendent objects. You could, for example, - grant a user "ForceChangePassword" permission against all user objects, or grant a security group - the ability to read every GMSA password under a certain OU. Below is an example taken from + grant a user "ForceChangePassword" permission against all user objects, or grant a security + group the ability to read every GMSA password under a certain OU. Below is an example taken from PowerView's help text on how to grant the "ITADMIN" user the ability to read the LAPS password from all computer objects in the "Workstations" OU: diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/Opsec.tsx index 4c2d092446..864f4ea424 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/Opsec.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/Opsec.tsx @@ -20,8 +20,8 @@ import { Typography } from '@mui/material'; const Opsec: FC = () => { return ( - This depends on the target object and how to take advantage of this permission. Opsec considerations for each - abuse primitive are documented on the specific abuse edges and on the BloodHound wiki. + This depends on the target object and how to take advantage of this permission. Opsec considerations for + each abuse primitive are documented on the specific abuse edges and on the BloodHound wiki. ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx index e0214c27fb..f6dfffb37c 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/GenericWrite/WindowsAbuse.tsx @@ -37,9 +37,9 @@ const WindowsAbuse: FC = ({ sourceName, sourceType, targetType }) opsec tab). - To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into your - agent session or into a PowerShell instance at the console. You may need to authenticate to the - Domain Controller as + To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into + your agent session or into a PowerShell instance at the console. You may need to authenticate to + the Domain Controller as {sourceType === 'User' ? `${sourceName} if you are not running a process as that user` : `a member of ${sourceName} if you are not running a process as a member`} diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/LinuxAbuse.tsx index 089b9646da..949daa4297 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/LinuxAbuse.tsx @@ -382,8 +382,8 @@ const LinuxAbuse: FC = ( The AllExtendedRights permission grants {sourceName} both the DS-Replication-Get-Changes and - DS-Replication-Get-Changes-All permissions, which combined allow a principal to replicate objects - from the domain {targetName}. + DS-Replication-Get-Changes-All permissions, which combined allow a principal to replicate + objects from the domain {targetName}. diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/Opsec.tsx index dc82bf4425..78d345d1e7 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/Opsec.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/Opsec.tsx @@ -31,9 +31,9 @@ const Opsec: FC = () => { handled the request. - Additional opsec considerations depend on the target object and how to take advantage of this permission. - Opsec considerations for each abuse primitive are documented on the specific abuse edges and on the - BloodHound wiki. + Additional opsec considerations depend on the target object and how to take advantage of this + permission. Opsec considerations for each abuse primitive are documented on the specific abuse edges and + on the BloodHound wiki. ); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/WindowsAbuse.tsx index c9e14659f6..0de9cde80b 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteDacl/WindowsAbuse.tsx @@ -67,9 +67,9 @@ const WindowsAbuse: FC = opsec tab). - To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into your - agent session or into a PowerShell instance at the console. You may need to authenticate to the - Domain Controller as{' '} + To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into + your agent session or into a PowerShell instance at the console. You may need to authenticate to + the Domain Controller as{' '} {sourceType === 'User' ? `${sourceName} if you are not running a process as that user` : `a member of ${sourceName} if you are not running a process as a member`} @@ -589,8 +589,8 @@ const WindowsAbuse: FC = If you want to be more targeted with your approach, it is possible to specify precisely what right you want to apply to precisely which kinds of descendent objects. You could, for example, - grant a user "ForceChangePassword" permission against all user objects, or grant a security group - the ability to read every GMSA password under a certain OU. Below is an example taken from + grant a user "ForceChangePassword" permission against all user objects, or grant a security + group the ability to read every GMSA password under a certain OU. Below is an example taken from PowerView's help text on how to grant the "ITADMIN" user the ability to read the LAPS password from all computer objects in the "Workstations" OU: diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/LinuxAbuse.tsx index 0a8ec2f4fe..3c0daa9103 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/LinuxAbuse.tsx @@ -422,8 +422,8 @@ const LinuxAbuse: FC = ({ The AllExtendedRights permission grants {sourceName} both the DS-Replication-Get-Changes and - DS-Replication-Get-Changes-All permissions, which combined allow a principal to replicate objects - from the domain {targetName}. + DS-Replication-Get-Changes-All permissions, which combined allow a principal to replicate + objects from the domain {targetName}. diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/Opsec.tsx index 13f18ccda4..423ecd1256 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/Opsec.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/Opsec.tsx @@ -35,9 +35,9 @@ const Opsec: FC = () => { handled the request. - Additional opsec considerations depend on the target object and how to take advantage of this permission. - Opsec considerations for each abuse primitive are documented on the specific abuse edges and on the - BloodHound wiki. + Additional opsec considerations depend on the target object and how to take advantage of this + permission. Opsec considerations for each abuse primitive are documented on the specific abuse edges and + on the BloodHound wiki. ); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/WindowsAbuse.tsx index 73a56e500e..e9210c3b35 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/WindowsAbuse.tsx @@ -55,8 +55,8 @@ const WindowsAbuse: FC = ({ } - To abuse ownership of a user object, you may grant yourself the AddMember permission. This can be - accomplished using the Add-DomainObjectAcl function in PowerView. + To abuse ownership of a user object, you may grant yourself the AddMember permission. This can + be accomplished using the Add-DomainObjectAcl function in PowerView. You may need to authenticate to the Domain Controller as{' '} @@ -91,9 +91,9 @@ const WindowsAbuse: FC = ({ opsec tab). - To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into your - agent session or into a PowerShell instance at the console. You may need to authenticate to the - Domain Controller as{' '} + To abuse this permission with PowerView's Add-DomainGroupMember, first import PowerView into + your agent session or into a PowerShell instance at the console. You may need to authenticate to + the Domain Controller as{' '} {sourceType === 'User' ? `${sourceName} if you are not running a process as that user` : `a member of ${sourceName} if you are not running a process as a member`} @@ -615,8 +615,8 @@ const WindowsAbuse: FC = ({ If you want to be more targeted with your approach, it is possible to specify precisely what right you want to apply to precisely which kinds of descendent objects. You could, for example, - grant a user "ForceChangePassword" permission against all user objects, or grant a security group - the ability to read every GMSA password under a certain OU. Below is an example taken from + grant a user "ForceChangePassword" permission against all user objects, or grant a security + group the ability to read every GMSA password under a certain OU. Below is an example taken from PowerView's help text on how to grant the "ITADMIN" user the ability to read the LAPS password from all computer objects in the "Workstations" OU: diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index f605484b87..bea8f3bf82 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -119,6 +119,7 @@ export enum ActiveDirectoryRelationshipKind { TrustedForNTAuth = 'TrustedForNTAuth', EnterpriseCAFor = 'EnterpriseCAFor', CanAbuseUPNCertMapping = 'CanAbuseUPNCertMapping', + CanAbuseWeakCertBinding = 'CanAbuseWeakCertBinding', IssuedSignedBy = 'IssuedSignedBy', GoldenCert = 'GoldenCert', EnrollOnBehalfOf = 'EnrollOnBehalfOf', @@ -233,6 +234,8 @@ export function ActiveDirectoryRelationshipKindToDisplay(value: ActiveDirectoryR return 'EnterpriseCAFor'; case ActiveDirectoryRelationshipKind.CanAbuseUPNCertMapping: return 'CanAbuseUPNCertMapping'; + case ActiveDirectoryRelationshipKind.CanAbuseWeakCertBinding: + return 'CanAbuseWeakCertBinding'; case ActiveDirectoryRelationshipKind.IssuedSignedBy: return 'IssuedSignedBy'; case ActiveDirectoryRelationshipKind.GoldenCert: