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 @@
+
+
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 @@
-
\ No newline at end of file
+
+
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: