From 7b64bfbccaf8f706324da3f45f1f88c60625c73c Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 29 Oct 2024 11:17:34 -0400 Subject: [PATCH 01/33] wip: initial start on owns edge changes --- packages/cue/bh/ad/ad.cue | 10 ++- packages/go/analysis/ad/owns.go | 72 +++++++++++++++++++ packages/go/ein/ad.go | 18 +++++ packages/go/ein/ad_test.go | 16 +++++ packages/go/graphschema/ad/ad.go | 12 +++- packages/go/graphschema/azure/azure.go | 1 - packages/go/graphschema/common/common.go | 1 - .../bh-shared-ui/src/graphSchema.ts | 3 + 8 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 packages/go/analysis/ad/owns.go diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 4ba6091a29..d9f7339024 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -735,6 +735,13 @@ MinPwdLength: types.#StringEnum & { representation: "minpwdlength" } +OwnerSid: types.#StringEnum & { + symbol: "OwnerSid" + schema: "ad" + name: "Owner SID" + representation: "ownersid" +} + Properties: [ AdminCount, CASecurityCollected, @@ -836,7 +843,8 @@ Properties: [ MinPwdAge, MaxPwdAge, LockoutDuration, - LockoutObservationWindow + LockoutObservationWindow, + OwnerSid ] // Kinds diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go new file mode 100644 index 0000000000..b8a9fd795a --- /dev/null +++ b/packages/go/analysis/ad/owns.go @@ -0,0 +1,72 @@ +// Copyright 2024 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 + +package ad + +import ( + "context" + "fmt" + "github.com/specterops/bloodhound/analysis" + "github.com/specterops/bloodhound/analysis/impact" + "github.com/specterops/bloodhound/dawgs/graph" + "github.com/specterops/bloodhound/dawgs/ops" + "github.com/specterops/bloodhound/dawgs/query" + "github.com/specterops/bloodhound/graphschema/ad" +) + +func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Aggregator) (*analysis.AtomicPostProcessingStats, error) { + if dsHeuristicsCache, err := GetDsHeuristicsCache(ctx, db); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values: %w", err) + } else { + operation := analysis.NewPostRelationshipOperation(ctx, db, "Owns Post Processing") + + } + return nil, nil +} + +func GetDsHeuristicsCache(ctx context.Context, db graph.Database) (map[string]bool, error) { + var ( + dsHeuristicValues = make(map[string]bool) + ) + return dsHeuristicValues, db.ReadTransaction(ctx, func(tx graph.Transaction) error { + if domains, err := ops.FetchNodes(tx.Nodes().Filter(query.Kind(query.Node(), ad.Domain))); err != nil { + return err + } else { + for _, domain := range domains { + if domainSid, err := domain.Properties.Get(ad.DomainSID.String()).String(); err != nil { + continue + } else if rawDsHeuristics, err := domain.Properties.Get(ad.DSHeuristics.String()).String(); err != nil { + continue + } else if len(rawDsHeuristics) < 29 { + continue + } else { + enforcedChar := string(rawDsHeuristics[28]) + switch enforcedChar { + case "0": + case "2": + dsHeuristicValues[domainSid] = false + case "1": + dsHeuristicValues[domainSid] = true + default: + continue + } + } + } + } + + return nil + }) +} diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 6bfd64480f..a0b3cfd708 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -45,6 +45,8 @@ func ConvertObjectToNode(item IngestBase, itemType graph.Kind) IngestibleNode { convertInvalidDomainProperties(itemProps) } + convertOwnsEdgeToProperty(item, itemProps) + return IngestibleNode{ ObjectID: item.ObjectIdentifier, PropertyMap: itemProps, @@ -52,6 +54,18 @@ func ConvertObjectToNode(item IngestBase, itemType graph.Kind) IngestibleNode { } } +// This function is to support our new method of doing Owns edges and makes older data sets backwards compatible +func convertOwnsEdgeToProperty(item IngestBase, itemProps map[string]any) { + for _, ace := range item.Aces { + if rightName, err := analysis.ParseKind(ace.RightName); err != nil { + continue + } else if rightName.Is(ad.Owns) { + itemProps[ad.OwnerSid.String()] = ace.PrincipalSID + return + } + } +} + func convertInvalidDomainProperties(itemProps map[string]any) { convertProperty(itemProps, "machineaccountquota", stringToInt) convertProperty(itemProps, "minpwdlength", stringToInt) @@ -199,6 +213,10 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } else if !ad.IsACLKind(rightKind) { log.Errorf("Non-ace edge type given to process aces: %s", ace.RightName) continue + } else if rightKind.Is(ad.Owns) { + // Owns aces are moving to post-processing. This indicates an older data set which we should ignore the owns edge from, as the ownersid + // property will be added during ingest of the node + continue } else { converted = append(converted, NewIngestibleRelationship( IngestibleSource{ diff --git a/packages/go/ein/ad_test.go b/packages/go/ein/ad_test.go index 88cb6d6654..81cb4f417c 100644 --- a/packages/go/ein/ad_test.go +++ b/packages/go/ein/ad_test.go @@ -1,3 +1,19 @@ +// Copyright 2024 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 + package ein_test import ( diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 68ff70567a..50a83651ce 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -21,8 +21,7 @@ package ad import ( "errors" - - "github.com/specterops/bloodhound/dawgs/graph" + graph "github.com/specterops/bloodhound/dawgs/graph" ) var ( @@ -217,10 +216,11 @@ const ( MaxPwdAge Property = "maxpwdage" LockoutDuration Property = "lockoutduration" LockoutObservationWindow Property = "lockoutobservationwindow" + OwnerSid Property = "ownersid" ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid} } func ParseProperty(source string) (Property, error) { switch source { @@ -426,6 +426,8 @@ func ParseProperty(source string) (Property, error) { return LockoutDuration, nil case "lockoutobservationwindow": return LockoutObservationWindow, nil + case "ownersid": + return OwnerSid, nil default: return "", errors.New("Invalid enumeration value: " + source) } @@ -634,6 +636,8 @@ func (s Property) String() string { return string(LockoutDuration) case LockoutObservationWindow: return string(LockoutObservationWindow) + case OwnerSid: + return string(OwnerSid) default: return "Invalid enumeration case: " + string(s) } @@ -842,6 +846,8 @@ func (s Property) Name() string { return "Lockout Duration" case LockoutObservationWindow: return "Lockout Observation Window" + case OwnerSid: + return "Owner SID" default: return "Invalid enumeration case: " + string(s) } diff --git a/packages/go/graphschema/azure/azure.go b/packages/go/graphschema/azure/azure.go index 787ee392e6..00b20f190f 100644 --- a/packages/go/graphschema/azure/azure.go +++ b/packages/go/graphschema/azure/azure.go @@ -21,7 +21,6 @@ package azure import ( "errors" - graph "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/go/graphschema/common/common.go b/packages/go/graphschema/common/common.go index 9320bb8d29..6fd161585e 100644 --- a/packages/go/graphschema/common/common.go +++ b/packages/go/graphschema/common/common.go @@ -21,7 +21,6 @@ package common import ( "errors" - graph "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index ec571e5225..541e7a642f 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -401,6 +401,7 @@ export enum ActiveDirectoryKindProperties { MaxPwdAge = 'maxpwdage', LockoutDuration = 'lockoutduration', LockoutObservationWindow = 'lockoutobservationwindow', + OwnerSid = 'ownersid', } export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKindProperties): string | undefined { switch (value) { @@ -606,6 +607,8 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Lockout Duration'; case ActiveDirectoryKindProperties.LockoutObservationWindow: return 'Lockout Observation Window'; + case ActiveDirectoryKindProperties.OwnerSid: + return 'Owner SID'; default: return undefined; } From c4831872ecf903c268c2760697cea4a662112c6d Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Thu, 31 Oct 2024 11:52:41 -0400 Subject: [PATCH 02/33] wip: write post operations for writeowner/owns, update ingest --- packages/cue/bh/ad/ad.cue | 66 ++++++- packages/go/analysis/ad/owns.go | 153 ++++++++++++++++- packages/go/analysis/post.go | 7 +- packages/go/analysis/post_operation.go | 14 +- packages/go/ein/ad.go | 162 ++++++++++++++---- packages/go/ein/incoming_models.go | 10 ++ packages/go/graphschema/ad/ad.go | 40 ++++- .../bh-shared-ui/src/graphSchema.ts | 26 +++ 8 files changed, 427 insertions(+), 51 deletions(-) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index d9f7339024..c4897d58f9 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -189,6 +189,20 @@ IsACL: types.#StringEnum & { representation: "isacl" } +LimitedRightsCreated: types.#StringEnum & { + symbol: "LimitedRightsCreated" + schema: "ad" + name: "Limited Rights Created" + representation: "limitedrightscreated" +} + +IsInherited: types.#StringEnum & { + symbol: "IsInherited" + schema: "ad" + name: "Is Inherited" + representation: "isinherited" +} + IsACLProtected: types.#StringEnum & { symbol: "IsACLProtected" schema: "ad" @@ -742,6 +756,20 @@ OwnerSid: types.#StringEnum & { representation: "ownersid" } +GMSA: types.#StringEnum & { + symbol: "GMSA" + schema: "ad" + name: "GMSA" + representation: "gmsa" +} + +MSA: types.#StringEnum & { + symbol: "MSA" + schema: "ad" + name: "MSA" + representation: "msa" +} + Properties: [ AdminCount, CASecurityCollected, @@ -768,6 +796,8 @@ Properties: [ HighValue, BlocksInheritance, IsACL, + LimitedRightsCreated, + IsInherited, IsACLProtected, IsDeleted, Enforced, @@ -844,7 +874,9 @@ Properties: [ MaxPwdAge, LockoutDuration, LockoutObservationWindow, - OwnerSid + OwnerSid, + GMSA, + MSA ] // Kinds @@ -1301,6 +1333,26 @@ SyncedToEntraUser: types.#Kind & { schema: "active_directory" } +WriteOwnerLimitedRights: types.#Kind & { + symbol: "WriteOwnerLimitedRights" + schema: "active_directory" +} + +WriteOwnerRaw: types.#Kind & { + symbol: "WriteOwnerRaw" + schema: "active_directory" +} + +OwnsLimitedRights: types.#Kind & { + symbol: "OwnsLimitedRights" + schema: "active_directory" +} + +OwnsRaw: types.#Kind & { + symbol: "OwnsRaw" + schema: "active_directory" +} + // Relationship Kinds RelationshipKinds: [ Owns, @@ -1372,6 +1424,10 @@ RelationshipKinds: [ ADCSESC10b, ADCSESC13, SyncedToEntraUser, + WriteOwnerLimitedRights, + WriteOwnerRaw, + OwnsLimitedRights, + OwnsRaw ] // ACL Relationships @@ -1401,7 +1457,11 @@ ACLRelationships: [ ManageCA, Enroll, WritePKIEnrollmentFlag, - WritePKINameFlag + WritePKINameFlag, + WriteOwnerLimitedRights, + WriteOwnerRaw, + OwnsLimitedRights, + OwnsRaw ] // Edges that are used in pathfinding @@ -1453,6 +1513,8 @@ PathfindingRelationships: [ ADCSESC13, DCFor, SyncedToEntraUser, + WriteOwnerLimitedRights, + OwnsLimitedRights ] EdgeCompositionRelationships: [ diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index b8a9fd795a..add523324a 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -21,27 +21,167 @@ import ( "fmt" "github.com/specterops/bloodhound/analysis" "github.com/specterops/bloodhound/analysis/impact" + "github.com/specterops/bloodhound/dawgs/cardinality" "github.com/specterops/bloodhound/dawgs/graph" "github.com/specterops/bloodhound/dawgs/ops" "github.com/specterops/bloodhound/dawgs/query" "github.com/specterops/bloodhound/graphschema/ad" + "github.com/specterops/bloodhound/graphschema/common" ) -func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Aggregator) (*analysis.AtomicPostProcessingStats, error) { - if dsHeuristicsCache, err := GetDsHeuristicsCache(ctx, db); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values: %w", err) +func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { + if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postowner: %w", err) + } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postowner: %w", err) } else { - operation := analysis.NewPostRelationshipOperation(ctx, db, "Owns Post Processing") + operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnerWriteOwner") + operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + return tx.Relationships().Filter( + query.And( + query.Kind(query.Relationship(), ad.OwnsRaw), + query.Kind(query.Start(), ad.Entity), + query.Equals(query.RelationshipProperty(ad.LimitedRightsCreated.String()), false), + ), + ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { + for rel := range cursor.Chan() { + if anyEnforced { + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue + } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false + } + + if !enforced { + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + } + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + continue + } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + } + } + } + } else { + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + } + } + } + + return nil + }) + }) + + operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + return tx.Relationships().Filter( + query.And( + query.Kind(query.Relationship(), ad.WriteOwnerRaw), + query.Kind(query.Start(), ad.Entity), + query.Equals(query.RelationshipProperty(ad.LimitedRightsCreated.String()), false), + ), + ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { + for rel := range cursor.Chan() { + if anyEnforced { + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue + } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false + } + + if !enforced { + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + } + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + continue + } else if !isComputerDerived { + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + } + } + } + } else { + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + } + } + } + + return nil + }) + }) } return nil, nil } -func GetDsHeuristicsCache(ctx context.Context, db graph.Database) (map[string]bool, error) { +func isTargetNodeComputerDerived(node *graph.Node) (bool, error) { + if node.Kinds.ContainsOneOf(ad.Computer) { + return true, nil + } else if isGmsa, err := node.Properties.Get(ad.GMSA.String()).Bool(); err != nil { + return false, err + } else if isGmsa { + return true, nil + } else { + return node.Properties.Get(ad.MSA.String()).Bool() + } +} + +func FetchAdminGroupIds(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (cardinality.Duplex[uint64], error) { + adminIds := cardinality.NewBitmap64() + + return adminIds, db.ReadTransaction(ctx, func(tx graph.Transaction) error { + return tx.Nodes().Filter( + query.Or( + query.StringEndsWith(query.NodeProperty(common.ObjectID.String()), DomainAdminsGroupSIDSuffix), + query.StringEndsWith(query.NodeProperty(common.ObjectID.String()), EnterpriseAdminsGroupSIDSuffix), + ), + ).FetchIDs(func(cursor graph.Cursor[graph.ID]) error { + for id := range cursor.Chan() { + adminIds.Add(id.Uint64()) + adminIds.Or(groupExpansions.Cardinality(id.Uint64())) + } + + return nil + }) + }) +} + +func GetDsHeuristicsCache(ctx context.Context, db graph.Database) (map[string]bool, bool, error) { var ( dsHeuristicValues = make(map[string]bool) + anyEnforced bool ) - return dsHeuristicValues, db.ReadTransaction(ctx, func(tx graph.Transaction) error { + return dsHeuristicValues, anyEnforced, db.ReadTransaction(ctx, func(tx graph.Transaction) error { if domains, err := ops.FetchNodes(tx.Nodes().Filter(query.Kind(query.Node(), ad.Domain))); err != nil { return err } else { @@ -60,6 +200,7 @@ func GetDsHeuristicsCache(ctx context.Context, db graph.Database) (map[string]bo dsHeuristicValues[domainSid] = false case "1": dsHeuristicValues[domainSid] = true + anyEnforced = true default: continue } diff --git a/packages/go/analysis/post.go b/packages/go/analysis/post.go index 16f0a8de79..da6e8668bd 100644 --- a/packages/go/analysis/post.go +++ b/packages/go/analysis/post.go @@ -110,9 +110,10 @@ func (s PostProcessingStats) LogStats() { } type CreatePostRelationshipJob struct { - FromID graph.ID - ToID graph.ID - Kind graph.Kind + FromID graph.ID + ToID graph.ID + Kind graph.Kind + RelProperties map[string]any } type DeleteRelationshipJob struct { diff --git a/packages/go/analysis/post_operation.go b/packages/go/analysis/post_operation.go index c5850c80e4..1eb9008d38 100644 --- a/packages/go/analysis/post_operation.go +++ b/packages/go/analysis/post_operation.go @@ -44,8 +44,18 @@ func NewPostRelationshipOperation(ctx context.Context, db graph.Database, operat ) for nextJob := range inC { - if err := batch.CreateRelationshipByIDs(nextJob.FromID, nextJob.ToID, nextJob.Kind, relProp); err != nil { - return err + if len(nextJob.RelProperties) > 0 { + tempRelProp := NewPropertiesWithLastSeen() + for key, val := range nextJob.RelProperties { + tempRelProp.Set(key, val) + } + if err := batch.CreateRelationshipByIDs(nextJob.FromID, nextJob.ToID, nextJob.Kind, tempRelProp); err != nil { + return err + } + } else { + if err := batch.CreateRelationshipByIDs(nextJob.FromID, nextJob.ToID, nextJob.Kind, relProp); err != nil { + return err + } } operation.Stats.AddRelationshipsCreated(nextJob.Kind, 1) diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index a0b3cfd708..de9c199423 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -59,7 +59,7 @@ func convertOwnsEdgeToProperty(item IngestBase, itemProps map[string]any) { for _, ace := range item.Aces { if rightName, err := analysis.ParseKind(ace.RightName); err != nil { continue - } else if rightName.Is(ad.Owns) { + } else if rightName.Is(ad.Owns) || rightName.Is(ad.OwnsRaw) { itemProps[ad.OwnerSid.String()] = ace.PrincipalSID return } @@ -128,7 +128,7 @@ func ParseObjectContainer(item IngestBase, itemType graph.Kind) IngestibleRelati TargetType: itemType, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.Contains, }, ) @@ -150,7 +150,7 @@ func ParsePrimaryGroup(item IngestBase, itemType graph.Kind, primaryGroupSid str TargetType: ad.Group, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false, "isprimarygroup": true}, + RelProps: map[string]any{ad.IsACL.String(): false, "isprimarygroup": true}, RelType: ad.MemberOf, }, ) @@ -174,7 +174,7 @@ func ParseGroupMembershipData(group Group) ParsedGroupMembershipData { TargetType: ad.Group, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false, "isprimarygroup": false}, + RelProps: map[string]any{ad.IsACL.String(): false, "isprimarygroup": false}, RelType: ad.MemberOf, }, )) @@ -189,7 +189,7 @@ func ParseGroupMembershipData(group Group) ParsedGroupMembershipData { TargetType: ad.Group, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false, "isprimarygroup": false}, + RelProps: map[string]any{ad.IsACL.String(): false, "isprimarygroup": false}, RelType: ad.MemberOf, }, )) @@ -199,8 +199,18 @@ func ParseGroupMembershipData(group Group) ParsedGroupMembershipData { return result } +type WriteOwnerLimitedCache struct { + SourceData IngestibleSource + IsInherited bool +} + func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []IngestibleRelationship { - converted := make([]IngestibleRelationship, 0) + var ( + converted = make([]IngestibleRelationship, 0) + writeOwnerLimitedPrincipals = make([]WriteOwnerLimitedCache, 0) + ownerLimitedPrivs = make([]string, 0) + ownerPrincipalInfo IngestibleSource + ) for _, ace := range aces { if ace.PrincipalSID == targetID { @@ -213,10 +223,12 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } else if !ad.IsACLKind(rightKind) { log.Errorf("Non-ace edge type given to process aces: %s", ace.RightName) continue - } else if rightKind.Is(ad.Owns) { - // Owns aces are moving to post-processing. This indicates an older data set which we should ignore the owns edge from, as the ownersid - // property will be added during ingest of the node - continue + } else if rightKind.Is(ad.Owns) || rightKind.Is(ad.OwnsRaw) { + ownerPrincipalInfo = ace.GetCachedValue().SourceData + } else if strings.HasSuffix(ace.PrincipalSID, "S-1-3-4") { + ownerLimitedPrivs = append(ownerLimitedPrivs, rightKind.String()) + } else if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { + writeOwnerLimitedPrincipals = append(writeOwnerLimitedPrincipals, ace.GetCachedValue()) } else { converted = append(converted, NewIngestibleRelationship( IngestibleSource{ @@ -228,13 +240,95 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{"isacl": true, "isinherited": ace.IsInherited}, + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): ace.IsInherited}, RelType: rightKind, }, )) } } + if len(ownerLimitedPrivs) > 0 { + for _, limitedPrincipal := range writeOwnerLimitedPrincipals { + converted = append(converted, NewIngestibleRelationship( + limitedPrincipal.SourceData, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): true}, + RelType: ad.WriteOwnerRaw, + }, + )) + + converted = append(converted, NewIngestibleRelationship( + limitedPrincipal.SourceData, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": ownerLimitedPrivs}, + RelType: ad.WriteOwnerLimitedRights, + }, + )) + } + + if ownerPrincipalInfo.Source != "" { + converted = append(converted, NewIngestibleRelationship( + ownerPrincipalInfo, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): true}, + RelType: ad.OwnsRaw, + }, + )) + + converted = append(converted, NewIngestibleRelationship( + ownerPrincipalInfo, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, "privileges": ownerLimitedPrivs}, + RelType: ad.OwnsLimitedRights, + }, + )) + } + } else { + if ownerPrincipalInfo.Source != "" { + converted = append(converted, NewIngestibleRelationship( + ownerPrincipalInfo, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): false}, + RelType: ad.OwnsRaw, + }, + )) + } + + for _, limitedPrincipal := range writeOwnerLimitedPrincipals { + converted = append(converted, NewIngestibleRelationship( + limitedPrincipal.SourceData, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): false}, + RelType: ad.WriteOwnerRaw, + }, + )) + } + } + return converted } @@ -255,7 +349,7 @@ func convertSPNData(spns []SPNTarget, sourceID string) []IngestibleRelationship TargetType: ad.Computer, }, IngestibleRel{ - RelProps: map[string]any{"isacl": true, "port": s.Port}, + RelProps: map[string]any{ad.IsACL.String(): true, "port": s.Port}, RelType: kind, }, )) @@ -284,7 +378,7 @@ func ParseUserMiscData(user User) []IngestibleRelationship { TargetType: target.Kind(), }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.AllowedToDelegate, }, )) @@ -301,7 +395,7 @@ func ParseUserMiscData(user User) []IngestibleRelationship { TargetType: target.Kind(), }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.HasSIDHistory, }, )) @@ -323,7 +417,7 @@ func ParseChildObjects(data []TypedPrincipal, containerId string, containerType TargetType: childObject.Kind(), }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.Contains, }, )) @@ -344,7 +438,7 @@ func ParseGpLinks(links []GPLink, itemIdentifier string, itemType graph.Kind) [] TargetType: itemType, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false, "enforced": gpLink.IsEnforced}, + RelProps: map[string]any{ad.IsACL.String(): false, "enforced": gpLink.IsEnforced}, RelType: ad.GPLink, }, )) @@ -391,7 +485,7 @@ func ParseDomainTrusts(domain Domain) ParsedDomainTrustData { }, IngestibleRel{ RelProps: map[string]any{ - "isacl": false, + ad.IsACL.String(): false, "sidfiltering": trust.SidFilteringEnabled, "tgtdelegationenabled": trust.TGTDelegationEnabled, "trustattributes": finalTrustAttributes, @@ -414,7 +508,7 @@ func ParseDomainTrusts(domain Domain) ParsedDomainTrustData { }, IngestibleRel{ RelProps: map[string]any{ - "isacl": false, + ad.IsACL.String(): false, "sidfiltering": trust.SidFilteringEnabled, "tgtdelegationenabled": trust.TGTDelegationEnabled, "trustattributes": finalTrustAttributes, @@ -443,7 +537,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: target.Kind(), }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.AllowedToDelegate, }, )) @@ -460,7 +554,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: ad.Computer, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.AllowedToAct, }, )) @@ -477,7 +571,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: target.Kind(), }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.DumpSMSAPassword, }, )) @@ -494,7 +588,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: target.Kind(), }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.HasSIDHistory, }, )) @@ -512,7 +606,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: ad.User, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.HasSession, }, )) @@ -531,7 +625,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: ad.User, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.HasSession, }, )) @@ -550,7 +644,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: ad.User, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.HasSession, }, )) @@ -568,7 +662,7 @@ func ParseComputerMiscData(computer Computer) []IngestibleRelationship { TargetType: ad.Domain, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.DCFor, }, )) @@ -599,7 +693,7 @@ func ConvertLocalGroup(localGroup LocalGroupAPIResult, computer Computer) Parsed TargetType: ad.Computer, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.LocalToComputer, }, )) @@ -615,7 +709,7 @@ func ConvertLocalGroup(localGroup LocalGroupAPIResult, computer Computer) Parsed TargetType: ad.LocalGroup, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.MemberOfLocalGroup, }, )) @@ -648,7 +742,7 @@ func ParseUserRightData(userRight UserRightsAssignmentAPIResult, computer Comput TargetType: ad.Computer, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: right, }, )) @@ -705,7 +799,7 @@ func ParseEnterpriseCAMiscData(enterpriseCA EnterpriseCA) []IngestibleRelationsh TargetType: ad.EnterpriseCA, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.PublishedTo, }, )) @@ -722,7 +816,7 @@ func ParseEnterpriseCAMiscData(enterpriseCA EnterpriseCA) []IngestibleRelationsh TargetType: ad.EnterpriseCA, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.HostsCAService, }, )) @@ -757,7 +851,7 @@ func handleEnterpriseCAEnrollmentAgentRestrictions(enterpriseCA EnterpriseCA, re TargetType: ad.CertTemplate, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.DelegatedEnrollmentAgent, }, )) @@ -822,7 +916,7 @@ func ParseRootCAMiscData(rootCA RootCA) []IngestibleRelationship { TargetType: ad.Domain, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.RootCAFor, }, )) @@ -848,7 +942,7 @@ func ParseNTAuthStoreData(ntAuthStore NTAuthStore) []IngestibleRelationship { TargetType: ad.Domain, }, IngestibleRel{ - RelProps: map[string]any{"isacl": false}, + RelProps: map[string]any{ad.IsACL.String(): false}, RelType: ad.NTAuthStoreFor, }, )) diff --git a/packages/go/ein/incoming_models.go b/packages/go/ein/incoming_models.go index 4d5fd580c9..295f215a77 100644 --- a/packages/go/ein/incoming_models.go +++ b/packages/go/ein/incoming_models.go @@ -68,6 +68,16 @@ func (s ACE) Kind() graph.Kind { return parseADKind(s.PrincipalType) } +func (s ACE) GetCachedValue() WriteOwnerLimitedCache { + return WriteOwnerLimitedCache{ + SourceData: IngestibleSource{ + Source: s.PrincipalSID, + SourceType: s.Kind(), + }, + IsInherited: s.IsInherited, + } +} + type IngestBase struct { ObjectIdentifier string Properties map[string]any diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 50a83651ce..5ed3394840 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -110,6 +110,10 @@ var ( ADCSESC10b = graph.StringKind("ADCSESC10b") ADCSESC13 = graph.StringKind("ADCSESC13") SyncedToEntraUser = graph.StringKind("SyncedToEntraUser") + WriteOwnerLimitedRights = graph.StringKind("WriteOwnerLimitedRights") + WriteOwnerRaw = graph.StringKind("WriteOwnerRaw") + OwnsLimitedRights = graph.StringKind("OwnsLimitedRights") + OwnsRaw = graph.StringKind("OwnsRaw") ) type Property string @@ -140,6 +144,8 @@ const ( HighValue Property = "highvalue" BlocksInheritance Property = "blocksinheritance" IsACL Property = "isacl" + LimitedRightsCreated Property = "limitedrightscreated" + IsInherited Property = "isinherited" IsACLProtected Property = "isaclprotected" IsDeleted Property = "isdeleted" Enforced Property = "enforced" @@ -217,10 +223,12 @@ const ( LockoutDuration Property = "lockoutduration" LockoutObservationWindow Property = "lockoutobservationwindow" OwnerSid Property = "ownersid" + GMSA Property = "gmsa" + MSA Property = "msa" ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, LimitedRightsCreated, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA} } func ParseProperty(source string) (Property, error) { switch source { @@ -274,6 +282,10 @@ func ParseProperty(source string) (Property, error) { return BlocksInheritance, nil case "isacl": return IsACL, nil + case "limitedrightscreated": + return LimitedRightsCreated, nil + case "isinherited": + return IsInherited, nil case "isaclprotected": return IsACLProtected, nil case "isdeleted": @@ -428,6 +440,10 @@ func ParseProperty(source string) (Property, error) { return LockoutObservationWindow, nil case "ownersid": return OwnerSid, nil + case "gmsa": + return GMSA, nil + case "msa": + return MSA, nil default: return "", errors.New("Invalid enumeration value: " + source) } @@ -484,6 +500,10 @@ func (s Property) String() string { return string(BlocksInheritance) case IsACL: return string(IsACL) + case LimitedRightsCreated: + return string(LimitedRightsCreated) + case IsInherited: + return string(IsInherited) case IsACLProtected: return string(IsACLProtected) case IsDeleted: @@ -638,6 +658,10 @@ func (s Property) String() string { return string(LockoutObservationWindow) case OwnerSid: return string(OwnerSid) + case GMSA: + return string(GMSA) + case MSA: + return string(MSA) default: return "Invalid enumeration case: " + string(s) } @@ -694,6 +718,10 @@ func (s Property) Name() string { return "Blocks GPO Inheritance" case IsACL: return "Is ACL" + case LimitedRightsCreated: + return "Limited Rights Created" + case IsInherited: + return "Is Inherited" case IsACLProtected: return "ACL Inheritance Denied" case IsDeleted: @@ -848,6 +876,10 @@ func (s Property) Name() string { return "Lockout Observation Window" case OwnerSid: return "Owner SID" + case GMSA: + return "GMSA" + case MSA: + return "MSA" default: return "Invalid enumeration case: " + string(s) } @@ -864,13 +896,13 @@ func Nodes() []graph.Kind { return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy} } 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, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser} + 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, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } 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, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag} + return []graph.Kind{AllExtendedRights, ForceChangePassword, AddMember, AddAllowedToAct, GenericAll, WriteDACL, WriteOwner, GenericWrite, ReadLAPSPassword, ReadGMSAPassword, Owns, AddSelf, WriteSPN, AddKeyCredentialLink, GetChanges, GetChangesAll, GetChangesInFilteredSet, WriteAccountRestrictions, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } func PathfindingRelationships() []graph.Kind { - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser} + return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser, WriteOwnerLimitedRights, OwnsLimitedRights} } func IsACLKind(s graph.Kind) bool { for _, acl := range ACLRelationships() { diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index 541e7a642f..b8c401765b 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -140,6 +140,10 @@ export enum ActiveDirectoryRelationshipKind { ADCSESC10b = 'ADCSESC10b', ADCSESC13 = 'ADCSESC13', SyncedToEntraUser = 'SyncedToEntraUser', + WriteOwnerLimitedRights = 'WriteOwnerLimitedRights', + WriteOwnerRaw = 'WriteOwnerRaw', + OwnsLimitedRights = 'OwnsLimitedRights', + OwnsRaw = 'OwnsRaw', } export function ActiveDirectoryRelationshipKindToDisplay(value: ActiveDirectoryRelationshipKind): string | undefined { switch (value) { @@ -281,6 +285,14 @@ export function ActiveDirectoryRelationshipKindToDisplay(value: ActiveDirectoryR return 'ADCSESC13'; case ActiveDirectoryRelationshipKind.SyncedToEntraUser: return 'SyncedToEntraUser'; + case ActiveDirectoryRelationshipKind.WriteOwnerLimitedRights: + return 'WriteOwnerLimitedRights'; + case ActiveDirectoryRelationshipKind.WriteOwnerRaw: + return 'WriteOwnerRaw'; + case ActiveDirectoryRelationshipKind.OwnsLimitedRights: + return 'OwnsLimitedRights'; + case ActiveDirectoryRelationshipKind.OwnsRaw: + return 'OwnsRaw'; default: return undefined; } @@ -325,6 +337,8 @@ export enum ActiveDirectoryKindProperties { HighValue = 'highvalue', BlocksInheritance = 'blocksinheritance', IsACL = 'isacl', + LimitedRightsCreated = 'limitedrightscreated', + IsInherited = 'isinherited', IsACLProtected = 'isaclprotected', IsDeleted = 'isdeleted', Enforced = 'enforced', @@ -402,6 +416,8 @@ export enum ActiveDirectoryKindProperties { LockoutDuration = 'lockoutduration', LockoutObservationWindow = 'lockoutobservationwindow', OwnerSid = 'ownersid', + GMSA = 'gmsa', + MSA = 'msa', } export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKindProperties): string | undefined { switch (value) { @@ -455,6 +471,10 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Blocks GPO Inheritance'; case ActiveDirectoryKindProperties.IsACL: return 'Is ACL'; + case ActiveDirectoryKindProperties.LimitedRightsCreated: + return 'Limited Rights Created'; + case ActiveDirectoryKindProperties.IsInherited: + return 'Is Inherited'; case ActiveDirectoryKindProperties.IsACLProtected: return 'ACL Inheritance Denied'; case ActiveDirectoryKindProperties.IsDeleted: @@ -609,6 +629,10 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Lockout Observation Window'; case ActiveDirectoryKindProperties.OwnerSid: return 'Owner SID'; + case ActiveDirectoryKindProperties.GMSA: + return 'GMSA'; + case ActiveDirectoryKindProperties.MSA: + return 'MSA'; default: return undefined; } @@ -662,6 +686,8 @@ export function ActiveDirectoryPathfindingEdges(): ActiveDirectoryRelationshipKi ActiveDirectoryRelationshipKind.ADCSESC13, ActiveDirectoryRelationshipKind.DCFor, ActiveDirectoryRelationshipKind.SyncedToEntraUser, + ActiveDirectoryRelationshipKind.WriteOwnerLimitedRights, + ActiveDirectoryRelationshipKind.OwnsLimitedRights, ]; } export enum AzureNodeKind { From 7e39e0834346f7abb585595855e8be18a7932d39 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Thu, 31 Oct 2024 11:53:59 -0400 Subject: [PATCH 03/33] wip: fix return type --- packages/go/analysis/ad/owns.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index add523324a..4985f7b145 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -140,8 +140,8 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa }) }) + return &operation.Stats, operation.Done() } - return nil, nil } func isTargetNodeComputerDerived(node *graph.Node) (bool, error) { From 403d3c2ebccc8deadcfe764c82ea807fea505464 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Thu, 31 Oct 2024 15:27:27 -0400 Subject: [PATCH 04/33] wip: remove unnecessary bool --- packages/cue/bh/ad/ad.cue | 8 ---- packages/go/analysis/ad/owns.go | 2 - packages/go/ein/ad.go | 43 +++++-------------- packages/go/graphschema/ad/ad.go | 9 +--- .../bh-shared-ui/src/graphSchema.ts | 3 -- 5 files changed, 11 insertions(+), 54 deletions(-) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index c4897d58f9..4d752a162d 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -189,13 +189,6 @@ IsACL: types.#StringEnum & { representation: "isacl" } -LimitedRightsCreated: types.#StringEnum & { - symbol: "LimitedRightsCreated" - schema: "ad" - name: "Limited Rights Created" - representation: "limitedrightscreated" -} - IsInherited: types.#StringEnum & { symbol: "IsInherited" schema: "ad" @@ -796,7 +789,6 @@ Properties: [ HighValue, BlocksInheritance, IsACL, - LimitedRightsCreated, IsInherited, IsACLProtected, IsDeleted, diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 4985f7b145..7cc6d50826 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -41,7 +41,6 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa query.And( query.Kind(query.Relationship(), ad.OwnsRaw), query.Kind(query.Start(), ad.Entity), - query.Equals(query.RelationshipProperty(ad.LimitedRightsCreated.String()), false), ), ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { @@ -93,7 +92,6 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa query.And( query.Kind(query.Relationship(), ad.WriteOwnerRaw), query.Kind(query.Start(), ad.Entity), - query.Equals(query.RelationshipProperty(ad.LimitedRightsCreated.String()), false), ), ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index de9c199423..7cfd39358e 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -206,10 +206,10 @@ type WriteOwnerLimitedCache struct { func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []IngestibleRelationship { var ( - converted = make([]IngestibleRelationship, 0) - writeOwnerLimitedPrincipals = make([]WriteOwnerLimitedCache, 0) - ownerLimitedPrivs = make([]string, 0) - ownerPrincipalInfo IngestibleSource + converted = make([]IngestibleRelationship, 0) + potentialWriteOwnerLimitedPrincipals = make([]WriteOwnerLimitedCache, 0) + ownerLimitedPrivs = make([]string, 0) + ownerPrincipalInfo IngestibleSource ) for _, ace := range aces { @@ -228,7 +228,7 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } else if strings.HasSuffix(ace.PrincipalSID, "S-1-3-4") { ownerLimitedPrivs = append(ownerLimitedPrivs, rightKind.String()) } else if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { - writeOwnerLimitedPrincipals = append(writeOwnerLimitedPrincipals, ace.GetCachedValue()) + potentialWriteOwnerLimitedPrincipals = append(potentialWriteOwnerLimitedPrincipals, ace.GetCachedValue()) } else { converted = append(converted, NewIngestibleRelationship( IngestibleSource{ @@ -247,20 +247,9 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } } + //TODO: When inheritance hashes are added, add them to these aces if len(ownerLimitedPrivs) > 0 { - for _, limitedPrincipal := range writeOwnerLimitedPrincipals { - converted = append(converted, NewIngestibleRelationship( - limitedPrincipal.SourceData, - IngestibleTarget{ - Target: targetID, - TargetType: targetType, - }, - IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): true}, - RelType: ad.WriteOwnerRaw, - }, - )) - + for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { converted = append(converted, NewIngestibleRelationship( limitedPrincipal.SourceData, IngestibleTarget{ @@ -275,18 +264,6 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } if ownerPrincipalInfo.Source != "" { - converted = append(converted, NewIngestibleRelationship( - ownerPrincipalInfo, - IngestibleTarget{ - Target: targetID, - TargetType: targetType, - }, - IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): true}, - RelType: ad.OwnsRaw, - }, - )) - converted = append(converted, NewIngestibleRelationship( ownerPrincipalInfo, IngestibleTarget{ @@ -308,13 +285,13 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): false}, + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false}, RelType: ad.OwnsRaw, }, )) } - for _, limitedPrincipal := range writeOwnerLimitedPrincipals { + for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { converted = append(converted, NewIngestibleRelationship( limitedPrincipal.SourceData, IngestibleTarget{ @@ -322,7 +299,7 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, ad.LimitedRightsCreated.String(): false}, + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited}, RelType: ad.WriteOwnerRaw, }, )) diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 5ed3394840..0ee0cd0a13 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -144,7 +144,6 @@ const ( HighValue Property = "highvalue" BlocksInheritance Property = "blocksinheritance" IsACL Property = "isacl" - LimitedRightsCreated Property = "limitedrightscreated" IsInherited Property = "isinherited" IsACLProtected Property = "isaclprotected" IsDeleted Property = "isdeleted" @@ -228,7 +227,7 @@ const ( ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, LimitedRightsCreated, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA} } func ParseProperty(source string) (Property, error) { switch source { @@ -282,8 +281,6 @@ func ParseProperty(source string) (Property, error) { return BlocksInheritance, nil case "isacl": return IsACL, nil - case "limitedrightscreated": - return LimitedRightsCreated, nil case "isinherited": return IsInherited, nil case "isaclprotected": @@ -500,8 +497,6 @@ func (s Property) String() string { return string(BlocksInheritance) case IsACL: return string(IsACL) - case LimitedRightsCreated: - return string(LimitedRightsCreated) case IsInherited: return string(IsInherited) case IsACLProtected: @@ -718,8 +713,6 @@ func (s Property) Name() string { return "Blocks GPO Inheritance" case IsACL: return "Is ACL" - case LimitedRightsCreated: - return "Limited Rights Created" case IsInherited: return "Is Inherited" case IsACLProtected: diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index b8c401765b..35cfd60407 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -337,7 +337,6 @@ export enum ActiveDirectoryKindProperties { HighValue = 'highvalue', BlocksInheritance = 'blocksinheritance', IsACL = 'isacl', - LimitedRightsCreated = 'limitedrightscreated', IsInherited = 'isinherited', IsACLProtected = 'isaclprotected', IsDeleted = 'isdeleted', @@ -471,8 +470,6 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Blocks GPO Inheritance'; case ActiveDirectoryKindProperties.IsACL: return 'Is ACL'; - case ActiveDirectoryKindProperties.LimitedRightsCreated: - return 'Limited Rights Created'; case ActiveDirectoryKindProperties.IsInherited: return 'Is Inherited'; case ActiveDirectoryKindProperties.IsACLProtected: From 073bd7591c61ba5da4ccdb66b97757e9fded712f Mon Sep 17 00:00:00 2001 From: Chris Thompson <30671833+Mayyhem@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:06:01 -0500 Subject: [PATCH 05/33] BED-4965: WIP merge changes to Owns/WriteOwner post-processing (#931) * Added inheritance condition for WriteOwner abuse, added comments to Owns/WriteOwner logic * Saving changes to post Owns/WriteOwner --- cmd/api/src/analysis/ad/post.go | 3 +++ packages/cue/bh/ad/ad.cue | 2 -- packages/go/analysis/ad/owns.go | 29 +++++++++++++++++++------ packages/go/ein/ad.go | 37 ++++++++++++++++++++++++++++---- packages/go/graphschema/ad/ad.go | 2 +- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/cmd/api/src/analysis/ad/post.go b/cmd/api/src/analysis/ad/post.go index 1c61b4f987..a7e36c20ea 100644 --- a/cmd/api/src/analysis/ad/post.go +++ b/cmd/api/src/analysis/ad/post.go @@ -18,6 +18,7 @@ package ad import ( "context" + "github.com/specterops/bloodhound/graphschema/azure" "github.com/specterops/bloodhound/analysis" @@ -40,6 +41,8 @@ func Post(ctx context.Context, db graph.Database, adcsEnabled bool, citrixEnable return &aggregateStats, err } else if adcsStats, err := adAnalysis.PostADCS(ctx, db, groupExpansions, adcsEnabled); err != nil { return &aggregateStats, err + } else if ownsStats, err := adAnalysis.PostOwnsAndWriteOwner(ctx, db, groupExpansions); err != nil { + return &ownsStats, err } else { aggregateStats.Merge(stats) aggregateStats.Merge(syncLAPSStats) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 4d752a162d..b09b9180b8 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -1451,9 +1451,7 @@ ACLRelationships: [ WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, - WriteOwnerRaw, OwnsLimitedRights, - OwnsRaw ] // Edges that are used in pathfinding diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 7cc6d50826..20056cb982 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -19,6 +19,7 @@ package ad import ( "context" "fmt" + "github.com/specterops/bloodhound/analysis" "github.com/specterops/bloodhound/analysis/impact" "github.com/specterops/bloodhound/dawgs/cardinality" @@ -29,13 +30,15 @@ import ( "github.com/specterops/bloodhound/graphschema/common" ) -func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { +func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (analysis.AtomicPostProcessingStats, error) { if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postowner: %w", err) + return analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postowner: %w", err) + return analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) } else { - operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnerWriteOwner") + operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") + + // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting explicit permissions to OWNER RIGHTS operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { return tx.Relationships().Filter( query.And( @@ -44,6 +47,8 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa ), ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { + + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) if anyEnforced { if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { continue @@ -55,6 +60,7 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa enforced = false } + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge if !enforced { outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, @@ -62,9 +68,14 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa Kind: ad.Owns, RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, } + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + // Check if the target node is a computer or derived object (MSA or GMSA) continue + } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { + // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) + // If the target node is NOT a computer or derived object, add the Owns edge outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, @@ -74,6 +85,7 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa } } } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, @@ -87,6 +99,7 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa }) }) + // Get all source nodes of WriteOwner ACEs where the target node has no ACEs granting explicit permissions to OWNER RIGHTS operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { return tx.Relationships().Filter( query.And( @@ -95,6 +108,8 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa ), ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { + + // If any domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), check if the target node is a computer or derived object (MSA or GMSA) if anyEnforced { if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { continue @@ -105,7 +120,6 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa if !ok { enforced = false } - if !enforced { outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, @@ -115,7 +129,9 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa } } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { continue + } else if !isComputerDerived { + // If the target node is NOT a computer or derived object, add the WriteOwner edge outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, @@ -125,6 +141,7 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa } } } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, @@ -138,7 +155,7 @@ func PostOwner(ctx context.Context, db graph.Database, groupExpansions impact.Pa }) }) - return &operation.Stats, operation.Done() + return operation.Stats, operation.Done() } } diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 7cfd39358e..99cd969665 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -206,10 +206,11 @@ type WriteOwnerLimitedCache struct { func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []IngestibleRelationship { var ( - converted = make([]IngestibleRelationship, 0) - potentialWriteOwnerLimitedPrincipals = make([]WriteOwnerLimitedCache, 0) - ownerLimitedPrivs = make([]string, 0) ownerPrincipalInfo IngestibleSource + ownerLimitedPrivs = make([]string, 0) + writeOwnerLimitedPrivs = make([]string, 0) + potentialWriteOwnerLimitedPrincipals = make([]WriteOwnerLimitedCache, 0) + converted = make([]IngestibleRelationship, 0) ) for _, ace := range aces { @@ -223,13 +224,26 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } else if !ad.IsACLKind(rightKind) { log.Errorf("Non-ace edge type given to process aces: %s", ace.RightName) continue + } else if rightKind.Is(ad.Owns) || rightKind.Is(ad.OwnsRaw) { + // Get Owner SID from ACE granting Owns permission ownerPrincipalInfo = ace.GetCachedValue().SourceData + } else if strings.HasSuffix(ace.PrincipalSID, "S-1-3-4") { + // Cache ACEs where the OWNER RIGHTS SID is granted explicit abusable permissions ownerLimitedPrivs = append(ownerLimitedPrivs, rightKind.String()) + + if ace.IsInherited { + // If the ACE is inherited, it is abusable by principals with WriteOwner permission + writeOwnerLimitedPrivs = append(writeOwnerLimitedPrivs, rightKind.String()) + } + } else if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { + // Cache ACEs where WriteOwner permission is granted potentialWriteOwnerLimitedPrincipals = append(potentialWriteOwnerLimitedPrincipals, ace.GetCachedValue()) + } else { + // Create edges for all other ACEs converted = append(converted, NewIngestibleRelationship( IngestibleSource{ Source: ace.PrincipalSID, @@ -248,7 +262,11 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib } //TODO: When inheritance hashes are added, add them to these aces + + // Process permissions granted to the OWNER RIGHTS SID if any were found if len(ownerLimitedPrivs) > 0 { + + // For every principal granted WriteOwner permission, create a WriteOwnerLimitedRights edge for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { converted = append(converted, NewIngestibleRelationship( limitedPrincipal.SourceData, @@ -256,13 +274,16 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib Target: targetID, TargetType: targetType, }, + + // Create an edge property containing an array of all INHERITED abusable permissions granted to the OWNER RIGHTS SID IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": ownerLimitedPrivs}, + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": writeOwnerLimitedPrivs}, RelType: ad.WriteOwnerLimitedRights, }, )) } + // Create an OwnsLimitedRights edge containing all abusable permissions granted to the OWNER RIGHTS SID if ownerPrincipalInfo.Source != "" { converted = append(converted, NewIngestibleRelationship( ownerPrincipalInfo, @@ -270,6 +291,8 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib Target: targetID, TargetType: targetType, }, + + // Owns is never inherited IngestibleRel{ RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, "privileges": ownerLimitedPrivs}, RelType: ad.OwnsLimitedRights, @@ -277,6 +300,9 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib )) } } else { + // If no permissions in the ACL are granted to the OWNER RIGHTS SID + + // Create a non-traversable OwnsRaw edge for post-processing if ownerPrincipalInfo.Source != "" { converted = append(converted, NewIngestibleRelationship( ownerPrincipalInfo, @@ -284,6 +310,8 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib Target: targetID, TargetType: targetType, }, + + // Owns is never inherited IngestibleRel{ RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false}, RelType: ad.OwnsRaw, @@ -291,6 +319,7 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib )) } + // Create a non-traversable WriteOwnerRaw edge for post-processing for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { converted = append(converted, NewIngestibleRelationship( limitedPrincipal.SourceData, diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 0ee0cd0a13..bcda4681ba 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -892,7 +892,7 @@ 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, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } 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, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} + return []graph.Kind{AllExtendedRights, ForceChangePassword, AddMember, AddAllowedToAct, GenericAll, WriteDACL, WriteOwner, GenericWrite, ReadLAPSPassword, ReadGMSAPassword, Owns, AddSelf, WriteSPN, AddKeyCredentialLink, GetChanges, GetChangesAll, GetChangesInFilteredSet, WriteAccountRestrictions, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights} } func PathfindingRelationships() []graph.Kind { return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser, WriteOwnerLimitedRights, OwnsLimitedRights} From 44966c634a1575d890059412687dbe6e3c70b3ca Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 4 Nov 2024 16:20:40 -0500 Subject: [PATCH 06/33] wip: fix post --- cmd/api/src/api/v2/auth/saml.go | 16 +++ packages/go/analysis/ad/owns.go | 45 +++++-- packages/go/analysis/post_operation.go | 4 +- packages/go/graphschema/ad/ad.go | 180 ++++++++++++------------- 4 files changed, 143 insertions(+), 102 deletions(-) diff --git a/cmd/api/src/api/v2/auth/saml.go b/cmd/api/src/api/v2/auth/saml.go index 591bfb742e..45fb3baa79 100644 --- a/cmd/api/src/api/v2/auth/saml.go +++ b/cmd/api/src/api/v2/auth/saml.go @@ -1,3 +1,19 @@ +// Copyright 2024 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 + package auth import ( diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 20056cb982..7499b909cf 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -18,6 +18,7 @@ package ad import ( "context" + "errors" "fmt" "github.com/specterops/bloodhound/analysis" @@ -62,35 +63,45 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge if !enforced { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { // Check if the target node is a computer or derived object (MSA or GMSA) continue - } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) // If the target node is NOT a computer or derived object, add the Owns edge + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } } } else { // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } } @@ -121,32 +132,44 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio enforced = false } if !enforced { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { continue } else if !isComputerDerived { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } // If the target node is NOT a computer or derived object, add the WriteOwner edge outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } } } else { // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): rel.Properties.GetOrDefault(ad.IsInherited.String(), false)}, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } } @@ -162,12 +185,14 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio func isTargetNodeComputerDerived(node *graph.Node) (bool, error) { if node.Kinds.ContainsOneOf(ad.Computer) { return true, nil - } else if isGmsa, err := node.Properties.Get(ad.GMSA.String()).Bool(); err != nil { + } else if isGmsa, err := node.Properties.Get(ad.GMSA.String()).Bool(); err != nil && !errors.Is(err, graph.ErrPropertyNotFound) { return false, err } else if isGmsa { return true, nil + } else if isMsa, err := node.Properties.Get(ad.MSA.String()).Bool(); err != nil && !errors.Is(err, graph.ErrPropertyNotFound) { + return false, err } else { - return node.Properties.Get(ad.MSA.String()).Bool() + return isMsa, nil } } diff --git a/packages/go/analysis/post_operation.go b/packages/go/analysis/post_operation.go index 1eb9008d38..d8fa8cd996 100644 --- a/packages/go/analysis/post_operation.go +++ b/packages/go/analysis/post_operation.go @@ -44,8 +44,8 @@ func NewPostRelationshipOperation(ctx context.Context, db graph.Database, operat ) for nextJob := range inC { - if len(nextJob.RelProperties) > 0 { - tempRelProp := NewPropertiesWithLastSeen() + if nextJob.RelProperties != nil && len(nextJob.RelProperties) > 0 { + tempRelProp := relProp for key, val := range nextJob.RelProperties { tempRelProp.Set(key, val) } diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index bcda4681ba..793bc1cd87 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -25,95 +25,95 @@ import ( ) var ( - Entity = graph.StringKind("Base") - User = graph.StringKind("User") - Computer = graph.StringKind("Computer") - Group = graph.StringKind("Group") - GPO = graph.StringKind("GPO") - OU = graph.StringKind("OU") - Container = graph.StringKind("Container") - Domain = graph.StringKind("Domain") - LocalGroup = graph.StringKind("ADLocalGroup") - LocalUser = graph.StringKind("ADLocalUser") - AIACA = graph.StringKind("AIACA") - RootCA = graph.StringKind("RootCA") - EnterpriseCA = graph.StringKind("EnterpriseCA") - NTAuthStore = graph.StringKind("NTAuthStore") - CertTemplate = graph.StringKind("CertTemplate") - IssuancePolicy = graph.StringKind("IssuancePolicy") - Owns = graph.StringKind("Owns") - GenericAll = graph.StringKind("GenericAll") - GenericWrite = graph.StringKind("GenericWrite") - WriteOwner = graph.StringKind("WriteOwner") - WriteDACL = graph.StringKind("WriteDacl") - MemberOf = graph.StringKind("MemberOf") - ForceChangePassword = graph.StringKind("ForceChangePassword") - AllExtendedRights = graph.StringKind("AllExtendedRights") - AddMember = graph.StringKind("AddMember") - HasSession = graph.StringKind("HasSession") - Contains = graph.StringKind("Contains") - GPLink = graph.StringKind("GPLink") - AllowedToDelegate = graph.StringKind("AllowedToDelegate") - GetChanges = graph.StringKind("GetChanges") - GetChangesAll = graph.StringKind("GetChangesAll") - GetChangesInFilteredSet = graph.StringKind("GetChangesInFilteredSet") - TrustedBy = graph.StringKind("TrustedBy") - AllowedToAct = graph.StringKind("AllowedToAct") - AdminTo = graph.StringKind("AdminTo") - CanPSRemote = graph.StringKind("CanPSRemote") - CanRDP = graph.StringKind("CanRDP") - ExecuteDCOM = graph.StringKind("ExecuteDCOM") - HasSIDHistory = graph.StringKind("HasSIDHistory") - AddSelf = graph.StringKind("AddSelf") - DCSync = graph.StringKind("DCSync") - ReadLAPSPassword = graph.StringKind("ReadLAPSPassword") - ReadGMSAPassword = graph.StringKind("ReadGMSAPassword") - DumpSMSAPassword = graph.StringKind("DumpSMSAPassword") - SQLAdmin = graph.StringKind("SQLAdmin") - AddAllowedToAct = graph.StringKind("AddAllowedToAct") - WriteSPN = graph.StringKind("WriteSPN") - AddKeyCredentialLink = graph.StringKind("AddKeyCredentialLink") - LocalToComputer = graph.StringKind("LocalToComputer") - MemberOfLocalGroup = graph.StringKind("MemberOfLocalGroup") - RemoteInteractiveLogonPrivilege = graph.StringKind("RemoteInteractiveLogonPrivilege") - SyncLAPSPassword = graph.StringKind("SyncLAPSPassword") - WriteAccountRestrictions = graph.StringKind("WriteAccountRestrictions") - WriteGPLink = graph.StringKind("WriteGPLink") - RootCAFor = graph.StringKind("RootCAFor") - DCFor = graph.StringKind("DCFor") - PublishedTo = graph.StringKind("PublishedTo") - ManageCertificates = graph.StringKind("ManageCertificates") - ManageCA = graph.StringKind("ManageCA") - DelegatedEnrollmentAgent = graph.StringKind("DelegatedEnrollmentAgent") - Enroll = graph.StringKind("Enroll") - HostsCAService = graph.StringKind("HostsCAService") - WritePKIEnrollmentFlag = graph.StringKind("WritePKIEnrollmentFlag") - WritePKINameFlag = graph.StringKind("WritePKINameFlag") - NTAuthStoreFor = graph.StringKind("NTAuthStoreFor") - TrustedForNTAuth = graph.StringKind("TrustedForNTAuth") - EnterpriseCAFor = graph.StringKind("EnterpriseCAFor") - IssuedSignedBy = graph.StringKind("IssuedSignedBy") - GoldenCert = graph.StringKind("GoldenCert") - EnrollOnBehalfOf = graph.StringKind("EnrollOnBehalfOf") - OIDGroupLink = graph.StringKind("OIDGroupLink") - ExtendedByPolicy = graph.StringKind("ExtendedByPolicy") - ADCSESC1 = graph.StringKind("ADCSESC1") - ADCSESC3 = graph.StringKind("ADCSESC3") - ADCSESC4 = graph.StringKind("ADCSESC4") - ADCSESC5 = graph.StringKind("ADCSESC5") - ADCSESC6a = graph.StringKind("ADCSESC6a") - ADCSESC6b = graph.StringKind("ADCSESC6b") - ADCSESC7 = graph.StringKind("ADCSESC7") - ADCSESC9a = graph.StringKind("ADCSESC9a") - ADCSESC9b = graph.StringKind("ADCSESC9b") - ADCSESC10a = graph.StringKind("ADCSESC10a") - ADCSESC10b = graph.StringKind("ADCSESC10b") - ADCSESC13 = graph.StringKind("ADCSESC13") - SyncedToEntraUser = graph.StringKind("SyncedToEntraUser") - WriteOwnerLimitedRights = graph.StringKind("WriteOwnerLimitedRights") - WriteOwnerRaw = graph.StringKind("WriteOwnerRaw") - OwnsLimitedRights = graph.StringKind("OwnsLimitedRights") - OwnsRaw = graph.StringKind("OwnsRaw") + Entity = graph.StringKind("Base") + User = graph.StringKind("User") + Computer = graph.StringKind("Computer") + Group = graph.StringKind("Group") + GPO = graph.StringKind("GPO") + OU = graph.StringKind("OU") + Container = graph.StringKind("Container") + Domain = graph.StringKind("Domain") + LocalGroup = graph.StringKind("ADLocalGroup") + LocalUser = graph.StringKind("ADLocalUser") + AIACA = graph.StringKind("AIACA") + RootCA = graph.StringKind("RootCA") + EnterpriseCA = graph.StringKind("EnterpriseCA") + NTAuthStore = graph.StringKind("NTAuthStore") + CertTemplate = graph.StringKind("CertTemplate") + IssuancePolicy = graph.StringKind("IssuancePolicy") + Owns = graph.StringKind("Owns") + GenericAll = graph.StringKind("GenericAll") + GenericWrite = graph.StringKind("GenericWrite") + WriteOwner = graph.StringKind("WriteOwner") + WriteDACL = graph.StringKind("WriteDacl") + MemberOf = graph.StringKind("MemberOf") + ForceChangePassword = graph.StringKind("ForceChangePassword") + AllExtendedRights = graph.StringKind("AllExtendedRights") + AddMember = graph.StringKind("AddMember") + HasSession = graph.StringKind("HasSession") + Contains = graph.StringKind("Contains") + GPLink = graph.StringKind("GPLink") + AllowedToDelegate = graph.StringKind("AllowedToDelegate") + GetChanges = graph.StringKind("GetChanges") + GetChangesAll = graph.StringKind("GetChangesAll") + GetChangesInFilteredSet = graph.StringKind("GetChangesInFilteredSet") + TrustedBy = graph.StringKind("TrustedBy") + AllowedToAct = graph.StringKind("AllowedToAct") + AdminTo = graph.StringKind("AdminTo") + CanPSRemote = graph.StringKind("CanPSRemote") + CanRDP = graph.StringKind("CanRDP") + ExecuteDCOM = graph.StringKind("ExecuteDCOM") + HasSIDHistory = graph.StringKind("HasSIDHistory") + AddSelf = graph.StringKind("AddSelf") + DCSync = graph.StringKind("DCSync") + ReadLAPSPassword = graph.StringKind("ReadLAPSPassword") + ReadGMSAPassword = graph.StringKind("ReadGMSAPassword") + DumpSMSAPassword = graph.StringKind("DumpSMSAPassword") + SQLAdmin = graph.StringKind("SQLAdmin") + AddAllowedToAct = graph.StringKind("AddAllowedToAct") + WriteSPN = graph.StringKind("WriteSPN") + AddKeyCredentialLink = graph.StringKind("AddKeyCredentialLink") + LocalToComputer = graph.StringKind("LocalToComputer") + MemberOfLocalGroup = graph.StringKind("MemberOfLocalGroup") + RemoteInteractiveLogonRight = graph.StringKind("RemoteInteractiveLogonRight") + SyncLAPSPassword = graph.StringKind("SyncLAPSPassword") + WriteAccountRestrictions = graph.StringKind("WriteAccountRestrictions") + WriteGPLink = graph.StringKind("WriteGPLink") + RootCAFor = graph.StringKind("RootCAFor") + DCFor = graph.StringKind("DCFor") + PublishedTo = graph.StringKind("PublishedTo") + ManageCertificates = graph.StringKind("ManageCertificates") + ManageCA = graph.StringKind("ManageCA") + DelegatedEnrollmentAgent = graph.StringKind("DelegatedEnrollmentAgent") + Enroll = graph.StringKind("Enroll") + HostsCAService = graph.StringKind("HostsCAService") + WritePKIEnrollmentFlag = graph.StringKind("WritePKIEnrollmentFlag") + WritePKINameFlag = graph.StringKind("WritePKINameFlag") + NTAuthStoreFor = graph.StringKind("NTAuthStoreFor") + TrustedForNTAuth = graph.StringKind("TrustedForNTAuth") + EnterpriseCAFor = graph.StringKind("EnterpriseCAFor") + IssuedSignedBy = graph.StringKind("IssuedSignedBy") + GoldenCert = graph.StringKind("GoldenCert") + EnrollOnBehalfOf = graph.StringKind("EnrollOnBehalfOf") + OIDGroupLink = graph.StringKind("OIDGroupLink") + ExtendedByPolicy = graph.StringKind("ExtendedByPolicy") + ADCSESC1 = graph.StringKind("ADCSESC1") + ADCSESC3 = graph.StringKind("ADCSESC3") + ADCSESC4 = graph.StringKind("ADCSESC4") + ADCSESC5 = graph.StringKind("ADCSESC5") + ADCSESC6a = graph.StringKind("ADCSESC6a") + ADCSESC6b = graph.StringKind("ADCSESC6b") + ADCSESC7 = graph.StringKind("ADCSESC7") + ADCSESC9a = graph.StringKind("ADCSESC9a") + ADCSESC9b = graph.StringKind("ADCSESC9b") + ADCSESC10a = graph.StringKind("ADCSESC10a") + ADCSESC10b = graph.StringKind("ADCSESC10b") + ADCSESC13 = graph.StringKind("ADCSESC13") + SyncedToEntraUser = graph.StringKind("SyncedToEntraUser") + WriteOwnerLimitedRights = graph.StringKind("WriteOwnerLimitedRights") + WriteOwnerRaw = graph.StringKind("WriteOwnerRaw") + OwnsLimitedRights = graph.StringKind("OwnsLimitedRights") + OwnsRaw = graph.StringKind("OwnsRaw") ) type Property string @@ -889,7 +889,7 @@ func Nodes() []graph.Kind { return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy} } 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, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonPrivilege, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} + 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, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } 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, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights} From 9753ce92b1851c772c0dbfee809971918a661e84 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 7 Nov 2024 17:01:57 -0500 Subject: [PATCH 07/33] Updated Owns/WriteOwner logic to check if any non-abusable ACEs granting OWNER RIGHTS are present --- packages/cue/bh/ad/ad.cue | 10 +- packages/go/analysis/ad/owns.go | 215 ++++++++++-------- packages/go/graphschema/ad/ad.go | 9 +- .../bh-shared-ui/src/graphSchema.ts | 3 + 4 files changed, 139 insertions(+), 98 deletions(-) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 1e5a635474..f918ef29e2 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -763,6 +763,13 @@ MSA: types.#StringEnum & { representation: "msa" } +DoesAnyAceGrantOwnerRights: types.#StringEnum & { + symbol: "DoesAnyAceGrantOwnerRights" + schema: "ad" + name: "Does Any ACE Grant Owner Rights" + representation: "doesanyacegrantownerrights" +} + Properties: [ AdminCount, CASecurityCollected, @@ -868,7 +875,8 @@ Properties: [ LockoutObservationWindow, OwnerSid, GMSA, - MSA + MSA, + DoesAnyAceGrantOwnerRights ] // Kinds diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 7499b909cf..8a98aaca9b 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -39,7 +39,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } else { operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") - // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting explicit permissions to OWNER RIGHTS + // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting abusable explicit permissions to OWNER RIGHTS operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { return tx.Relationships().Filter( query.And( @@ -49,59 +49,69 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) - if anyEnforced { - if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { - continue - } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - continue - } else { - enforced, ok := dsHeuristicsCache[domainSid] - if !ok { - enforced = false - } + // Get the target node of the OwnsRaw relationship + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue - // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge - if !enforced { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { - // Check if the target node is a computer or derived object (MSA or GMSA) + } else if doesAnyAceGrantOwnerRights, err := targetNode.Properties.Get(string(ad.DoesAnyAceGrantOwnerRights.String())).Bool(); err != nil && !errors.Is(err, graph.ErrPropertyNotFound) { + continue + + } else if !doesAnyAceGrantOwnerRights { + // Check if any [non-abusable] permissions are granted to OWNER RIGHTS, in which case, do NOT add the Owns edge + + if anyEnforced { + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) + if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { continue - } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { - // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) - // If the target node is NOT a computer or derived object, add the Owns edge - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge + if !enforced { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + // If no abusable or non-abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + continue + } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { + // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) + // If the target node is NOT a computer or derived object, add the Owns edge + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } } } - } - } else { - // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } } } } @@ -110,7 +120,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio }) }) - // Get all source nodes of WriteOwner ACEs where the target node has no ACEs granting explicit permissions to OWNER RIGHTS + // Get all source nodes of WriteOwner ACEs where the target node has no ACEs granting explicit abusable permissions to OWNER RIGHTS operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { return tx.Relationships().Filter( query.And( @@ -120,56 +130,69 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - // If any domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), check if the target node is a computer or derived object (MSA or GMSA) - if anyEnforced { - if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { - continue - } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - continue - } else { - enforced, ok := dsHeuristicsCache[domainSid] - if !ok { - enforced = false - } - if !enforced { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { - continue + // Get the target node of the WriteOwner relationship + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue - } else if !isComputerDerived { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false + } else if doesAnyAceGrantOwnerRights, err := targetNode.Properties.Get(string(ad.DoesAnyAceGrantOwnerRights.String())).Bool(); err != nil && !errors.Is(err, graph.ErrPropertyNotFound) { + continue + + } else if !doesAnyAceGrantOwnerRights { + // Check if any [non-abusable] permissions are granted to OWNER RIGHTS, in which case, do NOT add the WriteOwner edge + + if anyEnforced { + // If any domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), check if the target node is a computer or derived object (MSA or GMSA) + if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false } - // If the target node is NOT a computer or derived object, add the WriteOwner edge - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the WriteOwner edge + if !enforced { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + // If no abusable or non-abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + continue + + } else if !isComputerDerived { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + // If the target node is NOT a computer or derived object, add the WriteOwner edge + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } } } - } - } else { - // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } } } } diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 793bc1cd87..6ec6031fc3 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -224,10 +224,11 @@ const ( OwnerSid Property = "ownersid" GMSA Property = "gmsa" MSA Property = "msa" + DoesAnyAceGrantOwnerRights Property = "doesanyacegrantownerrights" ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA, DoesAnyAceGrantOwnerRights} } func ParseProperty(source string) (Property, error) { switch source { @@ -441,6 +442,8 @@ func ParseProperty(source string) (Property, error) { return GMSA, nil case "msa": return MSA, nil + case "doesanyacegrantownerrights": + return DoesAnyAceGrantOwnerRights, nil default: return "", errors.New("Invalid enumeration value: " + source) } @@ -657,6 +660,8 @@ func (s Property) String() string { return string(GMSA) case MSA: return string(MSA) + case DoesAnyAceGrantOwnerRights: + return string(DoesAnyAceGrantOwnerRights) default: return "Invalid enumeration case: " + string(s) } @@ -873,6 +878,8 @@ func (s Property) Name() string { return "GMSA" case MSA: return "MSA" + case DoesAnyAceGrantOwnerRights: + return "Does Any ACE Grant Owner Rights" default: return "Invalid enumeration case: " + string(s) } diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index 7195affaf5..66839a3f15 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -417,6 +417,7 @@ export enum ActiveDirectoryKindProperties { OwnerSid = 'ownersid', GMSA = 'gmsa', MSA = 'msa', + DoesAnyAceGrantOwnerRights = 'doesanyacegrantownerrights', } export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKindProperties): string | undefined { switch (value) { @@ -630,6 +631,8 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'GMSA'; case ActiveDirectoryKindProperties.MSA: return 'MSA'; + case ActiveDirectoryKindProperties.DoesAnyAceGrantOwnerRights: + return 'Does Any ACE Grant Owner Rights'; default: return undefined; } From 686201c3661a51b6dcc2363d9cf962ff72603c59 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Tue, 12 Nov 2024 14:05:31 -0500 Subject: [PATCH 08/33] Updated inherited vs. non-inherited benign ACE logic for WriteOwner --- cmd/api/src/daemons/datapipe/convertors.go | 54 +++--- packages/cue/bh/ad/ad.cue | 10 +- packages/go/analysis/ad/owns.go | 212 ++++++++++----------- packages/go/ein/ad.go | 92 ++++++--- 4 files changed, 208 insertions(+), 160 deletions(-) diff --git a/cmd/api/src/daemons/datapipe/convertors.go b/cmd/api/src/daemons/datapipe/convertors.go index 3d6604be31..3f7a267390 100644 --- a/cmd/api/src/daemons/datapipe/convertors.go +++ b/cmd/api/src/daemons/datapipe/convertors.go @@ -23,7 +23,7 @@ import ( func convertComputerData(computer ein.Computer, converted *ConvertedData) { baseNodeProp := ein.ConvertObjectToNode(computer.IngestBase, ad.Computer) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(computer.Aces, computer.ObjectIdentifier, ad.Computer)...) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, computer.Aces, computer.ObjectIdentifier, ad.Computer)...) if primaryGroupRel := ein.ParsePrimaryGroup(computer.IngestBase, ad.Computer, computer.PrimaryGroupSID); primaryGroupRel.IsValid() { converted.RelProps = append(converted.RelProps, primaryGroupRel) } @@ -59,8 +59,9 @@ func convertComputerData(computer ein.Computer, converted *ConvertedData) { } func convertUserData(user ein.User, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(user.IngestBase, ad.User)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(user.Aces, user.ObjectIdentifier, ad.User)...) + baseNodeProp := ein.ConvertObjectToNode(user.IngestBase, ad.User) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, user.Aces, user.ObjectIdentifier, ad.User)...) if rel := ein.ParseObjectContainer(user.IngestBase, ad.User); rel.IsValid() { converted.RelProps = append(converted.RelProps, rel) } @@ -68,12 +69,13 @@ func convertUserData(user ein.User, converted *ConvertedData) { } func convertGroupData(group ein.Group, converted *ConvertedGroupData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(group.IngestBase, ad.Group)) + baseNodeProp := ein.ConvertObjectToNode(group.IngestBase, ad.Group) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) if rel := ein.ParseObjectContainer(group.IngestBase, ad.Group); rel.Source != "" && rel.Target != "" { converted.RelProps = append(converted.RelProps, rel) } - converted.RelProps = append(converted.RelProps, ein.ParseACEData(group.Aces, group.ObjectIdentifier, ad.Group)...) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, group.Aces, group.ObjectIdentifier, ad.Group)...) groupMembershipData := ein.ParseGroupMembershipData(group) converted.RelProps = append(converted.RelProps, groupMembershipData.RegularMembers...) @@ -81,8 +83,9 @@ func convertGroupData(group ein.Group, converted *ConvertedGroupData) { } func convertDomainData(domain ein.Domain, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(domain.IngestBase, ad.Domain)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(domain.Aces, domain.ObjectIdentifier, ad.Domain)...) + baseNodeProp := ein.ConvertObjectToNode(domain.IngestBase, ad.Domain) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, domain.Aces, domain.ObjectIdentifier, ad.Domain)...) if len(domain.ChildObjects) > 0 { converted.RelProps = append(converted.RelProps, ein.ParseChildObjects(domain.ChildObjects, domain.ObjectIdentifier, ad.Domain)...) } @@ -97,13 +100,15 @@ func convertDomainData(domain ein.Domain, converted *ConvertedData) { } func convertGPOData(gpo ein.GPO, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(ein.IngestBase(gpo), ad.GPO)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(gpo.Aces, gpo.ObjectIdentifier, ad.GPO)...) + baseNodeProp := ein.ConvertObjectToNode(ein.IngestBase(gpo), ad.GPO) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, gpo.Aces, gpo.ObjectIdentifier, ad.GPO)...) } func convertOUData(ou ein.OU, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(ou.IngestBase, ad.OU)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(ou.Aces, ou.ObjectIdentifier, ad.OU)...) + baseNodeProp := ein.ConvertObjectToNode(ou.IngestBase, ad.OU) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, ou.Aces, ou.ObjectIdentifier, ad.OU)...) if container := ein.ParseObjectContainer(ou.IngestBase, ad.OU); container.IsValid() { converted.RelProps = append(converted.RelProps, container) } @@ -125,8 +130,9 @@ func CreateConvertedSessionData(count int) ConvertedSessionData { } func convertContainerData(container ein.Container, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(container.IngestBase, ad.Container)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(container.Aces, container.ObjectIdentifier, ad.Container)...) + baseNodeProp := ein.ConvertObjectToNode(container.IngestBase, ad.Container) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, container.Aces, container.ObjectIdentifier, ad.Container)...) if rel := ein.ParseObjectContainer(container.IngestBase, ad.Container); rel.IsValid() { converted.RelProps = append(converted.RelProps, rel) @@ -138,8 +144,9 @@ func convertContainerData(container ein.Container, converted *ConvertedData) { } func convertAIACAData(aiaca ein.AIACA, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(ein.IngestBase(aiaca), ad.AIACA)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(aiaca.Aces, aiaca.ObjectIdentifier, ad.AIACA)...) + baseNodeProp := ein.ConvertObjectToNode(ein.IngestBase(aiaca), ad.AIACA) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, aiaca.Aces, aiaca.ObjectIdentifier, ad.AIACA)...) if rel := ein.ParseObjectContainer(ein.IngestBase(aiaca), ad.AIACA); rel.IsValid() { converted.RelProps = append(converted.RelProps, rel) @@ -147,8 +154,9 @@ func convertAIACAData(aiaca ein.AIACA, converted *ConvertedData) { } func convertRootCAData(rootca ein.RootCA, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(rootca.IngestBase, ad.RootCA)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(rootca.Aces, rootca.ObjectIdentifier, ad.RootCA)...) + baseNodeProp := ein.ConvertObjectToNode(rootca.IngestBase, ad.RootCA) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, rootca.Aces, rootca.ObjectIdentifier, ad.RootCA)...) converted.RelProps = append(converted.RelProps, ein.ParseRootCAMiscData(rootca)...) if rel := ein.ParseObjectContainer(rootca.IngestBase, ad.RootCA); rel.IsValid() { @@ -167,9 +175,10 @@ func convertEnterpriseCAData(enterpriseca ein.EnterpriseCA, converted *Converted } func convertNTAuthStoreData(ntauthstore ein.NTAuthStore, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(ntauthstore.IngestBase, ad.NTAuthStore)) + baseNodeProp := ein.ConvertObjectToNode(ntauthstore.IngestBase, ad.NTAuthStore) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) converted.RelProps = append(converted.RelProps, ein.ParseNTAuthStoreData(ntauthstore)...) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(ntauthstore.Aces, ntauthstore.ObjectIdentifier, ad.NTAuthStore)...) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, ntauthstore.Aces, ntauthstore.ObjectIdentifier, ad.NTAuthStore)...) if rel := ein.ParseObjectContainer(ntauthstore.IngestBase, ad.NTAuthStore); rel.IsValid() { converted.RelProps = append(converted.RelProps, rel) @@ -177,8 +186,9 @@ func convertNTAuthStoreData(ntauthstore ein.NTAuthStore, converted *ConvertedDat } func convertCertTemplateData(certtemplate ein.CertTemplate, converted *ConvertedData) { - converted.NodeProps = append(converted.NodeProps, ein.ConvertObjectToNode(ein.IngestBase(certtemplate), ad.CertTemplate)) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(certtemplate.Aces, certtemplate.ObjectIdentifier, ad.CertTemplate)...) + baseNodeProp := ein.ConvertObjectToNode(ein.IngestBase(certtemplate), ad.CertTemplate) + converted.NodeProps = append(converted.NodeProps, baseNodeProp) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(baseNodeProp, certtemplate.Aces, certtemplate.ObjectIdentifier, ad.CertTemplate)...) if rel := ein.ParseObjectContainer(ein.IngestBase(certtemplate), ad.CertTemplate); rel.IsValid() { converted.RelProps = append(converted.RelProps, rel) @@ -206,7 +216,7 @@ func convertIssuancePolicy(issuancePolicy ein.IssuancePolicy, converted *Convert } converted.NodeProps = append(converted.NodeProps, props) - converted.RelProps = append(converted.RelProps, ein.ParseACEData(issuancePolicy.Aces, issuancePolicy.ObjectIdentifier, ad.IssuancePolicy)...) + converted.RelProps = append(converted.RelProps, ein.ParseACEData(props, issuancePolicy.Aces, issuancePolicy.ObjectIdentifier, ad.IssuancePolicy)...) if container := ein.ParseObjectContainer(issuancePolicy.IngestBase, ad.IssuancePolicy); container.IsValid() { converted.RelProps = append(converted.RelProps, container) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index f918ef29e2..d388afb7eb 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -770,6 +770,13 @@ DoesAnyAceGrantOwnerRights: types.#StringEnum & { representation: "doesanyacegrantownerrights" } +DoesAnyInheritedAceGrantOwnerRights: types.#StringEnum & { + symbol: "DoesAnyInheritedAceGrantOwnerRights" + schema: "ad" + name: "Does Any Inherited ACE Grant Owner Rights" + representation: "doesanyinheritedacegrantownerrights" +} + Properties: [ AdminCount, CASecurityCollected, @@ -876,7 +883,8 @@ Properties: [ OwnerSid, GMSA, MSA, - DoesAnyAceGrantOwnerRights + DoesAnyAceGrantOwnerRights, + DoesAnyInheritedAceGrantOwnerRights ] // Kinds diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 8a98aaca9b..27fd73c85a 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -49,69 +49,64 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - // Get the target node of the OwnsRaw relationship - if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { - continue + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) + if anyEnforced { - } else if doesAnyAceGrantOwnerRights, err := targetNode.Properties.Get(string(ad.DoesAnyAceGrantOwnerRights.String())).Bool(); err != nil && !errors.Is(err, graph.ErrPropertyNotFound) { - continue + // Get the target node of the OwnsRaw relationship + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue - } else if !doesAnyAceGrantOwnerRights { - // Check if any [non-abusable] permissions are granted to OWNER RIGHTS, in which case, do NOT add the Owns edge + } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + // Get the dSHeuristics value for the domain of the target node + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false + } - if anyEnforced { - // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) - if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - continue - } else { - enforced, ok := dsHeuristicsCache[domainSid] - if !ok { - enforced = false + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge + if !enforced { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge - if !enforced { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { - // If no abusable or non-abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) - continue - } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { - // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) - // If the target node is NOT a computer or derived object, add the Owns edge - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + continue + } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { + // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) + // If the target node is NOT a computer or derived object, add the Owns edge + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } - } else { - // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } + } + } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } } @@ -130,69 +125,64 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - // Get the target node of the WriteOwner relationship - if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { - continue + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) + if anyEnforced { - } else if doesAnyAceGrantOwnerRights, err := targetNode.Properties.Get(string(ad.DoesAnyAceGrantOwnerRights.String())).Bool(); err != nil && !errors.Is(err, graph.ErrPropertyNotFound) { - continue + // Get the target node of the WriteOwner relationship + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue - } else if !doesAnyAceGrantOwnerRights { - // Check if any [non-abusable] permissions are granted to OWNER RIGHTS, in which case, do NOT add the WriteOwner edge + } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + // Get the dSHeuristics value for the domain of the target node + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false + } - if anyEnforced { - // If any domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), check if the target node is a computer or derived object (MSA or GMSA) - if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - continue - } else { - enforced, ok := dsHeuristicsCache[domainSid] - if !ok { - enforced = false + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the WriteOwner edge + if !enforced { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the WriteOwner edge - if !enforced { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { - // If no abusable or non-abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) - continue + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + continue - } else if !isComputerDerived { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - // If the target node is NOT a computer or derived object, add the WriteOwner edge - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } + } else if !isComputerDerived { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + // If the target node is NOT a computer or derived object, add the WriteOwner edge + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } - } else { - // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } + } + } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } } } diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 99cd969665..641cb0cd06 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -204,7 +204,7 @@ type WriteOwnerLimitedCache struct { IsInherited bool } -func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []IngestibleRelationship { +func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, targetType graph.Kind) []IngestibleRelationship { var ( ownerPrincipalInfo IngestibleSource ownerLimitedPrivs = make([]string, 0) @@ -229,6 +229,11 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib // Get Owner SID from ACE granting Owns permission ownerPrincipalInfo = ace.GetCachedValue().SourceData + } else if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { + // Don't convert every WriteOwner permission to an edge, as they are not always abusable + // Cache ACEs where WriteOwner permission is granted + potentialWriteOwnerLimitedPrincipals = append(potentialWriteOwnerLimitedPrincipals, ace.GetCachedValue()) + } else if strings.HasSuffix(ace.PrincipalSID, "S-1-3-4") { // Cache ACEs where the OWNER RIGHTS SID is granted explicit abusable permissions ownerLimitedPrivs = append(ownerLimitedPrivs, rightKind.String()) @@ -236,11 +241,15 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib if ace.IsInherited { // If the ACE is inherited, it is abusable by principals with WriteOwner permission writeOwnerLimitedPrivs = append(writeOwnerLimitedPrivs, rightKind.String()) - } - } else if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { - // Cache ACEs where WriteOwner permission is granted - potentialWriteOwnerLimitedPrincipals = append(potentialWriteOwnerLimitedPrincipals, ace.GetCachedValue()) + // if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { + // // Cache ACEs where WriteOwner permission is granted + // potentialWriteOwnerLimitedPrincipals = append(potentialWriteOwnerLimitedPrincipals, ace.GetCachedValue()) + // } + } else { + // If the ACE is not inherited, it not abusable after abusing WriteOwner + continue + } } else { // Create edges for all other ACEs @@ -263,26 +272,9 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib //TODO: When inheritance hashes are added, add them to these aces - // Process permissions granted to the OWNER RIGHTS SID if any were found + // Process abusable permissions granted to the OWNER RIGHTS SID if any were found if len(ownerLimitedPrivs) > 0 { - // For every principal granted WriteOwner permission, create a WriteOwnerLimitedRights edge - for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { - converted = append(converted, NewIngestibleRelationship( - limitedPrincipal.SourceData, - IngestibleTarget{ - Target: targetID, - TargetType: targetType, - }, - - // Create an edge property containing an array of all INHERITED abusable permissions granted to the OWNER RIGHTS SID - IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": writeOwnerLimitedPrivs}, - RelType: ad.WriteOwnerLimitedRights, - }, - )) - } - // Create an OwnsLimitedRights edge containing all abusable permissions granted to the OWNER RIGHTS SID if ownerPrincipalInfo.Source != "" { converted = append(converted, NewIngestibleRelationship( @@ -299,8 +291,52 @@ func ParseACEData(aces []ACE, targetID string, targetType graph.Kind) []Ingestib }, )) } + + if len(writeOwnerLimitedPrivs) > 0 { + // For every principal granted WriteOwner permission, create a WriteOwnerLimitedRights edge + for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { + converted = append(converted, NewIngestibleRelationship( + limitedPrincipal.SourceData, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + + // Create an edge property containing an array of all INHERITED abusable permissions granted to the OWNER RIGHTS SID + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": writeOwnerLimitedPrivs}, + RelType: ad.WriteOwnerLimitedRights, + }, + )) + } + } + } else { - // If no permissions in the ACL are granted to the OWNER RIGHTS SID + // If no abusable permissions in the ACL are granted to the OWNER RIGHTS SID + + // Check whether any permissions were granted to the OWNER RIGHTS SID that are not abusable + doesAnyAceGrantOwnerRights, exists := targetNode.PropertyMap[ad.DoesAnyAceGrantOwnerRights.String()] + if exists { + doesAnyAceGrantOwnerRights, ok := doesAnyAceGrantOwnerRights.(bool) + if ok { + if doesAnyAceGrantOwnerRights { + + // If the non-abusable rights were inherited, they are not deleted on ownership change and WriteOwner is not abusable + // Do NOT create the OwnsRaw or WriteOwnerRaw edges if the OWNER RIGHTS SID has inherited, non-abusable permissions + doesAnyInheritedAceGrantOwnerRights, exists := targetNode.PropertyMap[ad.DoesAnyInheritedAceGrantOwnerRights.String()] + if exists { + doesAnyInheritedAceGrantOwnerRights, ok := doesAnyInheritedAceGrantOwnerRights.(bool) + if ok { + if doesAnyInheritedAceGrantOwnerRights { + return converted + } + // If the non-abusable rights were NOT inherited, they are deleted on ownership change and WriteOwner may be abusable + // Post will determine if WriteOwner is abusable based on dSHeuristics:BlockOwnerImplicitRights enforcement and object type + } + } + } + } + } // Create a non-traversable OwnsRaw edge for post-processing if ownerPrincipalInfo.Source != "" { @@ -871,6 +907,10 @@ func handleEnterpriseCAEnrollmentAgentRestrictions(enterpriseCA EnterpriseCA, re } func handleEnterpriseCASecurity(enterpriseCA EnterpriseCA, relationships []IngestibleRelationship) []IngestibleRelationship { + + // Get IngesibleNode for EnterpriceCA + baseNodeProp := ConvertObjectToNode(enterpriseCA.IngestBase, ad.EnterpriseCA) + if enterpriseCA.CARegistryData.CASecurity.Collected { caSecurityData := slicesext.Filter(enterpriseCA.CARegistryData.CASecurity.Data, func(s ACE) bool { if s.PrincipalType == ad.LocalGroup.String() { @@ -896,10 +936,10 @@ func handleEnterpriseCASecurity(enterpriseCA EnterpriseCA, relationships []Inges }) combinedData := append(caSecurityData, filteredACES...) - relationships = append(relationships, ParseACEData(combinedData, enterpriseCA.ObjectIdentifier, ad.EnterpriseCA)...) + relationships = append(relationships, ParseACEData(baseNodeProp, combinedData, enterpriseCA.ObjectIdentifier, ad.EnterpriseCA)...) } else { - relationships = append(relationships, ParseACEData(enterpriseCA.Aces, enterpriseCA.ObjectIdentifier, ad.EnterpriseCA)...) + relationships = append(relationships, ParseACEData(baseNodeProp, enterpriseCA.Aces, enterpriseCA.ObjectIdentifier, ad.EnterpriseCA)...) } return relationships From 04b7915a5090f5d048dd381fe012e5884e5f77f1 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Tue, 12 Nov 2024 17:36:45 -0500 Subject: [PATCH 09/33] Added Owns/WriteOwner to post-processed edges, deleted commented code --- packages/go/analysis/ad/post.go | 2 ++ packages/go/ein/ad.go | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/go/analysis/ad/post.go b/packages/go/analysis/ad/post.go index 4c08959de2..e56acb961e 100644 --- a/packages/go/analysis/ad/post.go +++ b/packages/go/analysis/ad/post.go @@ -59,6 +59,8 @@ func PostProcessedRelationships() []graph.Kind { ad.ADCSESC13, ad.EnrollOnBehalfOf, ad.SyncedToEntraUser, + ad.Owns, + ad.WriteOwner, } } diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 641cb0cd06..12a19fdece 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -241,11 +241,6 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target if ace.IsInherited { // If the ACE is inherited, it is abusable by principals with WriteOwner permission writeOwnerLimitedPrivs = append(writeOwnerLimitedPrivs, rightKind.String()) - - // if rightKind.Is(ad.WriteOwner) || rightKind.Is(ad.WriteOwnerRaw) { - // // Cache ACEs where WriteOwner permission is granted - // potentialWriteOwnerLimitedPrincipals = append(potentialWriteOwnerLimitedPrincipals, ace.GetCachedValue()) - // } } else { // If the ACE is not inherited, it not abusable after abusing WriteOwner continue From 6d18b9cfc4698e4e485211ea6fb50c76567eee85 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Wed, 13 Nov 2024 11:14:23 -0500 Subject: [PATCH 10/33] Return stats for aggregation --- cmd/api/src/analysis/ad/post.go | 3 ++- packages/go/analysis/ad/owns.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/api/src/analysis/ad/post.go b/cmd/api/src/analysis/ad/post.go index 557345b302..d32259d796 100644 --- a/cmd/api/src/analysis/ad/post.go +++ b/cmd/api/src/analysis/ad/post.go @@ -41,13 +41,14 @@ func Post(ctx context.Context, db graph.Database, adcsEnabled bool, citrixEnable } else if adcsStats, err := adAnalysis.PostADCS(ctx, db, groupExpansions, adcsEnabled); err != nil { return &aggregateStats, err } else if ownsStats, err := adAnalysis.PostOwnsAndWriteOwner(ctx, db, groupExpansions); err != nil { - return &ownsStats, err + return &aggregateStats, err } else { aggregateStats.Merge(stats) aggregateStats.Merge(syncLAPSStats) aggregateStats.Merge(dcSyncStats) aggregateStats.Merge(localGroupStats) aggregateStats.Merge(adcsStats) + aggregateStats.Merge(ownsStats) return &aggregateStats, nil } } diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 27fd73c85a..6e103438c7 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -31,11 +31,11 @@ import ( "github.com/specterops/bloodhound/graphschema/common" ) -func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (analysis.AtomicPostProcessingStats, error) { +func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { - return analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { - return analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) } else { operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") @@ -191,7 +191,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio }) }) - return operation.Stats, operation.Done() + return &operation.Stats, operation.Done() } } From 9b132d6c618a7d8890e3fcf46786f86e8d830e38 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Wed, 13 Nov 2024 11:19:53 -0500 Subject: [PATCH 11/33] Return cursor.Error() instead of nil --- packages/go/analysis/ad/owns.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 6e103438c7..6bdbbb904b 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -111,7 +111,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } } - return nil + return cursor.Error() }) }) @@ -187,7 +187,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } } - return nil + return cursor.Error() }) }) @@ -224,7 +224,7 @@ func FetchAdminGroupIds(ctx context.Context, db graph.Database, groupExpansions adminIds.Or(groupExpansions.Cardinality(id.Uint64())) } - return nil + return cursor.Error() }) }) } From c6e2175eb6d85ddea5b31e53dfd8d0f910a48779 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 14 Nov 2024 14:34:44 -0500 Subject: [PATCH 12/33] Updated Owns/WriteOwner[LimitedRights] help text --- .../src/components/HelpTexts/Owns/General.tsx | 42 ++++++++++++++++++- .../components/HelpTexts/Owns/References.tsx | 28 +++++++++++++ .../HelpTexts/OwnsLimitedRights/General.tsx | 33 +++++++++++++++ .../OwnsLimitedRights/LinuxAbuse.tsx | 35 ++++++++++++++++ .../HelpTexts/OwnsLimitedRights/Opsec.tsx | 30 +++++++++++++ .../OwnsLimitedRights/OwnsLimitedRights.tsx | 31 ++++++++++++++ .../OwnsLimitedRights/References.tsx | 34 +++++++++++++++ .../OwnsLimitedRights/WindowsAbuse.tsx | 34 +++++++++++++++ .../HelpTexts/WriteOwner/General.tsx | 20 ++++++++- .../HelpTexts/WriteOwner/References.tsx | 28 +++++++++++++ .../WriteOwnerLimitedRights/General.tsx | 34 +++++++++++++++ .../WriteOwnerLimitedRights/LinuxAbuse.tsx | 34 +++++++++++++++ .../WriteOwnerLimitedRights/Opsec.tsx | 30 +++++++++++++ .../WriteOwnerLimitedRights/References.tsx | 34 +++++++++++++++ .../WriteOwnerLimitedRights/WindowsAbuse.tsx | 34 +++++++++++++++ .../WriteOwnerLimitedRights.tsx | 31 ++++++++++++++ .../src/components/HelpTexts/index.tsx | 4 ++ 17 files changed, 512 insertions(+), 4 deletions(-) create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/OwnsLimitedRights.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx create mode 100644 packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WriteOwnerLimitedRights.tsx diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx index 2097f52beb..17064af9eb 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx @@ -27,8 +27,46 @@ const General: FC = ({ sourceName, sourceType, targetName, target - Object owners retain the ability to modify object security descriptors, regardless of permissions on the - object's DACL + The owner of an object is implicitly granted the ability to modify object security descriptors, including the DACL, + when the following conditions are met: +
+
    +
  • + The OWNER RIGHTS SID (S-1-3-4) is NOT explicitly granted privileges on the object +
  • +
    + OR +
    +
    +
  • + Implicit owner rights are NOT blocked +
  • +
+
+ + + Implicit owner rights are not blocked and are therefore abusable when the following conditions are met: + +
    +
  • + The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is defined in + the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, implicit owner rights are not blocked. +
  • +
    + AND EITHER: +
    +
    +
  • + The object is NOT a computer or derivative of a computer object (e.g., MSA, GMSA) +
  • +
    + OR +
    +
    +
  • + The object is a computer or derivative of a computer object and the owner is a member of the Domain Admins or Enterprise Admins group (or is the SID of either group) +
  • +
); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/References.tsx index 47864ed953..6b0751fd41 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/References.tsx @@ -143,6 +143,34 @@ const References: FC = () => { href='https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53'> https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53 +
+ + https://support.microsoft.com/en-us/topic/kb5008383-active-directory-permissions-updates-cve-2021-42291-536d5555-ffba-4248-a60e-d6cbc849cde1 + +
+ + https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/fb7c101d-ec8b-4fbf-bca8-7d7c2d747d0c + +
+ + https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 + +
+ + https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers + ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx new file mode 100644 index 0000000000..69050b4b15 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx @@ -0,0 +1,33 @@ +// 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 + +import { FC } from 'react'; +import { EdgeInfoProps } from '../index'; +import { Typography } from '@mui/material'; + +const General: FC = ({ sourceName, sourceType }) => { + return ( + <> + + When specific privileges on an object's DACL are explicitly granted to the "OWNER RIGHTS" SID (S-1-3-4), + implicit owner rights (e.g., WriteDacl) are blocked, and the owner is granted only the specific privileges + granted to OWNER RIGHTS. This can be used to limit the rights of the owner of an object. + + + ); +}; + +export default General; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx new file mode 100644 index 0000000000..805c18518a --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx @@ -0,0 +1,35 @@ +// 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 + +import { FC } from 'react'; +import { Typography } from '@mui/material'; + +const LinuxAbuse: FC = () => { + return ( + <> + + To abuse ownership of an object where the OWNER RIGHTS SID is explicitly granted permissions, + you can abuse the specific permissions granted to the OWNER RIGHTS SID. + + + + Please refer to the abuse info for the specific granted permissions at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + + + ) +}; + +export default LinuxAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx new file mode 100644 index 0000000000..52aa406e4b --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx @@ -0,0 +1,30 @@ +// 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 + +import { FC } from 'react'; +import { Typography } from '@mui/material'; + +const Opsec: FC = () => { + return ( + <> + + Please refer to the OPSEC info for the specific granted permissions at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + + + ) +}; + +export default Opsec; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/OwnsLimitedRights.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/OwnsLimitedRights.tsx new file mode 100644 index 0000000000..7aaaf46d8e --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/OwnsLimitedRights.tsx @@ -0,0 +1,31 @@ +// 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 + +import General from './General'; +import WindowsAbuse from './WindowsAbuse'; +import LinuxAbuse from './LinuxAbuse'; +import Opsec from './Opsec'; +import References from './References'; + +const OwnsLimitedRights = { + general: General, + windowsAbuse: WindowsAbuse, + linuxAbuse: LinuxAbuse, + opsec: Opsec, + references: References, +}; + +export default OwnsLimitedRights; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx new file mode 100644 index 0000000000..c8be0b6709 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx @@ -0,0 +1,34 @@ +// 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 + +import { FC } from 'react'; +import { Link, Box } from '@mui/material'; + +const References: FC = () => { + return ( + + + https://www.hub.trimarcsecurity.com/post/trimarc-whitepaper-owner-or-pwnd + +
+ + https://github.com/JimSycurity/OwnerOrPwned + +
+ ); +}; + +export default References; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx new file mode 100644 index 0000000000..d51492a93c --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx @@ -0,0 +1,34 @@ +// 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 + +import { FC } from 'react'; +import { Typography } from '@mui/material'; + +const WindowsAbuse: FC = () => { + return ( + <> + + To abuse ownership of an object where the OWNER RIGHTS SID is explicitly granted privileges, + you can abuse the specific privileges granted to the OWNER RIGHTS SID. +
+
+ Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html +
+ + ) +}; + +export default WindowsAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx index 064900ff47..c00c4f1157 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx @@ -26,9 +26,25 @@ const General: FC = ({ sourceName, sourceType, targetName, target {groupSpecialFormat(sourceType, sourceName)} the ability to modify the owner of the{' '} {typeFormat(targetType)} {targetName}. + - Object owners retain the ability to modify object security descriptors, regardless of permissions on the - object's DACL. + Implicit owner rights are not blocked and are therefore abusable via change in ownership when the following + conditions are met: +
    +
  • + Inheritance is not configured for any privileges explicitly granted to the OWNER RIGHTS SID (S-1-3-4). + Non-inherited privileges granted to OWNER RIGHTS are removed when the owner is changed, allowing the + new owner to have the full set of implicit owner rights. +
  • +
  • + The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is defined in + the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, implicit owner rights are not blocked. +
  • +
  • + The object is not a computer or a derivative of a computer object (e.g., MSA, GMSA). +
  • +
+
); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/References.tsx index 9ce4b4645e..aa56b5f991 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/References.tsx @@ -99,6 +99,34 @@ const References: FC = () => { href='https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53'> https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53 +
+ + https://support.microsoft.com/en-us/topic/kb5008383-active-directory-permissions-updates-cve-2021-42291-536d5555-ffba-4248-a60e-d6cbc849cde1 + +
+ + https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/fb7c101d-ec8b-4fbf-bca8-7d7c2d747d0c + +
+ + https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 + +
+ + https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers + ); }; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx new file mode 100644 index 0000000000..b2f0180833 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx @@ -0,0 +1,34 @@ +// 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 + +import { FC } from 'react'; +import { EdgeInfoProps } from '../index'; +import { Typography } from '@mui/material'; + +const General: FC = ({ sourceName, sourceType }) => { + return ( + <> + + When specific privileges on an object's DACL are explicitly granted to the "OWNER RIGHTS" SID (S-1-3-4), + and inheritance is configured for those permissions, they are inherited by the new object owner after a + change in ownership. In this case, implicit owner rights are blocked, and the new owner is granted only + the specific inherited privileges granted to OWNER RIGHTS. + + + ); +}; + +export default General; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx new file mode 100644 index 0000000000..b24acb174b --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx @@ -0,0 +1,34 @@ +// 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 + +import { FC } from 'react'; +import { Typography } from '@mui/material'; + +const LinuxAbuse: FC = () => { + return ( + <> + + To abuse change in ownership of an object where the OWNER RIGHTS SID is explicitly granted inherited privileges, + you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID in the context of the new owner. +
+
+ Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html +
+ + ) +}; + +export default LinuxAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx new file mode 100644 index 0000000000..52aa406e4b --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx @@ -0,0 +1,30 @@ +// 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 + +import { FC } from 'react'; +import { Typography } from '@mui/material'; + +const Opsec: FC = () => { + return ( + <> + + Please refer to the OPSEC info for the specific granted permissions at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + + + ) +}; + +export default Opsec; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx new file mode 100644 index 0000000000..c8be0b6709 --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx @@ -0,0 +1,34 @@ +// 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 + +import { FC } from 'react'; +import { Link, Box } from '@mui/material'; + +const References: FC = () => { + return ( + + + https://www.hub.trimarcsecurity.com/post/trimarc-whitepaper-owner-or-pwnd + +
+ + https://github.com/JimSycurity/OwnerOrPwned + +
+ ); +}; + +export default References; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx new file mode 100644 index 0000000000..025ca3c82f --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx @@ -0,0 +1,34 @@ +// 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 + +import { FC } from 'react'; +import { Typography } from '@mui/material'; + +const WindowsAbuse: FC = () => { + return ( + <> + + To abuse change in ownership of an object where the OWNER RIGHTS SID is explicitly granted inherited privileges, + you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID in the context of the new owner. +
+
+ Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html +
+ + ) +}; + +export default WindowsAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WriteOwnerLimitedRights.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WriteOwnerLimitedRights.tsx new file mode 100644 index 0000000000..518fa8469a --- /dev/null +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WriteOwnerLimitedRights.tsx @@ -0,0 +1,31 @@ +// 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 + +import General from './General'; +import WindowsAbuse from './WindowsAbuse'; +import LinuxAbuse from './LinuxAbuse'; +import Opsec from './Opsec'; +import References from './References'; + +const WriteOwnerLimitedRights = { + general: General, + windowsAbuse: WindowsAbuse, + linuxAbuse: LinuxAbuse, + opsec: Opsec, + references: References, +}; + +export default WriteOwnerLimitedRights; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx index 299ce6ff19..eca2bb5d53 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/index.tsx @@ -93,6 +93,7 @@ import MemberOf from './MemberOf/MemberOf'; import NTAuthStoreFor from './NTAuthStoreFor/NTAuthStoreFor'; import OIDGroupLink from './OIDGroupLink/OIDGroupLink'; import Owns from './Owns/Owns'; +import OwnsLimitedRights from './OwnsLimitedRights/OwnsLimitedRights'; import PublishedTo from './PublishedTo/PublishedTo'; import ReadGMSAPassword from './ReadGMSAPassword/ReadGMSAPassword'; import ReadLAPSPassword from './ReadLAPSPassword/ReadLAPSPassword'; @@ -107,6 +108,7 @@ import WriteAccountRestrictions from './WriteAccountRestrictions/WriteAccountRes import WriteGPLink from './WriteGPLink/WriteGPLink'; import WriteDacl from './WriteDacl/WriteDacl'; import WriteOwner from './WriteOwner/WriteOwner'; +import WriteOwnerLimitedRights from './WriteOwnerLimitedRights/WriteOwnerLimitedRights'; import WritePKIEnrollmentFlag from './WritePKIEnrollmentFlag/WritePKIEnrollmentFlag'; import WritePKINameFlag from './WritePKINameFlag/WritePKINameFlag'; import WriteSPN from './WriteSPN/WriteSPN'; @@ -141,8 +143,10 @@ const EdgeInfoComponents = { ForceChangePassword: ForceChangePassword, GenericWrite: GenericWrite, Owns: Owns, + OwnsLimitedRights: OwnsLimitedRights, WriteDacl: WriteDacl, WriteOwner: WriteOwner, + WriteOwnerLimitedRights: WriteOwnerLimitedRights, CanRDP: CanRDP, ExecuteDCOM: ExecuteDCOM, AllowedToDelegate: AllowedToDelegate, From d5d421b8970c12173ebdb4a77de82440ed9d058f Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Fri, 15 Nov 2024 13:29:38 -0500 Subject: [PATCH 13/33] Fallback to add Owns/WriteOwner if fetching dSHeuristics/Admins fails --- packages/go/analysis/ad/owns.go | 100 +++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 6bdbbb904b..5bdb94db92 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -32,11 +32,6 @@ import ( ) func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { - if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) - } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) - } else { operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting abusable explicit permissions to OWNER RIGHTS @@ -49,6 +44,39 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { + // Get the dSHeuristics values for all domains + if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { + + // If we fail to get the dSHeuristics values, add the Owns edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + + // Get the admin group IDs + } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { + + // If we fail to get the admin group IDs, add the Owns edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + } + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) if anyEnforced { @@ -125,6 +153,39 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { + // Get the dSHeuristics values for all domains + if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { + + // If we fail to get the dSHeuristics values, add the WriteOwner edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + + // Get the admin group IDs + } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { + + // If we fail to get the admin group IDs, add the Owns edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + } + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) if anyEnforced { @@ -154,21 +215,20 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { - // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) - continue - - } else if !isComputerDerived { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - // If the target node is NOT a computer or derived object, add the WriteOwner edge - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err == nil { + if !isComputerDerived { + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + // If the target node is NOT a computer or derived object, add the WriteOwner edge + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } } } } From b64cafabd7b079ca88304467b83047364e10356c Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Fri, 15 Nov 2024 13:30:04 -0500 Subject: [PATCH 14/33] Update docs URL --- .../bh-shared-ui/src/components/HelpTexts/Owns/General.tsx | 6 +++--- .../components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx | 2 +- .../components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx | 2 +- .../HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx | 2 +- .../HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx index 17064af9eb..591a8b63e5 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx @@ -32,14 +32,14 @@ const General: FC = ({ sourceName, sourceType, targetName, target
  • - The OWNER RIGHTS SID (S-1-3-4) is NOT explicitly granted privileges on the object + The OWNER RIGHTS SID (S-1-3-4) is not explicitly granted privileges on the object

  • OR

  • - Implicit owner rights are NOT blocked + Implicit owner rights are not blocked
@@ -57,7 +57,7 @@ const General: FC = ({ sourceName, sourceType, targetName, target

  • - The object is NOT a computer or derivative of a computer object (e.g., MSA, GMSA) + The object is not a computer or derivative of a computer object (e.g., MSA, GMSA)

  • OR diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx index 805c18518a..6411161c8e 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx @@ -26,7 +26,7 @@ const LinuxAbuse: FC = () => { - Please refer to the abuse info for the specific granted permissions at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + Please refer to the abuse info for the specific granted permissions at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges ) diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx index d51492a93c..cecba685dd 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx @@ -25,7 +25,7 @@ const WindowsAbuse: FC = () => { you can abuse the specific privileges granted to the OWNER RIGHTS SID.

    - Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges ) diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx index b24acb174b..31b465142d 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx @@ -25,7 +25,7 @@ const LinuxAbuse: FC = () => { you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID in the context of the new owner.

    - Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges ) diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx index 025ca3c82f..7f197e84e0 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx @@ -25,7 +25,7 @@ const WindowsAbuse: FC = () => { you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID in the context of the new owner.

    - Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges ) From 0a01818a750885e83fb5c4ad3633bef404b7e0b2 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Fri, 15 Nov 2024 16:38:25 -0500 Subject: [PATCH 15/33] Initial harnesses for Owns/WriteOwner changes --- cmd/api/src/test/integration/harnesses.go | 376 ++++++++++++++++++++++ 1 file changed, 376 insertions(+) diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index 637aebc7c0..2c73a696fc 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -8402,6 +8402,382 @@ func (s *ESC10bHarnessDC2) Setup(graphTestContext *GraphTestContext) { graphTestContext.UpdateNode(s.DC1) } +type OwnsWriteOwner struct { + + // Domain 1 + Domain1_BlockImplicitOwnerRights *graph.Node + + //// Object owners + Domain1_User1_Owner *graph.Node + Domain1_User2_DomainAdmin *graph.Node + Domain1_User3_EnterpriseAdmin *graph.Node + Domain1_User4_WriteOwner *graph.Node + Domain1_Group1_DomainAdmins *graph.Node + Domain1_Group2_EnterpriseAdmins *graph.Node + + //// Owned objects + Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_Computer2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_Computer3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_Computer4_OwnerRightsNotInherited *graph.Node + Domain1_Computer5_OwnerRightsInherited *graph.Node + + Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_MSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_MSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_MSA4_OwnerRightsNotInherited *graph.Node + Domain1_MSA5_OwnerRightsInherited *graph.Node + + Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_GMSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_GMSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_GMSA4_OwnerRightsNotInherited *graph.Node + Domain1_GMSA5_OwnerRightsInherited *graph.Node + + Domain1_User5_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_User6_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_User7_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_User8_OwnerRightsNotInherited *graph.Node + Domain1_User9_OwnerRightsInherited *graph.Node + + // Domain 2 + Domain2_DoNotBlockImplicitOwnerRights *graph.Node + + //// Object owners + Domain2_User1_Owner *graph.Node + Domain2_User2_DomainAdmin *graph.Node + Domain2_User3_EnterpriseAdmin *graph.Node + Domain2_User4_WriteOwner *graph.Node + Domain2_Group1_DomainAdmins *graph.Node + Domain2_Group2_EnterpriseAdmins *graph.Node + + //// Owned objects + Domain2_Computer1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain2_Computer2_NoOwnerRights_OwnerIsDA *graph.Node + Domain2_Computer3_NoOwnerRights_OwnerIsEA *graph.Node + Domain2_Computer4_OwnerRightsNotInherited *graph.Node + Domain2_Computer5_OwnerRightsInherited *graph.Node + + Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain2_MSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain2_MSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain2_MSA4_OwnerRightsNotInherited *graph.Node + Domain2_MSA5_OwnerRightsInherited *graph.Node + + Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain2_GMSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain2_GMSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain2_GMSA4_OwnerRightsNotInherited *graph.Node + Domain2_GMSA5_OwnerRightsInherited *graph.Node + + Domain2_User5_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain2_User6_NoOwnerRights_OwnerIsDA *graph.Node + Domain2_User7_NoOwnerRights_OwnerIsEA *graph.Node + Domain2_User8_OwnerRightsNotInherited *graph.Node + Domain2_User9_OwnerRightsInherited *graph.Node +} + +func (s *OwnsWriteOwner) Setup(graphTestContext *GraphTestContext) { + domainSid := RandomDomainSID() + + // Domain 1 + s.Domain1_BlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain1_BlockImplicitOwnerRights", domainSid, false, true) + s.Domain1_BlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000001") + + //// Object owners + s.Domain1_User1_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) + s.Domain1_User2_DomainAdmin = graphTestContext.NewActiveDirectoryUser("Domain Admin User", domainSid) + s.Domain1_User3_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("Enterprise Admin User", domainSid) + s.Domain1_User4_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) + + ////// Add the Domain Admins group and member + s.Domain1_Group1_DomainAdmins = graphTestContext.NewActiveDirectoryGroup("Domain Admins", domainSid) + s.Domain1_Group1_DomainAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-512") + graphTestContext.NewRelationship(s.Domain1_Group1_DomainAdmins, s.Domain1_User2_DomainAdmin, ad.MemberOf) + + ////// Add the Enterprise Admins group and member + s.Domain1_Group2_EnterpriseAdmins = graphTestContext.NewActiveDirectoryGroup("Enterprise Admins", domainSid) + s.Domain1_Group2_EnterpriseAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-519") + graphTestContext.NewRelationship(s.Domain1_Group2_EnterpriseAdmins, s.Domain1_User3_EnterpriseAdmin, ad.MemberOf) + + //// Owned objects + + ////// Computers + s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain1_Computer2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("Computer2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_Computer2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain1_Computer3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("Computer3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_Computer3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain1_Computer4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_OwnerRightsNotInherited", domainSid) + s.Domain1_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_Computer4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_Computer5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OwnerRightsInherited", domainSid) + s.Domain1_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_Computer5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_Computer5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + ////// MSAs + s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.MSA.String(), true) + s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain1_MSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("MSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.MSA.String(), true) + s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain1_MSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("MSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.MSA.String(), true) + s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain1_MSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_OwnerRightsNotInherited", domainSid) + s.Domain1_MSA4_OwnerRightsNotInherited.Properties.Set(ad.MSA.String(), true) + s.Domain1_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_MSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_MSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_OwnerRightsInherited", domainSid) + s.Domain1_MSA5_OwnerRightsInherited.Properties.Set(ad.MSA.String(), true) + s.Domain1_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_MSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_MSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + ////// GMSAs + s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.GMSA.String(), true) + s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("GMSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.GMSA.String(), true) + s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("GMSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.GMSA.String(), true) + s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain1_GMSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_OwnerRightsNotInherited", domainSid) + s.Domain1_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.GMSA.String(), true) + s.Domain1_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_GMSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_GMSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_OwnerRightsInherited", domainSid) + s.Domain1_GMSA5_OwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) + s.Domain1_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_GMSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_GMSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + ////// Users + s.Domain1_User5_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User5_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_User5_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain1_User6_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User6_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_User6_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_User6_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain1_User7_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User7_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_User7_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_User7_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain1_User8_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryUser("User6_OwnerRightsNotInherited", domainSid) + s.Domain1_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_User8_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_User9_OwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User7_OwnerRightsInherited", domainSid) + s.Domain1_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_User9_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_User9_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + // Domain 2 + s.Domain2_DoNotBlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain2_DoNotBlockImplicitOwnerRights", domainSid, false, true) + s.Domain2_DoNotBlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000000") + + //// Object owners + s.Domain2_User1_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) + s.Domain2_User2_DomainAdmin = graphTestContext.NewActiveDirectoryUser("Domain Admin User", domainSid) + s.Domain2_User3_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("Enterprise Admin User", domainSid) + s.Domain2_User4_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) + + ////// Add the Domain Admins group and member + s.Domain2_Group1_DomainAdmins = graphTestContext.NewActiveDirectoryGroup("Domain Admins", domainSid) + s.Domain2_Group1_DomainAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-512") + graphTestContext.NewRelationship(s.Domain2_Group1_DomainAdmins, s.Domain2_User2_DomainAdmin, ad.MemberOf) + + ////// Add the Enterprise Admins group and member + s.Domain2_Group2_EnterpriseAdmins = graphTestContext.NewActiveDirectoryGroup("Enterprise Admins", domainSid) + s.Domain2_Group2_EnterpriseAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-519") + graphTestContext.NewRelationship(s.Domain2_Group2_EnterpriseAdmins, s.Domain2_User3_EnterpriseAdmin, ad.MemberOf) + + //// Owned objects + + ////// Computers + s.Domain2_Computer1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain2_Computer1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain2_Computer2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("Computer2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain2_Computer2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_Computer2_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain2_Computer3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("Computer3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain2_Computer3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_Computer3_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain2_Computer4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_OwnerRightsNotInherited", domainSid) + s.Domain2_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain2_Computer5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OwnerRightsInherited", domainSid) + s.Domain2_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + ////// MSAs + s.Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.MSA.String(), true) + s.Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain2_MSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("MSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain2_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.MSA.String(), true) + s.Domain2_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_MSA2_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain2_MSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("MSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain2_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.MSA.String(), true) + s.Domain2_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_MSA3_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain2_MSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_OwnerRightsNotInherited", domainSid) + s.Domain2_MSA4_OwnerRightsNotInherited.Properties.Set(ad.MSA.String(), true) + s.Domain2_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_MSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain2_MSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_OwnerRightsInherited", domainSid) + s.Domain2_MSA5_OwnerRightsInherited.Properties.Set(ad.MSA.String(), true) + s.Domain2_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_MSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_MSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + ////// GMSAs + s.Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.GMSA.String(), true) + s.Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("GMSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.GMSA.String(), true) + s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("GMSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.GMSA.String(), true) + s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain2_GMSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_OwnerRightsNotInherited", domainSid) + s.Domain2_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.GMSA.String(), true) + s.Domain2_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_GMSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain2_GMSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_OwnerRightsInherited", domainSid) + s.Domain2_GMSA5_OwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) + s.Domain2_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_GMSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_GMSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + ////// Users + s.Domain2_User5_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User5_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain2_User5_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + + s.Domain2_User6_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User6_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain2_User6_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_User6_NoOwnerRights_OwnerIsDA, ad.Owns) + + s.Domain2_User7_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User7_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain2_User7_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_User7_NoOwnerRights_OwnerIsEA, ad.Owns) + + s.Domain2_User8_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryUser("User6_OwnerRightsNotInherited", domainSid) + s.Domain2_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_User8_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain2_User9_OwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User7_OwnerRightsInherited", domainSid) + s.Domain2_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_User9_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_User9_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + +} + type HarnessDetails struct { RDP RDPHarness RDPB RDPHarness2 From c3f1808b5a545a9051080ed051259ff9b671c4d8 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Mon, 18 Nov 2024 09:29:43 -0500 Subject: [PATCH 16/33] Remove unused variable --- packages/go/analysis/ad/owns.go | 160 +++++++++++++++----------------- 1 file changed, 73 insertions(+), 87 deletions(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 5bdb94db92..b82e020e09 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -32,51 +32,52 @@ import ( ) func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { - operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") - - // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting abusable explicit permissions to OWNER RIGHTS - operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - return tx.Relationships().Filter( - query.And( - query.Kind(query.Relationship(), ad.OwnsRaw), - query.Kind(query.Start(), ad.Entity), - ), - ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { - for rel := range cursor.Chan() { - - // Get the dSHeuristics values for all domains - if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { - - // If we fail to get the dSHeuristics values, add the Owns edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") + + // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting abusable explicit permissions to OWNER RIGHTS + operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + return tx.Relationships().Filter( + query.And( + query.Kind(query.Relationship(), ad.OwnsRaw), + query.Kind(query.Start(), ad.Entity), + ), + ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { + for rel := range cursor.Chan() { + + // Get the dSHeuristics values for all domains + if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { + + // If we fail to get the dSHeuristics values, add the Owns edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) // Get the admin group IDs - } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { + } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { - // If we fail to get the admin group IDs, add the Owns edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + // If we fail to get the admin group IDs, add the Owns edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - + return fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + + } else { + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) if anyEnforced { @@ -138,54 +139,39 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } } } + } - return cursor.Error() - }) + return cursor.Error() }) + }) - // Get all source nodes of WriteOwner ACEs where the target node has no ACEs granting explicit abusable permissions to OWNER RIGHTS - operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - return tx.Relationships().Filter( - query.And( - query.Kind(query.Relationship(), ad.WriteOwnerRaw), - query.Kind(query.Start(), ad.Entity), - ), - ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { - for rel := range cursor.Chan() { - - // Get the dSHeuristics values for all domains - if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { - - // If we fail to get the dSHeuristics values, add the WriteOwner edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + // Get all source nodes of WriteOwner ACEs where the target node has no ACEs granting explicit abusable permissions to OWNER RIGHTS + operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + return tx.Relationships().Filter( + query.And( + query.Kind(query.Relationship(), ad.WriteOwnerRaw), + query.Kind(query.Start(), ad.Entity), + ), + ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { + for rel := range cursor.Chan() { - // Get the admin group IDs - } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { + // Get the dSHeuristics values for all domains + if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { - // If we fail to get the admin group IDs, add the Owns edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, - } - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + // If we fail to get the dSHeuristics values, add the WriteOwner edge and return an error + isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + } + return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + } else { // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) if anyEnforced { @@ -215,7 +201,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err == nil { if !isComputerDerived { isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() @@ -246,13 +232,13 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } } } + } - return cursor.Error() - }) + return cursor.Error() }) + }) - return &operation.Stats, operation.Done() - } + return &operation.Stats, operation.Done() } func isTargetNodeComputerDerived(node *graph.Node) (bool, error) { From 5b22a332c07b9b614ff1198fffdc51bfb1a78143 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Mon, 18 Nov 2024 15:53:35 -0500 Subject: [PATCH 17/33] Accounted for abusable permissions that are deleted on owner change --- packages/go/ein/ad.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 12a19fdece..0a93cfa80e 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -304,6 +304,23 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target }, )) } + } else { + // If there are abusable permissions granted to the OWNER RIGHTS SID, but they are not inherited, + // they will be deleted on ownership change and WriteOwner may be abusable, so create a WriteOwnerRaw + // edge for post-processing so we can check BlockOwnerImplicitRights enforcement and object type + for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { + converted = append(converted, NewIngestibleRelationship( + limitedPrincipal.SourceData, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited}, + RelType: ad.WriteOwnerRaw, + }, + )) + } } } else { From e15faee6835cf52b11ed87dd9eefbf8daf557ed1 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Mon, 18 Nov 2024 16:14:09 -0500 Subject: [PATCH 18/33] Update Owns and WriteOwner setup harness --- cmd/api/src/test/integration/harnesses.go | 485 +++++++++++----------- 1 file changed, 236 insertions(+), 249 deletions(-) diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index 2c73a696fc..cb2201f254 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -8408,73 +8408,64 @@ type OwnsWriteOwner struct { Domain1_BlockImplicitOwnerRights *graph.Node //// Object owners - Domain1_User1_Owner *graph.Node - Domain1_User2_DomainAdmin *graph.Node - Domain1_User3_EnterpriseAdmin *graph.Node - Domain1_User4_WriteOwner *graph.Node + Domain1_User101_Owner *graph.Node + Domain1_User102_DomainAdmin *graph.Node + Domain1_User103_EnterpriseAdmin *graph.Node + Domain1_User104_WriteOwner *graph.Node Domain1_Group1_DomainAdmins *graph.Node Domain1_Group2_EnterpriseAdmins *graph.Node //// Owned objects - Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain1_Computer2_NoOwnerRights_OwnerIsDA *graph.Node - Domain1_Computer3_NoOwnerRights_OwnerIsEA *graph.Node - Domain1_Computer4_OwnerRightsNotInherited *graph.Node - Domain1_Computer5_OwnerRightsInherited *graph.Node - - Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain1_MSA2_NoOwnerRights_OwnerIsDA *graph.Node - Domain1_MSA3_NoOwnerRights_OwnerIsEA *graph.Node - Domain1_MSA4_OwnerRightsNotInherited *graph.Node - Domain1_MSA5_OwnerRightsInherited *graph.Node - - Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain1_GMSA2_NoOwnerRights_OwnerIsDA *graph.Node - Domain1_GMSA3_NoOwnerRights_OwnerIsEA *graph.Node - Domain1_GMSA4_OwnerRightsNotInherited *graph.Node - Domain1_GMSA5_OwnerRightsInherited *graph.Node - - Domain1_User5_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain1_User6_NoOwnerRights_OwnerIsDA *graph.Node - Domain1_User7_NoOwnerRights_OwnerIsEA *graph.Node - Domain1_User8_OwnerRightsNotInherited *graph.Node - Domain1_User9_OwnerRightsInherited *graph.Node + Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_Computer2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_Computer3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_Computer4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_Computer5_AbusableOwnerRightsInherited *graph.Node + Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_Computer8_OnlyNonabusableOwnerRightsInherited *graph.Node + + Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_MSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_MSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_MSA4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_MSA5_AbusableOwnerRightsInherited *graph.Node + Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_MSA8_OnlyNonabusableOwnerRightsInherited *graph.Node + + Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_GMSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_GMSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_GMSA4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_GMSA5_AbusableOwnerRightsInherited *graph.Node + Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited *graph.Node + + Domain1_User1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_User2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_User3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_User4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_User5_AbusableOwnerRightsInherited *graph.Node + Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_User8_OnlyNonabusableOwnerRightsInherited *graph.Node // Domain 2 Domain2_DoNotBlockImplicitOwnerRights *graph.Node //// Object owners - Domain2_User1_Owner *graph.Node - Domain2_User2_DomainAdmin *graph.Node - Domain2_User3_EnterpriseAdmin *graph.Node - Domain2_User4_WriteOwner *graph.Node - Domain2_Group1_DomainAdmins *graph.Node - Domain2_Group2_EnterpriseAdmins *graph.Node + Domain2_User1_Owner *graph.Node + Domain2_User2_WriteOwner *graph.Node //// Owned objects - Domain2_Computer1_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain2_Computer2_NoOwnerRights_OwnerIsDA *graph.Node - Domain2_Computer3_NoOwnerRights_OwnerIsEA *graph.Node - Domain2_Computer4_OwnerRightsNotInherited *graph.Node - Domain2_Computer5_OwnerRightsInherited *graph.Node - - Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain2_MSA2_NoOwnerRights_OwnerIsDA *graph.Node - Domain2_MSA3_NoOwnerRights_OwnerIsEA *graph.Node - Domain2_MSA4_OwnerRightsNotInherited *graph.Node - Domain2_MSA5_OwnerRightsInherited *graph.Node - - Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain2_GMSA2_NoOwnerRights_OwnerIsDA *graph.Node - Domain2_GMSA3_NoOwnerRights_OwnerIsEA *graph.Node - Domain2_GMSA4_OwnerRightsNotInherited *graph.Node - Domain2_GMSA5_OwnerRightsInherited *graph.Node - - Domain2_User5_NoOwnerRights_OwnerIsLowPriv *graph.Node - Domain2_User6_NoOwnerRights_OwnerIsDA *graph.Node - Domain2_User7_NoOwnerRights_OwnerIsEA *graph.Node - Domain2_User8_OwnerRightsNotInherited *graph.Node - Domain2_User9_OwnerRightsInherited *graph.Node + Domain2_Computer1_NoOwnerRights *graph.Node + Domain2_Computer2_AbusableOwnerRightsNoneInherited *graph.Node + Domain2_Computer3_AbusableOwnerRightsInherited *graph.Node + Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain2_Computer6_OnlyNonabusableOwnerRightsInherited *graph.Node } func (s *OwnsWriteOwner) Setup(graphTestContext *GraphTestContext) { @@ -8485,148 +8476,238 @@ func (s *OwnsWriteOwner) Setup(graphTestContext *GraphTestContext) { s.Domain1_BlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000001") //// Object owners - s.Domain1_User1_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) - s.Domain1_User2_DomainAdmin = graphTestContext.NewActiveDirectoryUser("Domain Admin User", domainSid) - s.Domain1_User3_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("Enterprise Admin User", domainSid) - s.Domain1_User4_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) + s.Domain1_User101_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) + s.Domain1_User102_DomainAdmin = graphTestContext.NewActiveDirectoryUser("Domain Admin User", domainSid) + s.Domain1_User103_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("Enterprise Admin User", domainSid) + s.Domain1_User104_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) ////// Add the Domain Admins group and member s.Domain1_Group1_DomainAdmins = graphTestContext.NewActiveDirectoryGroup("Domain Admins", domainSid) s.Domain1_Group1_DomainAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-512") - graphTestContext.NewRelationship(s.Domain1_Group1_DomainAdmins, s.Domain1_User2_DomainAdmin, ad.MemberOf) + graphTestContext.NewRelationship(s.Domain1_Group1_DomainAdmins, s.Domain1_User102_DomainAdmin, ad.MemberOf) ////// Add the Enterprise Admins group and member s.Domain1_Group2_EnterpriseAdmins = graphTestContext.NewActiveDirectoryGroup("Enterprise Admins", domainSid) s.Domain1_Group2_EnterpriseAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-519") - graphTestContext.NewRelationship(s.Domain1_Group2_EnterpriseAdmins, s.Domain1_User3_EnterpriseAdmin, ad.MemberOf) + graphTestContext.NewRelationship(s.Domain1_Group2_EnterpriseAdmins, s.Domain1_User103_EnterpriseAdmin, ad.MemberOf) //// Owned objects ////// Computers s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights_OwnerIsLowPriv", domainSid) s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_Computer2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("Computer2_NoOwnerRights_OwnerIsDA", domainSid) s.Domain1_Computer2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.Owns) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_Computer3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("Computer3_NoOwnerRights_OwnerIsEA", domainSid) s.Domain1_Computer3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.Owns) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) - s.Domain1_Computer4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_OwnerRightsNotInherited", domainSid) - s.Domain1_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_Computer4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_Computer4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_Computer4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) - s.Domain1_Computer5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OwnerRightsInherited", domainSid) - s.Domain1_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_Computer5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_Computer5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_Computer5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_Computer5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + + s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + ////// MSAs s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_MSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("MSA2_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.Owns) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_MSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("MSA3_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.Owns) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) - s.Domain1_MSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_OwnerRightsNotInherited", domainSid) - s.Domain1_MSA4_OwnerRightsNotInherited.Properties.Set(ad.MSA.String(), true) - s.Domain1_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_MSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_MSA4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_MSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) - s.Domain1_MSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_OwnerRightsInherited", domainSid) - s.Domain1_MSA5_OwnerRightsInherited.Properties.Set(ad.MSA.String(), true) - s.Domain1_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_MSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_MSA5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_MSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_MSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("MSA6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + + s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + ////// GMSAs s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("GMSA2_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.Owns) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("GMSA3_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.Owns) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) - s.Domain1_GMSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_OwnerRightsNotInherited", domainSid) - s.Domain1_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.GMSA.String(), true) - s.Domain1_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_GMSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) - s.Domain1_GMSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_OwnerRightsInherited", domainSid) - s.Domain1_GMSA5_OwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) - s.Domain1_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_GMSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_GMSA5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_GMSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_GMSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - ////// Users - s.Domain1_User5_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User5_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain1_User5_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("GMSA6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) - s.Domain1_User6_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User6_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain1_User6_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User2_DomainAdmin, s.Domain1_User6_NoOwnerRights_OwnerIsDA, ad.Owns) + s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) - s.Domain1_User7_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User7_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain1_User7_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User3_EnterpriseAdmin, s.Domain1_User7_NoOwnerRights_OwnerIsEA, ad.Owns) + s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) - s.Domain1_User8_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryUser("User6_OwnerRightsNotInherited", domainSid) - s.Domain1_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_User8_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ////// Users + s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + + s.Domain1_User2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_User2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + + s.Domain1_User3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_User3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + + s.Domain1_User4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryUser("User4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_User4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) - s.Domain1_User9_OwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User7_OwnerRightsInherited", domainSid) - s.Domain1_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain1_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_User9_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain1_User5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_User5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User1_Owner, s.Domain1_User9_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + + s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryUser("User6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + + s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryUser("User7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_User8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_User8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain1_User8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) // Domain 2 s.Domain2_DoNotBlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain2_DoNotBlockImplicitOwnerRights", domainSid, false, true) @@ -8634,148 +8715,53 @@ func (s *OwnsWriteOwner) Setup(graphTestContext *GraphTestContext) { //// Object owners s.Domain2_User1_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) - s.Domain2_User2_DomainAdmin = graphTestContext.NewActiveDirectoryUser("Domain Admin User", domainSid) - s.Domain2_User3_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("Enterprise Admin User", domainSid) - s.Domain2_User4_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) - - ////// Add the Domain Admins group and member - s.Domain2_Group1_DomainAdmins = graphTestContext.NewActiveDirectoryGroup("Domain Admins", domainSid) - s.Domain2_Group1_DomainAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-512") - graphTestContext.NewRelationship(s.Domain2_Group1_DomainAdmins, s.Domain2_User2_DomainAdmin, ad.MemberOf) - - ////// Add the Enterprise Admins group and member - s.Domain2_Group2_EnterpriseAdmins = graphTestContext.NewActiveDirectoryGroup("Enterprise Admins", domainSid) - s.Domain2_Group2_EnterpriseAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-519") - graphTestContext.NewRelationship(s.Domain2_Group2_EnterpriseAdmins, s.Domain2_User3_EnterpriseAdmin, ad.MemberOf) + s.Domain2_User2_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) //// Owned objects ////// Computers - s.Domain2_Computer1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain2_Computer1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - - s.Domain2_Computer2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("Computer2_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain2_Computer2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_Computer2_NoOwnerRights_OwnerIsDA, ad.Owns) - - s.Domain2_Computer3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("Computer3_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain2_Computer3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_Computer3_NoOwnerRights_OwnerIsEA, ad.Owns) - - s.Domain2_Computer4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_OwnerRightsNotInherited", domainSid) - s.Domain2_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_Computer4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain2_Computer1_NoOwnerRights = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights", domainSid) + s.Domain2_Computer1_NoOwnerRights.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer1_NoOwnerRights, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer1_NoOwnerRights, ad.WriteOwnerRaw) + + s.Domain2_Computer2_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer2_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain2_Computer2_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer2_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer2_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer2_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) - s.Domain2_Computer5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OwnerRightsInherited", domainSid) - s.Domain2_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_Computer5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain2_Computer3_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer3_AbusableOwnerRightsInherited", domainSid) + s.Domain2_Computer3_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer3_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - ////// MSAs - s.Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.MSA.String(), true) - s.Domain2_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - - s.Domain2_MSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("MSA2_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain2_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.MSA.String(), true) - s.Domain2_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_MSA2_NoOwnerRights_OwnerIsDA, ad.Owns) - - s.Domain2_MSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("MSA3_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain2_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.MSA.String(), true) - s.Domain2_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_MSA3_NoOwnerRights_OwnerIsEA, ad.Owns) - - s.Domain2_MSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_OwnerRightsNotInherited", domainSid) - s.Domain2_MSA4_OwnerRightsNotInherited.Properties.Set(ad.MSA.String(), true) - s.Domain2_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_MSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_MSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - - s.Domain2_MSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_OwnerRightsInherited", domainSid) - s.Domain2_MSA5_OwnerRightsInherited.Properties.Set(ad.MSA.String(), true) - s.Domain2_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_MSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_MSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_MSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - - ////// GMSAs - s.Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.GMSA.String(), true) - s.Domain2_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - - s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("GMSA2_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.GMSA.String(), true) - s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_GMSA2_NoOwnerRights_OwnerIsDA, ad.Owns) - - s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("GMSA3_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.GMSA.String(), true) - s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_GMSA3_NoOwnerRights_OwnerIsEA, ad.Owns) - - s.Domain2_GMSA4_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_OwnerRightsNotInherited", domainSid) - s.Domain2_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.GMSA.String(), true) - s.Domain2_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_GMSA4_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_GMSA4_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - - s.Domain2_GMSA5_OwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_OwnerRightsInherited", domainSid) - s.Domain2_GMSA5_OwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) - s.Domain2_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_GMSA5_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_GMSA5_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_GMSA5_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - - ////// Users - s.Domain2_User5_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User5_NoOwnerRights_OwnerIsLowPriv", domainSid) - s.Domain2_User5_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) - s.Domain2_User6_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User6_NoOwnerRights_OwnerIsDA", domainSid) - s.Domain2_User6_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User2_DomainAdmin, s.Domain2_User6_NoOwnerRights_OwnerIsDA, ad.Owns) - - s.Domain2_User7_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User7_NoOwnerRights_OwnerIsEA", domainSid) - s.Domain2_User7_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User3_EnterpriseAdmin, s.Domain2_User7_NoOwnerRights_OwnerIsEA, ad.Owns) - - s.Domain2_User8_OwnerRightsNotInherited = graphTestContext.NewActiveDirectoryUser("User6_OwnerRightsNotInherited", domainSid) - s.Domain2_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_User8_OwnerRightsNotInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_User8_OwnerRightsNotInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - - s.Domain2_User9_OwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User7_OwnerRightsInherited", domainSid) - s.Domain2_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) - s.Domain2_User9_OwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_User9_OwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_User9_OwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ - ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, - })) + s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) + s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) } type HarnessDetails struct { @@ -8876,4 +8862,5 @@ type HarnessDetails struct { DCSyncHarness DCSyncHarness SyncLAPSPasswordHarness SyncLAPSPasswordHarness HybridAttackPaths HybridAttackPaths + OwnsWriteOwner OwnsWriteOwner } From d5dbb9595ceb540f04757f23a1c6b7f616cc75f1 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Tue, 19 Nov 2024 15:43:03 -0500 Subject: [PATCH 19/33] Confirmed harness data is accurate and is processed as expected by PostOwnsWriteOwner --- .../src/analysis/ad/ad_integration_test.go | 32 ++++ cmd/api/src/test/integration/harnesses.go | 162 ++++++++++++------ packages/go/analysis/ad/owns.go | 2 +- 3 files changed, 141 insertions(+), 55 deletions(-) diff --git a/cmd/api/src/analysis/ad/ad_integration_test.go b/cmd/api/src/analysis/ad/ad_integration_test.go index d42521ca19..60b8921e59 100644 --- a/cmd/api/src/analysis/ad/ad_integration_test.go +++ b/cmd/api/src/analysis/ad/ad_integration_test.go @@ -1115,3 +1115,35 @@ func TestDCSync(t *testing.T) { } }) } + +func TestOwnsWriteOwner(t *testing.T) { + testContext := integration.NewGraphTestContext(t, schema.DefaultGraphSchema()) + + testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error { + harness.OwnsWriteOwner.Setup(testContext) + // To verify in Neo4j: MATCH (n:Computer) MATCH (u:User) RETURN n, u + return nil + }, func(harness integration.HarnessDetails, db graph.Database) { + if groupExpansions, err := adAnalysis.ExpandAllRDPLocalGroups(testContext.Context(), db); err != nil { + t.Fatalf("error expanding groups in integration test; %v", err) + } else if _, err := adAnalysis.PostOwnsAndWriteOwner(testContext.Context(), db, groupExpansions); err != nil { + t.Fatalf("error creating Owns/WriteOwner edges in integration test; %v", err) + } else { + db.ReadTransaction(context.Background(), func(tx graph.Transaction) error { + + // Owns + if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.Owns), + query.Kind(query.Start(), ad.Entity), + ) + })); err != nil { + t.Fatalf("error fetching Owns edges in integration test; %v", err) + } else { + require.Equal(t, 10, len(results)) + } + return nil + }) + } + }) +} diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index cb2201f254..5f3b8a0b80 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -8474,260 +8474,311 @@ func (s *OwnsWriteOwner) Setup(graphTestContext *GraphTestContext) { // Domain 1 s.Domain1_BlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain1_BlockImplicitOwnerRights", domainSid, false, true) s.Domain1_BlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000001") + graphTestContext.UpdateNode(s.Domain1_BlockImplicitOwnerRights) //// Object owners - s.Domain1_User101_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) - s.Domain1_User102_DomainAdmin = graphTestContext.NewActiveDirectoryUser("Domain Admin User", domainSid) - s.Domain1_User103_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("Enterprise Admin User", domainSid) - s.Domain1_User104_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) + s.Domain1_User101_Owner = graphTestContext.NewActiveDirectoryUser("User101_Owner", domainSid) + s.Domain1_User102_DomainAdmin = graphTestContext.NewActiveDirectoryUser("User102_DomainAdmin", domainSid) + s.Domain1_User103_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("User103_EnterpriseAdmin", domainSid) + s.Domain1_User104_WriteOwner = graphTestContext.NewActiveDirectoryUser("User104_WriteOwner", domainSid) ////// Add the Domain Admins group and member s.Domain1_Group1_DomainAdmins = graphTestContext.NewActiveDirectoryGroup("Domain Admins", domainSid) s.Domain1_Group1_DomainAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-512") - graphTestContext.NewRelationship(s.Domain1_Group1_DomainAdmins, s.Domain1_User102_DomainAdmin, ad.MemberOf) + graphTestContext.UpdateNode(s.Domain1_Group1_DomainAdmins) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Group1_DomainAdmins, ad.MemberOf) ////// Add the Enterprise Admins group and member s.Domain1_Group2_EnterpriseAdmins = graphTestContext.NewActiveDirectoryGroup("Enterprise Admins", domainSid) s.Domain1_Group2_EnterpriseAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-519") - graphTestContext.NewRelationship(s.Domain1_Group2_EnterpriseAdmins, s.Domain1_User103_EnterpriseAdmin, ad.MemberOf) + graphTestContext.UpdateNode(s.Domain1_Group2_EnterpriseAdmins) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Group2_EnterpriseAdmins, ad.MemberOf) //// Owned objects ////// Computers s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights_OwnerIsLowPriv", domainSid) s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_Computer2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("Computer2_NoOwnerRights_OwnerIsDA", domainSid) s.Domain1_Computer2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_Computer2_NoOwnerRights_OwnerIsDA) graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_Computer3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("Computer3_NoOwnerRights_OwnerIsEA", domainSid) s.Domain1_Computer3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_Computer3_NoOwnerRights_OwnerIsEA) graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) s.Domain1_Computer4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_AbusableOwnerRightsNoneInherited", domainSid) s.Domain1_Computer4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_Computer4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_Computer4_AbusableOwnerRightsNoneInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) s.Domain1_Computer5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_AbusableOwnerRightsInherited", domainSid) s.Domain1_Computer5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_Computer5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_Computer5_AbusableOwnerRightsInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer8_OnlyNonabusableOwnerRightsInherited", domainSid) s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present ////// MSAs s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_MSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("MSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_MSA2_NoOwnerRights_OwnerIsDA) graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_MSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("MSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_MSA3_NoOwnerRights_OwnerIsEA) graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) s.Domain1_MSA4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_MSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_MSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_MSA4_AbusableOwnerRightsNoneInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) s.Domain1_MSA5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_MSA5_AbusableOwnerRightsInherited.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_MSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA5_AbusableOwnerRightsInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("MSA6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.MSA.String(), true) s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present ////// GMSAs s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("GMSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA) graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("GMSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA) graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) s.Domain1_GMSA5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_GMSA5_AbusableOwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_GMSA5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA5_AbusableOwnerRightsInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("GMSA6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present ////// Users s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User1_NoOwnerRights_OwnerIsLowPriv", domainSid) s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) s.Domain1_User2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User2_NoOwnerRights_OwnerIsDA", domainSid) s.Domain1_User2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_User2_NoOwnerRights_OwnerIsDA) graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) s.Domain1_User3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User3_NoOwnerRights_OwnerIsEA", domainSid) s.Domain1_User3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_User3_NoOwnerRights_OwnerIsEA) graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) s.Domain1_User4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryUser("User4_AbusableOwnerRightsNoneInherited", domainSid) s.Domain1_User4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_User4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain1_User4_AbusableOwnerRightsNoneInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) s.Domain1_User5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User5_AbusableOwnerRightsInherited", domainSid) s.Domain1_User5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_User5_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_User5_AbusableOwnerRightsInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryUser("User6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited) graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryUser("User7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_User8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User8_OnlyNonabusableOwnerRightsInherited", domainSid) s.Domain1_User8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain1_User8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain1_User8_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present // Domain 2 + domainSid = RandomDomainSID() s.Domain2_DoNotBlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain2_DoNotBlockImplicitOwnerRights", domainSid, false, true) s.Domain2_DoNotBlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000000") + graphTestContext.UpdateNode(s.Domain2_DoNotBlockImplicitOwnerRights) //// Object owners - s.Domain2_User1_Owner = graphTestContext.NewActiveDirectoryUser("Owner User", domainSid) - s.Domain2_User2_WriteOwner = graphTestContext.NewActiveDirectoryUser("WriteOwner User", domainSid) + s.Domain2_User1_Owner = graphTestContext.NewActiveDirectoryUser("User1_Owner", domainSid) + s.Domain2_User2_WriteOwner = graphTestContext.NewActiveDirectoryUser("User2_WriteOwner", domainSid) //// Owned objects ////// Computers s.Domain2_Computer1_NoOwnerRights = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights", domainSid) s.Domain2_Computer1_NoOwnerRights.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain2_Computer1_NoOwnerRights) graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer1_NoOwnerRights, ad.OwnsRaw) graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer1_NoOwnerRights, ad.WriteOwnerRaw) s.Domain2_Computer2_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer2_AbusableOwnerRightsNoneInherited", domainSid) s.Domain2_Computer2_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain2_Computer2_AbusableOwnerRightsNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) + graphTestContext.UpdateNode(s.Domain2_Computer2_AbusableOwnerRightsNoneInherited) graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer2_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) @@ -8736,32 +8787,35 @@ func (s *OwnsWriteOwner) Setup(graphTestContext *GraphTestContext) { s.Domain2_Computer3_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer3_AbusableOwnerRightsInherited", domainSid) s.Domain2_Computer3_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain2_Computer3_AbusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain2_Computer3_AbusableOwnerRightsInherited) graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) + graphTestContext.UpdateNode(s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited) graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), false) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_OnlyNonabusableOwnerRightsInherited", domainSid) s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyAceGrantOwnerRights.String(), true) s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.DoesAnyInheritedAceGrantOwnerRights.String(), true) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) - graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) + graphTestContext.UpdateNode(s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present } type HarnessDetails struct { diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index b82e020e09..a3de27afce 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -201,8 +201,8 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, } - // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err == nil { + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) if !isComputerDerived { isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() if err != nil { From 6e86f5071a587869705e8c14537140ad0e3f1613 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Tue, 19 Nov 2024 17:10:48 -0500 Subject: [PATCH 20/33] Completed Owns edge tests --- .../src/analysis/ad/ad_integration_test.go | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/cmd/api/src/analysis/ad/ad_integration_test.go b/cmd/api/src/analysis/ad/ad_integration_test.go index 60b8921e59..17b183c483 100644 --- a/cmd/api/src/analysis/ad/ad_integration_test.go +++ b/cmd/api/src/analysis/ad/ad_integration_test.go @@ -1131,7 +1131,7 @@ func TestOwnsWriteOwner(t *testing.T) { } else { db.ReadTransaction(context.Background(), func(tx graph.Transaction) error { - // Owns + // Owns: MATCH (a)-[r:Owns]->(b) RETURN a, r, b; if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { return query.And( query.Kind(query.Relationship(), ad.Owns), @@ -1141,7 +1141,96 @@ func TestOwnsWriteOwner(t *testing.T) { t.Fatalf("error fetching Owns edges in integration test; %v", err) } else { require.Equal(t, 10, len(results)) + + for _, rel := range results { + if startNode, err := ops.FetchNode(tx, rel.StartID); err != nil { + t.Fatalf("error fetching start node in integration test; %v", err) + } else if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + t.Fatalf("error fetching target node in integration test; %v", err) + } else { + // Extract 'name' properties from startNode and targetNode + startNodeName, okStart := startNode.Properties.Map["name"].(string) + if !okStart { + startNodeName = "" + } + targetNodeName, okTarget := targetNode.Properties.Map["name"].(string) + if !okTarget { + targetNodeName = "" + } + + if targetNode.ID == harness.OwnsWriteOwner.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.ID { + // Domain1_User101_Owner -[Owns]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv + require.Equal(t, harness.OwnsWriteOwner.Domain1_User101_Owner.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_Computer2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_Computer2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_MSA2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_MSA2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_GMSA2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_User2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_Computer3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_Computer3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_MSA3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_MSA3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_GMSA3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_User3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer1_NoOwnerRights.ID { + // Domain2_User1_Owner -[Owns]-> Domain2_Computer1_NoOwnerRights + require.Equal(t, harness.OwnsWriteOwner.Domain2_User1_Owner.ID, startNode.ID) + } else { + t.Fatalf("unexpected edge in integration test: %s -[Owns]-> %s", startNodeName, targetNodeName) + } + } + } + } + + // OwnsLimitedRights: MATCH (a)-[r:OwnsLimitedRights]->(b) RETURN a, r, b; + if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.OwnsLimitedRights), + query.Kind(query.Start(), ad.Entity), + ) + })); err != nil { + t.Fatalf("error fetching OwnsLimitedRights edges in integration test; %v", err) + } else { + require.Equal(t, 15, len(results)) + } + + // WriteOwner: MATCH (a)-[r:WriteOwner]->(b) RETURN a, r, b; + if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.WriteOwner), + query.Kind(query.Start(), ad.Entity), + ) + })); err != nil { + t.Fatalf("error fetching WriteOwner edges in integration test; %v", err) + } else { + require.Equal(t, 8, len(results)) } + + // WriteOwnerLimitedRights: MATCH (a)-[r:WriteOwnerLimitedRights]->(b) RETURN a, r, b; + if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.WriteOwnerLimitedRights), + query.Kind(query.Start(), ad.Entity), + ) + })); err != nil { + t.Fatalf("error fetching WriteOwner edges in integration test; %v", err) + } else { + require.Equal(t, 5, len(results)) + } + return nil }) } From 7d06e704f5ec995a0bd31d1954f46d3737b358c8 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Wed, 20 Nov 2024 09:08:38 -0500 Subject: [PATCH 21/33] Completed WriteOwner edge tests --- .../src/analysis/ad/ad_integration_test.go | 86 ++++++++++++++----- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/cmd/api/src/analysis/ad/ad_integration_test.go b/cmd/api/src/analysis/ad/ad_integration_test.go index 17b183c483..20d66e8a02 100644 --- a/cmd/api/src/analysis/ad/ad_integration_test.go +++ b/cmd/api/src/analysis/ad/ad_integration_test.go @@ -1161,33 +1161,43 @@ func TestOwnsWriteOwner(t *testing.T) { if targetNode.ID == harness.OwnsWriteOwner.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.ID { // Domain1_User101_Owner -[Owns]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv require.Equal(t, harness.OwnsWriteOwner.Domain1_User101_Owner.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_Computer2_NoOwnerRights_OwnerIsDA.ID { // Domain1_User102_DomainAdmin -[Owns]-> Domain1_Computer2_NoOwnerRights_OwnerIsDA require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_MSA2_NoOwnerRights_OwnerIsDA.ID { // Domain1_User102_DomainAdmin -[Owns]-> Domain1_MSA2_NoOwnerRights_OwnerIsDA require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.ID { // Domain1_User102_DomainAdmin -[Owns]-> Domain1_GMSA2_NoOwnerRights_OwnerIsDA require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User2_NoOwnerRights_OwnerIsDA.ID { // Domain1_User102_DomainAdmin -[Owns]-> Domain1_User2_NoOwnerRights_OwnerIsDA require.Equal(t, harness.OwnsWriteOwner.Domain1_User102_DomainAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_Computer3_NoOwnerRights_OwnerIsEA.ID { // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_Computer3_NoOwnerRights_OwnerIsEA require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_MSA3_NoOwnerRights_OwnerIsEA.ID { // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_MSA3_NoOwnerRights_OwnerIsEA require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.ID { // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_GMSA3_NoOwnerRights_OwnerIsEA require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User3_NoOwnerRights_OwnerIsEA.ID { // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_User3_NoOwnerRights_OwnerIsEA require.Equal(t, harness.OwnsWriteOwner.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer1_NoOwnerRights.ID { // Domain2_User1_Owner -[Owns]-> Domain2_Computer1_NoOwnerRights require.Equal(t, harness.OwnsWriteOwner.Domain2_User1_Owner.ID, startNode.ID) + } else { t.Fatalf("unexpected edge in integration test: %s -[Owns]-> %s", startNodeName, targetNodeName) } @@ -1195,18 +1205,6 @@ func TestOwnsWriteOwner(t *testing.T) { } } - // OwnsLimitedRights: MATCH (a)-[r:OwnsLimitedRights]->(b) RETURN a, r, b; - if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { - return query.And( - query.Kind(query.Relationship(), ad.OwnsLimitedRights), - query.Kind(query.Start(), ad.Entity), - ) - })); err != nil { - t.Fatalf("error fetching OwnsLimitedRights edges in integration test; %v", err) - } else { - require.Equal(t, 15, len(results)) - } - // WriteOwner: MATCH (a)-[r:WriteOwner]->(b) RETURN a, r, b; if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { return query.And( @@ -1217,18 +1215,60 @@ func TestOwnsWriteOwner(t *testing.T) { t.Fatalf("error fetching WriteOwner edges in integration test; %v", err) } else { require.Equal(t, 8, len(results)) - } - // WriteOwnerLimitedRights: MATCH (a)-[r:WriteOwnerLimitedRights]->(b) RETURN a, r, b; - if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { - return query.And( - query.Kind(query.Relationship(), ad.WriteOwnerLimitedRights), - query.Kind(query.Start(), ad.Entity), - ) - })); err != nil { - t.Fatalf("error fetching WriteOwner edges in integration test; %v", err) - } else { - require.Equal(t, 5, len(results)) + for _, rel := range results { + if startNode, err := ops.FetchNode(tx, rel.StartID); err != nil { + t.Fatalf("error fetching start node in integration test; %v", err) + } else if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + t.Fatalf("error fetching target node in integration test; %v", err) + } else { + // Extract 'name' properties from startNode and targetNode + startNodeName, okStart := startNode.Properties.Map["name"].(string) + if !okStart { + startNodeName = "" + } + targetNodeName, okTarget := targetNode.Properties.Map["name"].(string) + if !okTarget { + targetNodeName = "" + } + + if targetNode.ID == harness.OwnsWriteOwner.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.ID { + // Domain1_User104_WriteOwner -[Owns]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv + require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User104_WriteOwner -[Owns]-> Domain1_User2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User104_WriteOwner -[Owns]-> Domain1_User3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User4_AbusableOwnerRightsNoneInherited.ID { + // Domain1_User104_WriteOwner -[Owns]-> Domain1_User4_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.ID { + // Domain1_User104_WriteOwner -[Owns]-> Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited + require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer1_NoOwnerRights.ID { + // Domain2_User2_WriteOwner -[Owns]-> Domain2_Computer1_NoOwnerRights + require.Equal(t, harness.OwnsWriteOwner.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer2_AbusableOwnerRightsNoneInherited.ID { + // Domain2_User2_WriteOwner -[Owns]-> Domain2_Computer2_AbusableOwnerRightsNoneInherited + require.Equal(t, harness.OwnsWriteOwner.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.ID { + // Domain2_User2_WriteOwner -[Owns]-> Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited + require.Equal(t, harness.OwnsWriteOwner.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else { + t.Fatalf("unexpected edge in integration test: %s -[Owns]-> %s", startNodeName, targetNodeName) + } + } + } } return nil From ef55a25e15ef58a2da8682b82e1fc43d062b4802 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Wed, 20 Nov 2024 16:03:27 -0500 Subject: [PATCH 22/33] Accounted for all abusability and inheritance situations for user and computer object ownership and WriteOwner permissions --- packages/go/analysis/ad/owns.go | 3 +-- packages/go/ein/ad.go | 37 +++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index a3de27afce..0e7895b2d7 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -60,9 +60,8 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) - // Get the admin group IDs } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { - + // Get the admin group IDs // If we fail to get the admin group IDs, add the Owns edge and return an error isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() if err != nil { diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 0a93cfa80e..8e2fc023e2 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -305,6 +305,21 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target )) } } else { + // We're dealing with a case where the OWNER RIGHTS SID is granted uninherited abusable permissions + // We can tell if any non-abusable ACE is present and inherited by checking the DoesAnyInheritedAceGrantOwnerRights property + // If there are no inherited abusable permissions but there are inherited non-abusable permissions, + // the non-abusable permissions will NOT be deleted on ownership change, so WriteOwner will NOT be abusable + doesAnyInheritedAceGrantOwnerRights, exists := targetNode.PropertyMap[ad.DoesAnyInheritedAceGrantOwnerRights.String()] + if exists { + doesAnyInheritedAceGrantOwnerRights, ok := doesAnyInheritedAceGrantOwnerRights.(bool) + if ok { + if doesAnyInheritedAceGrantOwnerRights { + return converted + } + // If the non-abusable rights were NOT inherited, they are deleted on ownership change and WriteOwner may be abusable + // Post will determine if WriteOwner is abusable based on dSHeuristics:BlockOwnerImplicitRights enforcement and object type + } + } // If there are abusable permissions granted to the OWNER RIGHTS SID, but they are not inherited, // they will be deleted on ownership change and WriteOwner may be abusable, so create a WriteOwnerRaw // edge for post-processing so we can check BlockOwnerImplicitRights enforcement and object type @@ -341,15 +356,33 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target if ok { if doesAnyInheritedAceGrantOwnerRights { return converted + } else { + // If the non-abusable rights were NOT inherited, they are deleted on ownership change and WriteOwner may be abusable + // Post will determine if WriteOwner is abusable based on dSHeuristics:BlockOwnerImplicitRights enforcement and object type + // Create a non-traversable WriteOwnerRaw edge for post-processing + for _, limitedPrincipal := range potentialWriteOwnerLimitedPrincipals { + converted = append(converted, NewIngestibleRelationship( + limitedPrincipal.SourceData, + IngestibleTarget{ + Target: targetID, + TargetType: targetType, + }, + IngestibleRel{ + RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited}, + RelType: ad.WriteOwnerRaw, + }, + )) + } + // Don't add the OwnsRaw edge if the OWNER RIGHTS SID has uninherited, non-abusable permissions + return converted } - // If the non-abusable rights were NOT inherited, they are deleted on ownership change and WriteOwner may be abusable - // Post will determine if WriteOwner is abusable based on dSHeuristics:BlockOwnerImplicitRights enforcement and object type } } } } } + // When the SharpHound collection does not include the doesanyacegrantownerrights property // Create a non-traversable OwnsRaw edge for post-processing if ownerPrincipalInfo.Source != "" { converted = append(converted, NewIngestibleRelationship( From c928b2d506f7b2039afb49765970c3d100a0790d Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 21 Nov 2024 16:22:16 -0500 Subject: [PATCH 23/33] Updated command to identify dSHeuristics, minor formatting --- packages/go/ein/ad.go | 4 +++- .../src/components/HelpTexts/Owns/General.tsx | 10 ++++++++++ .../src/components/HelpTexts/WriteOwner/General.tsx | 6 +++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 8e2fc023e2..81683b4613 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -307,7 +307,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target } else { // We're dealing with a case where the OWNER RIGHTS SID is granted uninherited abusable permissions // We can tell if any non-abusable ACE is present and inherited by checking the DoesAnyInheritedAceGrantOwnerRights property - // If there are no inherited abusable permissions but there are inherited non-abusable permissions, + // If there are no inherited abusable permissions but there are inherited non-abusable permissions, // the non-abusable permissions will NOT be deleted on ownership change, so WriteOwner will NOT be abusable doesAnyInheritedAceGrantOwnerRights, exists := targetNode.PropertyMap[ad.DoesAnyInheritedAceGrantOwnerRights.String()] if exists { @@ -383,6 +383,8 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target } // When the SharpHound collection does not include the doesanyacegrantownerrights property + // Or when the doesanyinheritedacegrantownerrights property is false + // Create a non-traversable OwnsRaw edge for post-processing if ownerPrincipalInfo.Source != "" { converted = append(converted, NewIngestibleRelationship( diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx index 591a8b63e5..f54688136d 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx @@ -34,6 +34,11 @@ const General: FC = ({ sourceName, sourceType, targetName, target
  • The OWNER RIGHTS SID (S-1-3-4) is not explicitly granted privileges on the object
  • + + { + "(Get-ACL -Path (\"AD:\" + \"CN=Object,DC=example,DC=com\")).Access | Where-Object { $_.IdentityReference -eq \"OWNER RIGHTS\" }" + } +
    OR
    @@ -52,6 +57,11 @@ const General: FC = ({ sourceName, sourceType, targetName, target The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is defined in the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, implicit owner rights are not blocked. + + { + "$searcher = [adsisearcher]\"\"\n$searcher.SearchRoot = \"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=EXAMPLE,DC=LOCAL\"\n$searcher.SearchScope = [System.DirectoryServices.SearchScope]::Base\n$searcher.Filter = \"(objectClass=*)\"\n$searcher.PropertiesToLoad.Add(\"DSHeuristics\") | Out-Null\n$result = $searcher.FindOne()\nWrite-Output \"DSHeuristics: $($result.Properties['DSHeuristics'])\"" + } +
    AND EITHER:
    diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx index c00c4f1157..8c5916a6d6 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx @@ -40,11 +40,15 @@ const General: FC = ({ sourceName, sourceType, targetName, target The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is defined in the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, implicit owner rights are not blocked. + + { + "$searcher = [adsisearcher]\"\"\n$searcher.SearchRoot = \"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=EXAMPLE,DC=LOCAL\"\n$searcher.SearchScope = [System.DirectoryServices.SearchScope]::Base\n$searcher.Filter = \"(objectClass=*)\"\n$searcher.PropertiesToLoad.Add(\"DSHeuristics\") | Out-Null\n$result = $searcher.FindOne()\nWrite-Output \"DSHeuristics: $($result.Properties['DSHeuristics'])\"" + } +
  • The object is not a computer or a derivative of a computer object (e.g., MSA, GMSA).
  • - ); From 06790f266ebcb2d30f88843a60402ee2429708eb Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 21 Nov 2024 16:54:21 -0500 Subject: [PATCH 24/33] Added test harness and integration testing for collector versions prior to change --- .../src/analysis/ad/ad_integration_test.go | 217 ++++++++++- cmd/api/src/test/integration/harnesses.go | 336 ++++++++++++++++++ 2 files changed, 545 insertions(+), 8 deletions(-) diff --git a/cmd/api/src/analysis/ad/ad_integration_test.go b/cmd/api/src/analysis/ad/ad_integration_test.go index 20d66e8a02..9220ea7a88 100644 --- a/cmd/api/src/analysis/ad/ad_integration_test.go +++ b/cmd/api/src/analysis/ad/ad_integration_test.go @@ -1116,6 +1116,207 @@ func TestDCSync(t *testing.T) { }) } +func TestOwnsWriteOwnerPriorCollectorVersions(t *testing.T) { + testContext := integration.NewGraphTestContext(t, schema.DefaultGraphSchema()) + + testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error { + harness.OwnsWriteOwnerPriorCollectorVersions.Setup(testContext) + // To verify in Neo4j: MATCH (n:Computer) MATCH (u:User) RETURN n, u + return nil + }, func(harness integration.HarnessDetails, db graph.Database) { + if groupExpansions, err := adAnalysis.ExpandAllRDPLocalGroups(testContext.Context(), db); err != nil { + t.Fatalf("error expanding groups in integration test; %v", err) + } else if _, err := adAnalysis.PostOwnsAndWriteOwner(testContext.Context(), db, groupExpansions); err != nil { + t.Fatalf("error creating Owns/WriteOwner edges in integration test; %v", err) + } else { + db.ReadTransaction(context.Background(), func(tx graph.Transaction) error { + + // Owns: MATCH (a)-[r:Owns]->(b) RETURN a, r, b; + if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.Owns), + query.Kind(query.Start(), ad.Entity), + ) + })); err != nil { + t.Fatalf("error fetching Owns edges in integration test; %v", err) + } else { + require.Equal(t, 10, len(results)) + + for _, rel := range results { + if startNode, err := ops.FetchNode(tx, rel.StartID); err != nil { + t.Fatalf("error fetching start node in integration test; %v", err) + } else if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + t.Fatalf("error fetching target node in integration test; %v", err) + } else { + // Extract 'name' properties from startNode and targetNode + startNodeName, okStart := startNode.Properties.Map["name"].(string) + if !okStart { + startNodeName = "" + } + targetNodeName, okTarget := targetNode.Properties.Map["name"].(string) + if !okTarget { + targetNodeName = "" + } + + if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.ID { + // Domain1_User101_Owner -[Owns]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_Computer2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_Computer2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User102_DomainAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_MSA2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_MSA2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User102_DomainAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_GMSA2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User102_DomainAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User102_DomainAdmin -[Owns]-> Domain1_User2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User102_DomainAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_Computer3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_Computer3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_MSA3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_MSA3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_GMSA3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User103_EnterpriseAdmin -[Owns]-> Domain1_User3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User103_EnterpriseAdmin.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer1_NoOwnerRights.ID { + // Domain2_User1_Owner -[Owns]-> Domain2_Computer1_NoOwnerRights + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User1_Owner.ID, startNode.ID) + + // + // Below here are the expected false positives present after post-processing data from the prior collector versions + // + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.ID { + // Domain1_User101_Owner -[Owns]-> Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User8_OnlyNonabusableOwnerRightsInherited.ID { + // Domain1_User101_Owner -[Owns]-> Domain1_User8_OnlyNonabusableOwnerRightsInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.ID { + // Domain2_User1_Owner -[Owns]-> Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User1_Owner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited.ID { + // Domain2_User1_Owner -[Owns]-> Domain2_Computer6_OnlyNonabusableOwnerRightsInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User1_Owner.ID, startNode.ID) + + } else { + t.Fatalf("unexpected edge in integration test: %s -[Owns]-> %s", startNodeName, targetNodeName) + } + } + } + } + + // WriteOwner: MATCH (a)-[r:WriteOwner]->(b) RETURN a, r, b; + if results, err := ops.FetchRelationships(tx.Relationships().Filterf(func() graph.Criteria { + return query.And( + query.Kind(query.Relationship(), ad.WriteOwner), + query.Kind(query.Start(), ad.Entity), + ) + })); err != nil { + t.Fatalf("error fetching WriteOwner edges in integration test; %v", err) + } else { + require.Equal(t, 8, len(results)) + + for _, rel := range results { + if startNode, err := ops.FetchNode(tx, rel.StartID); err != nil { + t.Fatalf("error fetching start node in integration test; %v", err) + } else if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + t.Fatalf("error fetching target node in integration test; %v", err) + } else { + // Extract 'name' properties from startNode and targetNode + startNodeName, okStart := startNode.Properties.Map["name"].(string) + if !okStart { + startNodeName = "" + } + targetNodeName, okTarget := targetNode.Properties.Map["name"].(string) + if !okTarget { + targetNodeName = "" + } + + if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.ID { + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User2_NoOwnerRights_OwnerIsDA.ID { + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User2_NoOwnerRights_OwnerIsDA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User3_NoOwnerRights_OwnerIsEA.ID { + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User3_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User4_AbusableOwnerRightsNoneInherited.ID { + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User4_NoOwnerRights_OwnerIsEA + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.ID { + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer1_NoOwnerRights.ID { + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer1_NoOwnerRights + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer2_AbusableOwnerRightsNoneInherited.ID { + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer2_AbusableOwnerRightsNoneInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.ID { + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User2_WriteOwner.ID, startNode.ID) + + // + // Below here are the expected false positives present after post-processing data from the prior collector versions + // + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited.ID { + // Domain1_User101_Owner -[WriteOwner]-> Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User8_OnlyNonabusableOwnerRightsInherited.ID { + // Domain1_User101_Owner -[WriteOwner]-> Domain1_User8_OnlyNonabusableOwnerRightsInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited.ID { + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited.ID { + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer6_OnlyNonabusableOwnerRightsInherited + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_User2_WriteOwner.ID, startNode.ID) + + } else { + t.Fatalf("unexpected edge in integration test: %s -[Owns]-> %s", startNodeName, targetNodeName) + } + } + } + } + + return nil + }) + } + }) +} + func TestOwnsWriteOwner(t *testing.T) { testContext := integration.NewGraphTestContext(t, schema.DefaultGraphSchema()) @@ -1233,35 +1434,35 @@ func TestOwnsWriteOwner(t *testing.T) { } if targetNode.ID == harness.OwnsWriteOwner.Domain1_User1_NoOwnerRights_OwnerIsLowPriv.ID { - // Domain1_User104_WriteOwner -[Owns]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User1_NoOwnerRights_OwnerIsLowPriv require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User2_NoOwnerRights_OwnerIsDA.ID { - // Domain1_User104_WriteOwner -[Owns]-> Domain1_User2_NoOwnerRights_OwnerIsDA + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User2_NoOwnerRights_OwnerIsDA require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User3_NoOwnerRights_OwnerIsEA.ID { - // Domain1_User104_WriteOwner -[Owns]-> Domain1_User3_NoOwnerRights_OwnerIsEA + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User3_NoOwnerRights_OwnerIsEA require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User4_AbusableOwnerRightsNoneInherited.ID { - // Domain1_User104_WriteOwner -[Owns]-> Domain1_User4_NoOwnerRights_OwnerIsEA + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User4_NoOwnerRights_OwnerIsEA require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited.ID { - // Domain1_User104_WriteOwner -[Owns]-> Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited + // Domain1_User104_WriteOwner -[WriteOwner]-> Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited require.Equal(t, harness.OwnsWriteOwner.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer1_NoOwnerRights.ID { - // Domain2_User2_WriteOwner -[Owns]-> Domain2_Computer1_NoOwnerRights + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer1_NoOwnerRights require.Equal(t, harness.OwnsWriteOwner.Domain2_User2_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer2_AbusableOwnerRightsNoneInherited.ID { - // Domain2_User2_WriteOwner -[Owns]-> Domain2_Computer2_AbusableOwnerRightsNoneInherited + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer2_AbusableOwnerRightsNoneInherited require.Equal(t, harness.OwnsWriteOwner.Domain2_User2_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwner.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited.ID { - // Domain2_User2_WriteOwner -[Owns]-> Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited + // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited require.Equal(t, harness.OwnsWriteOwner.Domain2_User2_WriteOwner.ID, startNode.ID) } else { diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index 5f3b8a0b80..496a05cd91 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -8402,6 +8402,341 @@ func (s *ESC10bHarnessDC2) Setup(graphTestContext *GraphTestContext) { graphTestContext.UpdateNode(s.DC1) } +type OwnsWriteOwnerPriorCollectorVersions struct { + + // Domain 1 + Domain1_BlockImplicitOwnerRights *graph.Node + + //// Object owners + Domain1_User101_Owner *graph.Node + Domain1_User102_DomainAdmin *graph.Node + Domain1_User103_EnterpriseAdmin *graph.Node + Domain1_User104_WriteOwner *graph.Node + Domain1_Group1_DomainAdmins *graph.Node + Domain1_Group2_EnterpriseAdmins *graph.Node + + //// Owned objects + Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_Computer2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_Computer3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_Computer4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_Computer5_AbusableOwnerRightsInherited *graph.Node + Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_Computer8_OnlyNonabusableOwnerRightsInherited *graph.Node + + Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_MSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_MSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_MSA4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_MSA5_AbusableOwnerRightsInherited *graph.Node + Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_MSA8_OnlyNonabusableOwnerRightsInherited *graph.Node + + Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_GMSA2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_GMSA3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_GMSA4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_GMSA5_AbusableOwnerRightsInherited *graph.Node + Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited *graph.Node + + Domain1_User1_NoOwnerRights_OwnerIsLowPriv *graph.Node + Domain1_User2_NoOwnerRights_OwnerIsDA *graph.Node + Domain1_User3_NoOwnerRights_OwnerIsEA *graph.Node + Domain1_User4_AbusableOwnerRightsNoneInherited *graph.Node + Domain1_User5_AbusableOwnerRightsInherited *graph.Node + Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain1_User8_OnlyNonabusableOwnerRightsInherited *graph.Node + + // Domain 2 + Domain2_DoNotBlockImplicitOwnerRights *graph.Node + + //// Object owners + Domain2_User1_Owner *graph.Node + Domain2_User2_WriteOwner *graph.Node + + //// Owned objects + Domain2_Computer1_NoOwnerRights *graph.Node + Domain2_Computer2_AbusableOwnerRightsNoneInherited *graph.Node + Domain2_Computer3_AbusableOwnerRightsInherited *graph.Node + Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited *graph.Node + Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited *graph.Node + Domain2_Computer6_OnlyNonabusableOwnerRightsInherited *graph.Node +} + +func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTestContext) { + + // This is the same as OwnsWriteOwner except that DoesAnyAceGrantOwnerRights and DoesAnyInheritedAceGrantOwnerRights are + // not set on any objects because SharpHound didn't collect them prior to moving Owns and WriteOwner to post-processing + + domainSid := RandomDomainSID() + + // Domain 1 + s.Domain1_BlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain1_BlockImplicitOwnerRights", domainSid, false, true) + s.Domain1_BlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000001") + graphTestContext.UpdateNode(s.Domain1_BlockImplicitOwnerRights) + + //// Object owners + s.Domain1_User101_Owner = graphTestContext.NewActiveDirectoryUser("User101_Owner", domainSid) + s.Domain1_User102_DomainAdmin = graphTestContext.NewActiveDirectoryUser("User102_DomainAdmin", domainSid) + s.Domain1_User103_EnterpriseAdmin = graphTestContext.NewActiveDirectoryUser("User103_EnterpriseAdmin", domainSid) + s.Domain1_User104_WriteOwner = graphTestContext.NewActiveDirectoryUser("User104_WriteOwner", domainSid) + + ////// Add the Domain Admins group and member + s.Domain1_Group1_DomainAdmins = graphTestContext.NewActiveDirectoryGroup("Domain Admins", domainSid) + s.Domain1_Group1_DomainAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-512") + graphTestContext.UpdateNode(s.Domain1_Group1_DomainAdmins) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Group1_DomainAdmins, ad.MemberOf) + + ////// Add the Enterprise Admins group and member + s.Domain1_Group2_EnterpriseAdmins = graphTestContext.NewActiveDirectoryGroup("Enterprise Admins", domainSid) + s.Domain1_Group2_EnterpriseAdmins.Properties.Set(common.ObjectID.String(), domainSid+"-519") + graphTestContext.UpdateNode(s.Domain1_Group2_EnterpriseAdmins) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Group2_EnterpriseAdmins, ad.MemberOf) + + //// Owned objects + + ////// Computers + s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights_OwnerIsLowPriv", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + + s.Domain1_Computer2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("Computer2_NoOwnerRights_OwnerIsDA", domainSid) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + + s.Domain1_Computer3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("Computer3_NoOwnerRights_OwnerIsEA", domainSid) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + + s.Domain1_Computer4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_AbusableOwnerRightsNoneInherited", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_Computer5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_AbusableOwnerRightsInherited", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + + s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer8_OnlyNonabusableOwnerRightsInherited", domainSid) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + + ////// MSAs + s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + + s.Domain1_MSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("MSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_MSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA2_NoOwnerRights_OwnerIsDA) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + + s.Domain1_MSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("MSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_MSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA3_NoOwnerRights_OwnerIsEA) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + + s.Domain1_MSA4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_MSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA4_AbusableOwnerRightsNoneInherited) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_MSA5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_MSA5_AbusableOwnerRightsInherited.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA5_AbusableOwnerRightsInherited) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("MSA6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + + s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.MSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + + ////// GMSAs + s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) + s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + + s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryComputer("GMSA2_NoOwnerRights_OwnerIsDA", domainSid) + s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + + s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryComputer("GMSA3_NoOwnerRights_OwnerIsEA", domainSid) + s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + + s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA4_AbusableOwnerRightsNoneInherited", domainSid) + s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_GMSA5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA5_AbusableOwnerRightsInherited", domainSid) + s.Domain1_GMSA5_AbusableOwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA5_AbusableOwnerRightsInherited) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("GMSA6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + + s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA8_OnlyNonabusableOwnerRightsInherited", domainSid) + s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) + graphTestContext.UpdateNode(s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + + ////// Users + s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User1_NoOwnerRights_OwnerIsLowPriv", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv, ad.WriteOwnerRaw) + + s.Domain1_User2_NoOwnerRights_OwnerIsDA = graphTestContext.NewActiveDirectoryUser("User2_NoOwnerRights_OwnerIsDA", domainSid) + graphTestContext.NewRelationship(s.Domain1_User102_DomainAdmin, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User2_NoOwnerRights_OwnerIsDA, ad.WriteOwnerRaw) + + s.Domain1_User3_NoOwnerRights_OwnerIsEA = graphTestContext.NewActiveDirectoryUser("User3_NoOwnerRights_OwnerIsEA", domainSid) + graphTestContext.NewRelationship(s.Domain1_User103_EnterpriseAdmin, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User3_NoOwnerRights_OwnerIsEA, ad.WriteOwnerRaw) + + s.Domain1_User4_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryUser("User4_AbusableOwnerRightsNoneInherited", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User4_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_User5_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User5_AbusableOwnerRightsInherited", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User5_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryUser("User6_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + + s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryUser("User7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain1_User8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User8_OnlyNonabusableOwnerRightsInherited", domainSid) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + + // Domain 2 + domainSid = RandomDomainSID() + s.Domain2_DoNotBlockImplicitOwnerRights = graphTestContext.NewActiveDirectoryDomain("Domain2_DoNotBlockImplicitOwnerRights", domainSid, false, true) + s.Domain2_DoNotBlockImplicitOwnerRights.Properties.Set(ad.DSHeuristics.String(), "00000000000000000000000000000") + graphTestContext.UpdateNode(s.Domain2_DoNotBlockImplicitOwnerRights) + + //// Object owners + s.Domain2_User1_Owner = graphTestContext.NewActiveDirectoryUser("User1_Owner", domainSid) + s.Domain2_User2_WriteOwner = graphTestContext.NewActiveDirectoryUser("User2_WriteOwner", domainSid) + + //// Owned objects + + ////// Computers + s.Domain2_Computer1_NoOwnerRights = graphTestContext.NewActiveDirectoryComputer("Computer1_NoOwnerRights", domainSid) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer1_NoOwnerRights, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer1_NoOwnerRights, ad.WriteOwnerRaw) + + s.Domain2_Computer2_AbusableOwnerRightsNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer2_AbusableOwnerRightsNoneInherited", domainSid) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer2_AbusableOwnerRightsNoneInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer2_AbusableOwnerRightsNoneInherited, ad.WriteOwnerRaw) + + s.Domain2_Computer3_AbusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer3_AbusableOwnerRightsInherited", domainSid) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer3_AbusableOwnerRightsInherited, ad.WriteOwnerLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + + s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited = graphTestContext.NewActiveDirectoryComputer("Computer4_AbusableOwnerRightsOnlyNonabusableInherited", domainSid) + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited, ad.OwnsLimitedRights, graph.AsProperties(graph.PropertyMap{ + ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, + })) + // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + + s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) + // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) + + s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_OnlyNonabusableOwnerRightsInherited", domainSid) + // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present +} + type OwnsWriteOwner struct { // Domain 1 @@ -8917,4 +9252,5 @@ type HarnessDetails struct { SyncLAPSPasswordHarness SyncLAPSPasswordHarness HybridAttackPaths HybridAttackPaths OwnsWriteOwner OwnsWriteOwner + OwnsWriteOwnerPriorCollectorVersions OwnsWriteOwnerPriorCollectorVersions } From e44c9b7693931d7d3c9084f018b3e27177f27a8f Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Fri, 22 Nov 2024 09:32:13 -0500 Subject: [PATCH 25/33] Formatting changes from just generate --- .../src/components/HelpTexts/Owns/General.tsx | 41 ++++++++----------- .../HelpTexts/OwnsLimitedRights/General.tsx | 4 +- .../OwnsLimitedRights/LinuxAbuse.tsx | 9 ++-- .../HelpTexts/OwnsLimitedRights/Opsec.tsx | 5 ++- .../OwnsLimitedRights/References.tsx | 5 ++- .../OwnsLimitedRights/WindowsAbuse.tsx | 13 +++--- .../HelpTexts/WriteOwner/General.tsx | 25 ++++++----- .../WriteOwnerLimitedRights/General.tsx | 2 +- .../WriteOwnerLimitedRights/LinuxAbuse.tsx | 14 ++++--- .../WriteOwnerLimitedRights/Opsec.tsx | 5 ++- .../WriteOwnerLimitedRights/References.tsx | 5 ++- .../WriteOwnerLimitedRights/WindowsAbuse.tsx | 14 ++++--- 12 files changed, 75 insertions(+), 67 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx index f54688136d..2b34274c85 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/Owns/General.tsx @@ -27,54 +27,49 @@ const General: FC = ({ sourceName, sourceType, targetName, target - The owner of an object is implicitly granted the ability to modify object security descriptors, including the DACL, - when the following conditions are met: + The owner of an object is implicitly granted the ability to modify object security descriptors, + including the DACL, when the following conditions are met:
      -
    • - The OWNER RIGHTS SID (S-1-3-4) is not explicitly granted privileges on the object -
    • +
    • The OWNER RIGHTS SID (S-1-3-4) is not explicitly granted privileges on the object
    • - { - "(Get-ACL -Path (\"AD:\" + \"CN=Object,DC=example,DC=com\")).Access | Where-Object { $_.IdentityReference -eq \"OWNER RIGHTS\" }" - } + { + '(Get-ACL -Path ("AD:" + "CN=Object,DC=example,DC=com")).Access | Where-Object { $_.IdentityReference -eq "OWNER RIGHTS" }' + }
      OR

      -
    • - Implicit owner rights are not blocked -
    • +
    • Implicit owner rights are not blocked
    - + Implicit owner rights are not blocked and are therefore abusable when the following conditions are met: -
    • - The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is defined in - the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, implicit owner rights are not blocked. + The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is + defined in the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, + implicit owner rights are not blocked.
    • - { - "$searcher = [adsisearcher]\"\"\n$searcher.SearchRoot = \"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=EXAMPLE,DC=LOCAL\"\n$searcher.SearchScope = [System.DirectoryServices.SearchScope]::Base\n$searcher.Filter = \"(objectClass=*)\"\n$searcher.PropertiesToLoad.Add(\"DSHeuristics\") | Out-Null\n$result = $searcher.FindOne()\nWrite-Output \"DSHeuristics: $($result.Properties['DSHeuristics'])\"" - } - + { + '$searcher = [adsisearcher]""\n$searcher.SearchRoot = "LDAP://CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=EXAMPLE,DC=LOCAL"\n$searcher.SearchScope = [System.DirectoryServices.SearchScope]::Base\n$searcher.Filter = "(objectClass=*)"\n$searcher.PropertiesToLoad.Add("DSHeuristics") | Out-Null\n$result = $searcher.FindOne()\nWrite-Output "DSHeuristics: $($result.Properties[\'DSHeuristics\'])"' + } +
      AND EITHER:

      -
    • - The object is not a computer or derivative of a computer object (e.g., MSA, GMSA) -
    • +
    • The object is not a computer or derivative of a computer object (e.g., MSA, GMSA)

    • OR

    • - The object is a computer or derivative of a computer object and the owner is a member of the Domain Admins or Enterprise Admins group (or is the SID of either group) + The object is a computer or derivative of a computer object and the owner is a member of the + Domain Admins or Enterprise Admins group (or is the SID of either group)
    diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx index 69050b4b15..1f0e1fddaa 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx @@ -23,8 +23,8 @@ const General: FC = ({ sourceName, sourceType }) => { <> When specific privileges on an object's DACL are explicitly granted to the "OWNER RIGHTS" SID (S-1-3-4), - implicit owner rights (e.g., WriteDacl) are blocked, and the owner is granted only the specific privileges - granted to OWNER RIGHTS. This can be used to limit the rights of the owner of an object. + implicit owner rights (e.g., WriteDacl) are blocked, and the owner is granted only the specific + privileges granted to OWNER RIGHTS. This can be used to limit the rights of the owner of an object. ); diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx index 6411161c8e..898f4ddd08 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/LinuxAbuse.tsx @@ -21,15 +21,16 @@ const LinuxAbuse: FC = () => { return ( <> - To abuse ownership of an object where the OWNER RIGHTS SID is explicitly granted permissions, - you can abuse the specific permissions granted to the OWNER RIGHTS SID. + To abuse ownership of an object where the OWNER RIGHTS SID is explicitly granted permissions, you can + abuse the specific permissions granted to the OWNER RIGHTS SID. - Please refer to the abuse info for the specific granted permissions at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges + Please refer to the abuse info for the specific granted permissions at + https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges - ) + ); }; export default LinuxAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx index 52aa406e4b..78b1029879 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/Opsec.tsx @@ -21,10 +21,11 @@ const Opsec: FC = () => { return ( <> - Please refer to the OPSEC info for the specific granted permissions at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + Please refer to the OPSEC info for the specific granted permissions at + https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html - ) + ); }; export default Opsec; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx index c8be0b6709..af08969cbe 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/References.tsx @@ -20,7 +20,10 @@ import { Link, Box } from '@mui/material'; const References: FC = () => { return ( - + https://www.hub.trimarcsecurity.com/post/trimarc-whitepaper-owner-or-pwnd
    diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx index cecba685dd..1f8d99a817 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/WindowsAbuse.tsx @@ -21,14 +21,15 @@ const WindowsAbuse: FC = () => { return ( <> - To abuse ownership of an object where the OWNER RIGHTS SID is explicitly granted privileges, - you can abuse the specific privileges granted to the OWNER RIGHTS SID. -
    -
    - Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges + To abuse ownership of an object where the OWNER RIGHTS SID is explicitly granted privileges, you can + abuse the specific privileges granted to the OWNER RIGHTS SID. +
    +
    + Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at + https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges
    - ) + ); }; export default WindowsAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx index 8c5916a6d6..e487a47241 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwner/General.tsx @@ -28,26 +28,25 @@ const General: FC = ({ sourceName, sourceType, targetName, target - Implicit owner rights are not blocked and are therefore abusable via change in ownership when the following - conditions are met: + Implicit owner rights are not blocked and are therefore abusable via change in ownership when the + following conditions are met:
    • - Inheritance is not configured for any privileges explicitly granted to the OWNER RIGHTS SID (S-1-3-4). - Non-inherited privileges granted to OWNER RIGHTS are removed when the owner is changed, allowing the - new owner to have the full set of implicit owner rights. + Inheritance is not configured for any privileges explicitly granted to the OWNER RIGHTS SID + (S-1-3-4). Non-inherited privileges granted to OWNER RIGHTS are removed when the owner is + changed, allowing the new owner to have the full set of implicit owner rights.
    • - The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is defined in - the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, implicit owner rights are not blocked. + The domain's BlockOwnerImplicitRights setting is not in enforcement mode. This setting is + defined in the 29th character in the domain's dSHeuristics attribute. When set to 0 or 2, + implicit owner rights are not blocked.
    • - { - "$searcher = [adsisearcher]\"\"\n$searcher.SearchRoot = \"LDAP://CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=EXAMPLE,DC=LOCAL\"\n$searcher.SearchScope = [System.DirectoryServices.SearchScope]::Base\n$searcher.Filter = \"(objectClass=*)\"\n$searcher.PropertiesToLoad.Add(\"DSHeuristics\") | Out-Null\n$result = $searcher.FindOne()\nWrite-Output \"DSHeuristics: $($result.Properties['DSHeuristics'])\"" - } + { + '$searcher = [adsisearcher]""\n$searcher.SearchRoot = "LDAP://CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=EXAMPLE,DC=LOCAL"\n$searcher.SearchScope = [System.DirectoryServices.SearchScope]::Base\n$searcher.Filter = "(objectClass=*)"\n$searcher.PropertiesToLoad.Add("DSHeuristics") | Out-Null\n$result = $searcher.FindOne()\nWrite-Output "DSHeuristics: $($result.Properties[\'DSHeuristics\'])"' + } -
    • - The object is not a computer or a derivative of a computer object (e.g., MSA, GMSA). -
    • +
    • The object is not a computer or a derivative of a computer object (e.g., MSA, GMSA).
    diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx index b2f0180833..4bd262d215 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx @@ -24,7 +24,7 @@ const General: FC = ({ sourceName, sourceType }) => { When specific privileges on an object's DACL are explicitly granted to the "OWNER RIGHTS" SID (S-1-3-4), and inheritance is configured for those permissions, they are inherited by the new object owner after a - change in ownership. In this case, implicit owner rights are blocked, and the new owner is granted only + change in ownership. In this case, implicit owner rights are blocked, and the new owner is granted only the specific inherited privileges granted to OWNER RIGHTS. diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx index 31b465142d..f5d11a6264 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/LinuxAbuse.tsx @@ -21,14 +21,16 @@ const LinuxAbuse: FC = () => { return ( <> - To abuse change in ownership of an object where the OWNER RIGHTS SID is explicitly granted inherited privileges, - you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID in the context of the new owner. -
    -
    - Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges + To abuse change in ownership of an object where the OWNER RIGHTS SID is explicitly granted inherited + privileges, you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID + in the context of the new owner. +
    +
    + Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at + https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges
    - ) + ); }; export default LinuxAbuse; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx index 52aa406e4b..78b1029879 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/Opsec.tsx @@ -21,10 +21,11 @@ const Opsec: FC = () => { return ( <> - Please refer to the OPSEC info for the specific granted permissions at https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html + Please refer to the OPSEC info for the specific granted permissions at + https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html - ) + ); }; export default Opsec; diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx index c8be0b6709..af08969cbe 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/References.tsx @@ -20,7 +20,10 @@ import { Link, Box } from '@mui/material'; const References: FC = () => { return ( - + https://www.hub.trimarcsecurity.com/post/trimarc-whitepaper-owner-or-pwnd
    diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx index 7f197e84e0..9bcbc41687 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/WindowsAbuse.tsx @@ -21,14 +21,16 @@ const WindowsAbuse: FC = () => { return ( <> - To abuse change in ownership of an object where the OWNER RIGHTS SID is explicitly granted inherited privileges, - you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID in the context of the new owner. -
    -
    - Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges + To abuse change in ownership of an object where the OWNER RIGHTS SID is explicitly granted inherited + privileges, you can modify the owner, then abuse the specific privileges granted to the OWNER RIGHTS SID + in the context of the new owner. +
    +
    + Please refer to the abuse info for the specific privileges granted to OWNER RIGHTS at + https://support.bloodhoundenterprise.io/hc/en-us/articles/17224136169371-About-BloodHound-Edges
    - ) + ); }; export default WindowsAbuse; From cbb9766e23ededd11ed284fe43b1bfebfefe3e8a Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Fri, 22 Nov 2024 09:41:43 -0500 Subject: [PATCH 26/33] Updating schema with DoesAnyInheritedAceGrantOwnerRights --- packages/go/graphschema/ad/ad.go | 9 ++++++++- packages/javascript/bh-shared-ui/src/graphSchema.ts | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 6ec6031fc3..960ff439d4 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -225,10 +225,11 @@ const ( GMSA Property = "gmsa" MSA Property = "msa" DoesAnyAceGrantOwnerRights Property = "doesanyacegrantownerrights" + DoesAnyInheritedAceGrantOwnerRights Property = "doesanyinheritedacegrantownerrights" ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA, DoesAnyAceGrantOwnerRights} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA, DoesAnyAceGrantOwnerRights, DoesAnyInheritedAceGrantOwnerRights} } func ParseProperty(source string) (Property, error) { switch source { @@ -444,6 +445,8 @@ func ParseProperty(source string) (Property, error) { return MSA, nil case "doesanyacegrantownerrights": return DoesAnyAceGrantOwnerRights, nil + case "doesanyinheritedacegrantownerrights": + return DoesAnyInheritedAceGrantOwnerRights, nil default: return "", errors.New("Invalid enumeration value: " + source) } @@ -662,6 +665,8 @@ func (s Property) String() string { return string(MSA) case DoesAnyAceGrantOwnerRights: return string(DoesAnyAceGrantOwnerRights) + case DoesAnyInheritedAceGrantOwnerRights: + return string(DoesAnyInheritedAceGrantOwnerRights) default: return "Invalid enumeration case: " + string(s) } @@ -880,6 +885,8 @@ func (s Property) Name() string { return "MSA" case DoesAnyAceGrantOwnerRights: return "Does Any ACE Grant Owner Rights" + case DoesAnyInheritedAceGrantOwnerRights: + return "Does Any Inherited ACE Grant Owner Rights" default: return "Invalid enumeration case: " + string(s) } diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index 66839a3f15..4cc8ea0e47 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -418,6 +418,7 @@ export enum ActiveDirectoryKindProperties { GMSA = 'gmsa', MSA = 'msa', DoesAnyAceGrantOwnerRights = 'doesanyacegrantownerrights', + DoesAnyInheritedAceGrantOwnerRights = 'doesanyinheritedacegrantownerrights', } export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKindProperties): string | undefined { switch (value) { @@ -633,6 +634,8 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'MSA'; case ActiveDirectoryKindProperties.DoesAnyAceGrantOwnerRights: return 'Does Any ACE Grant Owner Rights'; + case ActiveDirectoryKindProperties.DoesAnyInheritedAceGrantOwnerRights: + return 'Does Any Inherited ACE Grant Owner Rights'; default: return undefined; } From 6de74d5da22dd3cc956a5c7a856e8aa6547439e9 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Fri, 22 Nov 2024 16:34:12 -0500 Subject: [PATCH 27/33] Finished integration tests for post-processing collections from SharpHound versions prior to change --- .../src/analysis/ad/ad_integration_test.go | 8 ++--- cmd/api/src/test/integration/harnesses.go | 36 +++++++++++++++++++ packages/cue/bh/ad/ad.cue | 16 --------- packages/go/analysis/ad/owns.go | 36 +++++++++---------- packages/go/analysis/ad/post.go | 3 -- packages/go/ein/ad.go | 17 ++++----- packages/go/graphschema/ad/ad.go | 28 ++------------- .../bh-shared-ui/src/graphSchema.ts | 6 ---- 8 files changed, 70 insertions(+), 80 deletions(-) diff --git a/cmd/api/src/analysis/ad/ad_integration_test.go b/cmd/api/src/analysis/ad/ad_integration_test.go index 9220ea7a88..77eaf0407f 100644 --- a/cmd/api/src/analysis/ad/ad_integration_test.go +++ b/cmd/api/src/analysis/ad/ad_integration_test.go @@ -1140,7 +1140,7 @@ func TestOwnsWriteOwnerPriorCollectorVersions(t *testing.T) { })); err != nil { t.Fatalf("error fetching Owns edges in integration test; %v", err) } else { - require.Equal(t, 10, len(results)) + require.Equal(t, 14, len(results)) for _, rel := range results { if startNode, err := ops.FetchNode(tx, rel.StartID); err != nil { @@ -1234,7 +1234,7 @@ func TestOwnsWriteOwnerPriorCollectorVersions(t *testing.T) { })); err != nil { t.Fatalf("error fetching WriteOwner edges in integration test; %v", err) } else { - require.Equal(t, 8, len(results)) + require.Equal(t, 12, len(results)) for _, rel := range results { if startNode, err := ops.FetchNode(tx, rel.StartID); err != nil { @@ -1290,11 +1290,11 @@ func TestOwnsWriteOwnerPriorCollectorVersions(t *testing.T) { } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited.ID { // Domain1_User101_Owner -[WriteOwner]-> Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited - require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User8_OnlyNonabusableOwnerRightsInherited.ID { // Domain1_User101_Owner -[WriteOwner]-> Domain1_User8_OnlyNonabusableOwnerRightsInherited - require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User101_Owner.ID, startNode.ID) + require.Equal(t, harness.OwnsWriteOwnerPriorCollectorVersions.Domain1_User104_WriteOwner.ID, startNode.ID) } else if targetNode.ID == harness.OwnsWriteOwnerPriorCollectorVersions.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited.ID { // Domain2_User2_WriteOwner -[WriteOwner]-> Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited diff --git a/cmd/api/src/test/integration/harnesses.go b/cmd/api/src/test/integration/harnesses.go index 496a05cd91..5a4c505b9d 100644 --- a/cmd/api/src/test/integration/harnesses.go +++ b/cmd/api/src/test/integration/harnesses.go @@ -8472,6 +8472,7 @@ func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTest // This is the same as OwnsWriteOwner except that DoesAnyAceGrantOwnerRights and DoesAnyInheritedAceGrantOwnerRights are // not set on any objects because SharpHound didn't collect them prior to moving Owns and WriteOwner to post-processing + // This also impacts ingest for Domain1_7 and 8 and Domain2_Computer5 and 6 domainSid := RandomDomainSID() @@ -8532,13 +8533,20 @@ func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTest ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + // Ingest adds WriteOwnerRaw if no abusable owner rights are present and DoesAnyInheritedAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer8_OnlyNonabusableOwnerRightsInherited", domainSid) // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_Computer8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) ////// MSAs s.Domain1_MSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("MSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) @@ -8584,17 +8592,24 @@ func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTest ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + // Ingest adds WriteOwnerRaw if no abusable owner rights are present and DoesAnyInheritedAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("MSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.MSA.String(), true) graphTestContext.UpdateNode(s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited) // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("MSA8_OnlyNonabusableOwnerRightsInherited", domainSid) s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.MSA.String(), true) graphTestContext.UpdateNode(s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited) // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_MSA8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) ////// GMSAs s.Domain1_GMSA1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryComputer("GMSA1_NoOwnerRights_OwnerIsLowPriv", domainSid) @@ -8640,17 +8655,24 @@ func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTest ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + // Ingest adds WriteOwnerRaw if no abusable owner rights are present and DoesAnyInheritedAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited.Properties.Set(ad.GMSA.String(), true) graphTestContext.UpdateNode(s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited) // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("GMSA8_OnlyNonabusableOwnerRightsInherited", domainSid) s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited.Properties.Set(ad.GMSA.String(), true) graphTestContext.UpdateNode(s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited) // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_GMSA8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) ////// Users s.Domain1_User1_NoOwnerRights_OwnerIsLowPriv = graphTestContext.NewActiveDirectoryUser("User1_NoOwnerRights_OwnerIsLowPriv", domainSid) @@ -8684,13 +8706,20 @@ func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTest ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + // Ingest adds WriteOwnerRaw if no abusable owner rights are present and DoesAnyInheritedAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User6_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryUser("User7_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User7_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain1_User8_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryUser("User8_OnlyNonabusableOwnerRightsInherited", domainSid) // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain1_User101_Owner, s.Domain1_User8_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain1_User104_WriteOwner, s.Domain1_User8_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) // Domain 2 domainSid = RandomDomainSID() @@ -8728,13 +8757,20 @@ func (s *OwnsWriteOwnerPriorCollectorVersions) Setup(graphTestContext *GraphTest ad.Property("privileges"): []string{"AddKeyCredentialLink", "WriteSPN"}, })) // Ingest does not add WriteOwnerRaw if only non-abusable owner rights are inherited + // Ingest adds WriteOwnerRaw if no abusable owner rights are present and DoesAnyInheritedAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer4_AbusableOwnerRightsOnlyNonabusableInherited, ad.WriteOwnerRaw) s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited = graphTestContext.NewActiveDirectoryComputer("Computer5_OnlyNonabusableOwnerRightsAndNoneInherited", domainSid) // Ingest does not add OwnsRaw if only non-abusable owner rights are present, but adds WriteOwnerRaw if none are inherited + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.OwnsRaw) graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer5_OnlyNonabusableOwnerRightsAndNoneInherited, ad.WriteOwnerRaw) s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited = graphTestContext.NewActiveDirectoryComputer("Computer6_OnlyNonabusableOwnerRightsInherited", domainSid) // Ingest does not add OwnsRaw or WriteOwnerRaw if only non-abusable, inherited owner rights are present + // Ingest adds OwnsRaw and WriteOwnerRaw if no abusable owner rights are present and DoesAnyAceGrantOwnerRights is not present + graphTestContext.NewRelationship(s.Domain2_User1_Owner, s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited, ad.OwnsRaw) + graphTestContext.NewRelationship(s.Domain2_User2_WriteOwner, s.Domain2_Computer6_OnlyNonabusableOwnerRightsInherited, ad.WriteOwnerRaw) } type OwnsWriteOwner struct { diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 9fdb5a11f1..8b9f63e27f 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -189,13 +189,6 @@ IsACL: types.#StringEnum & { representation: "isacl" } -IsInherited: types.#StringEnum & { - symbol: "IsInherited" - schema: "ad" - name: "Is Inherited" - representation: "isinherited" -} - IsACLProtected: types.#StringEnum & { symbol: "IsACLProtected" schema: "ad" @@ -742,13 +735,6 @@ MinPwdLength: types.#StringEnum & { representation: "minpwdlength" } -OwnerSid: types.#StringEnum & { - symbol: "OwnerSid" - schema: "ad" - name: "Owner SID" - representation: "ownersid" -} - GMSA: types.#StringEnum & { symbol: "GMSA" schema: "ad" @@ -803,7 +789,6 @@ Properties: [ HighValue, BlocksInheritance, IsACL, - IsInherited, IsACLProtected, IsDeleted, Enforced, @@ -880,7 +865,6 @@ Properties: [ MaxPwdAge, LockoutDuration, LockoutObservationWindow, - OwnerSid, GMSA, MSA, DoesAnyAceGrantOwnerRights, diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 0e7895b2d7..cef4b5f285 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -48,7 +48,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { // If we fail to get the dSHeuristics values, add the Owns edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -56,14 +56,14 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { // Get the admin group IDs // If we fail to get the admin group IDs, add the Owns edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -71,7 +71,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } return fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) @@ -95,7 +95,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge if !enforced { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -103,7 +103,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { @@ -112,7 +112,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) // If the target node is NOT a computer or derived object, add the Owns edge - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -120,13 +120,13 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } } } else { // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -134,7 +134,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } } @@ -158,7 +158,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { // If we fail to get the dSHeuristics values, add the WriteOwner edge and return an error - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -166,7 +166,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) @@ -189,7 +189,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the WriteOwner edge if !enforced { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -197,13 +197,13 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err == nil { // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) if !isComputerDerived { - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -212,14 +212,14 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } } } } else { // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship - isInherited, err := rel.Properties.GetOrDefault(ad.IsInherited.String(), false).Bool() + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } @@ -227,7 +227,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): isInherited}, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } } diff --git a/packages/go/analysis/ad/post.go b/packages/go/analysis/ad/post.go index 7237c7f9ad..04838fe3c8 100644 --- a/packages/go/analysis/ad/post.go +++ b/packages/go/analysis/ad/post.go @@ -60,12 +60,9 @@ func PostProcessedRelationships() []graph.Kind { ad.ADCSESC13, ad.EnrollOnBehalfOf, ad.SyncedToEntraUser, -<<<<<<< HEAD ad.Owns, ad.WriteOwner, -======= ad.ExtendedByPolicy, ->>>>>>> main } } diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index bd300383d2..8d1aa26096 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -23,6 +23,7 @@ import ( "github.com/specterops/bloodhound/analysis" "github.com/specterops/bloodhound/dawgs/graph" "github.com/specterops/bloodhound/graphschema/ad" + "github.com/specterops/bloodhound/graphschema/common" "github.com/specterops/bloodhound/log" "github.com/specterops/bloodhound/slicesext" ) @@ -60,7 +61,7 @@ func convertOwnsEdgeToProperty(item IngestBase, itemProps map[string]any) { if rightName, err := analysis.ParseKind(ace.RightName); err != nil { continue } else if rightName.Is(ad.Owns) || rightName.Is(ad.OwnsRaw) { - itemProps[ad.OwnerSid.String()] = ace.PrincipalSID + itemProps[common.OwnerObjectID.String()] = ace.PrincipalSID return } } @@ -258,7 +259,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): ace.IsInherited}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): ace.IsInherited}, RelType: rightKind, }, )) @@ -281,7 +282,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target // Owns is never inherited IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false, "privileges": ownerLimitedPrivs}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): false, "privileges": ownerLimitedPrivs}, RelType: ad.OwnsLimitedRights, }, )) @@ -299,7 +300,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target // Create an edge property containing an array of all INHERITED abusable permissions granted to the OWNER RIGHTS SID IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": writeOwnerLimitedPrivs}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): limitedPrincipal.IsInherited, "privileges": writeOwnerLimitedPrivs}, RelType: ad.WriteOwnerLimitedRights, }, )) @@ -331,7 +332,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): limitedPrincipal.IsInherited}, RelType: ad.WriteOwnerRaw, }, )) @@ -368,7 +369,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): limitedPrincipal.IsInherited}, RelType: ad.WriteOwnerRaw, }, )) @@ -396,7 +397,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target // Owns is never inherited IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): false}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): false}, RelType: ad.OwnsRaw, }, )) @@ -411,7 +412,7 @@ func ParseACEData(targetNode IngestibleNode, aces []ACE, targetID string, target TargetType: targetType, }, IngestibleRel{ - RelProps: map[string]any{ad.IsACL.String(): true, ad.IsInherited.String(): limitedPrincipal.IsInherited}, + RelProps: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): limitedPrincipal.IsInherited}, RelType: ad.WriteOwnerRaw, }, )) diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 7f0313ea3e..08e098351b 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -145,7 +145,6 @@ const ( HighValue Property = "highvalue" BlocksInheritance Property = "blocksinheritance" IsACL Property = "isacl" - IsInherited Property = "isinherited" IsACLProtected Property = "isaclprotected" IsDeleted Property = "isdeleted" Enforced Property = "enforced" @@ -222,7 +221,6 @@ const ( MaxPwdAge Property = "maxpwdage" LockoutDuration Property = "lockoutduration" LockoutObservationWindow Property = "lockoutobservationwindow" - OwnerSid Property = "ownersid" GMSA Property = "gmsa" MSA Property = "msa" DoesAnyAceGrantOwnerRights Property = "doesanyacegrantownerrights" @@ -230,7 +228,7 @@ const ( ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsInherited, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, OwnerSid, GMSA, MSA, DoesAnyAceGrantOwnerRights, DoesAnyInheritedAceGrantOwnerRights} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, GMSA, MSA, DoesAnyAceGrantOwnerRights, DoesAnyInheritedAceGrantOwnerRights} } func ParseProperty(source string) (Property, error) { switch source { @@ -284,8 +282,6 @@ func ParseProperty(source string) (Property, error) { return BlocksInheritance, nil case "isacl": return IsACL, nil - case "isinherited": - return IsInherited, nil case "isaclprotected": return IsACLProtected, nil case "isdeleted": @@ -438,8 +434,6 @@ func ParseProperty(source string) (Property, error) { return LockoutDuration, nil case "lockoutobservationwindow": return LockoutObservationWindow, nil - case "ownersid": - return OwnerSid, nil case "gmsa": return GMSA, nil case "msa": @@ -504,8 +498,6 @@ func (s Property) String() string { return string(BlocksInheritance) case IsACL: return string(IsACL) - case IsInherited: - return string(IsInherited) case IsACLProtected: return string(IsACLProtected) case IsDeleted: @@ -658,8 +650,6 @@ func (s Property) String() string { return string(LockoutDuration) case LockoutObservationWindow: return string(LockoutObservationWindow) - case OwnerSid: - return string(OwnerSid) case GMSA: return string(GMSA) case MSA: @@ -724,8 +714,6 @@ func (s Property) Name() string { return "Blocks GPO Inheritance" case IsACL: return "Is ACL" - case IsInherited: - return "Is Inherited" case IsACLProtected: return "ACL Inheritance Denied" case IsDeleted: @@ -878,8 +866,6 @@ func (s Property) Name() string { return "Lockout Duration" case LockoutObservationWindow: return "Lockout Observation Window" - case OwnerSid: - return "Owner SID" case GMSA: return "GMSA" case MSA: @@ -904,21 +890,13 @@ func Nodes() []graph.Kind { return []graph.Kind{Entity, User, Computer, Group, GPO, OU, Container, Domain, LocalGroup, LocalUser, AIACA, RootCA, EnterpriseCA, NTAuthStore, CertTemplate, IssuancePolicy} } func Relationships() []graph.Kind { -<<<<<<< HEAD - 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, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} -======= - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser} ->>>>>>> main + return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } 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, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights} } func PathfindingRelationships() []graph.Kind { -<<<<<<< HEAD - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser, WriteOwnerLimitedRights, OwnsLimitedRights} -======= - return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser} ->>>>>>> main + return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser, WriteOwnerLimitedRights, OwnsLimitedRights} } func IsACLKind(s graph.Kind) bool { for _, acl := range ACLRelationships() { diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index a5b1680ee3..bc981e7ea2 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -340,7 +340,6 @@ export enum ActiveDirectoryKindProperties { HighValue = 'highvalue', BlocksInheritance = 'blocksinheritance', IsACL = 'isacl', - IsInherited = 'isinherited', IsACLProtected = 'isaclprotected', IsDeleted = 'isdeleted', Enforced = 'enforced', @@ -417,7 +416,6 @@ export enum ActiveDirectoryKindProperties { MaxPwdAge = 'maxpwdage', LockoutDuration = 'lockoutduration', LockoutObservationWindow = 'lockoutobservationwindow', - OwnerSid = 'ownersid', GMSA = 'gmsa', MSA = 'msa', DoesAnyAceGrantOwnerRights = 'doesanyacegrantownerrights', @@ -475,8 +473,6 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Blocks GPO Inheritance'; case ActiveDirectoryKindProperties.IsACL: return 'Is ACL'; - case ActiveDirectoryKindProperties.IsInherited: - return 'Is Inherited'; case ActiveDirectoryKindProperties.IsACLProtected: return 'ACL Inheritance Denied'; case ActiveDirectoryKindProperties.IsDeleted: @@ -629,8 +625,6 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Lockout Duration'; case ActiveDirectoryKindProperties.LockoutObservationWindow: return 'Lockout Observation Window'; - case ActiveDirectoryKindProperties.OwnerSid: - return 'Owner SID'; case ActiveDirectoryKindProperties.GMSA: return 'GMSA'; case ActiveDirectoryKindProperties.MSA: From 0ad719d00cebf5c08d9c87dff5827b29116b74b9 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Mon, 2 Dec 2024 17:16:45 -0500 Subject: [PATCH 28/33] Tested prior SharpHound collection file upload followed by newer version file upload --- packages/cue/bh/ad/ad.cue | 12 +++++++++++- packages/go/ein/ad.go | 2 +- packages/go/graphschema/ad/ad.go | 11 +++++++++-- packages/javascript/bh-shared-ui/src/graphSchema.ts | 3 +++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 8b9f63e27f..2e91c42d92 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -763,6 +763,13 @@ DoesAnyInheritedAceGrantOwnerRights: types.#StringEnum & { representation: "doesanyinheritedacegrantownerrights" } +OwnerSid: types.#StringEnum & { + symbol: "OwnerSid" + schema: "ad" + name: "Owner SID" + representation: "ownersid" +} + Properties: [ AdminCount, CASecurityCollected, @@ -868,7 +875,8 @@ Properties: [ GMSA, MSA, DoesAnyAceGrantOwnerRights, - DoesAnyInheritedAceGrantOwnerRights + DoesAnyInheritedAceGrantOwnerRights, + OwnerSid ] // Kinds @@ -1458,6 +1466,8 @@ ACLRelationships: [ WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights, + OwnsRaw, + WriteOwnerRaw ] // Edges that are used in pathfinding diff --git a/packages/go/ein/ad.go b/packages/go/ein/ad.go index 8d1aa26096..23833e1817 100644 --- a/packages/go/ein/ad.go +++ b/packages/go/ein/ad.go @@ -61,7 +61,7 @@ func convertOwnsEdgeToProperty(item IngestBase, itemProps map[string]any) { if rightName, err := analysis.ParseKind(ace.RightName); err != nil { continue } else if rightName.Is(ad.Owns) || rightName.Is(ad.OwnsRaw) { - itemProps[common.OwnerObjectID.String()] = ace.PrincipalSID + itemProps[ad.OwnerSid.String()] = ace.PrincipalSID return } } diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 08e098351b..e0f58574d7 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -225,10 +225,11 @@ const ( MSA Property = "msa" DoesAnyAceGrantOwnerRights Property = "doesanyacegrantownerrights" DoesAnyInheritedAceGrantOwnerRights Property = "doesanyinheritedacegrantownerrights" + OwnerSid Property = "ownersid" ) func AllProperties() []Property { - return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, GMSA, MSA, DoesAnyAceGrantOwnerRights, DoesAnyInheritedAceGrantOwnerRights} + return []Property{AdminCount, CASecurityCollected, CAName, CertChain, CertName, CertThumbprint, CertThumbprints, HasEnrollmentAgentRestrictions, EnrollmentAgentRestrictionsCollected, IsUserSpecifiesSanEnabled, IsUserSpecifiesSanEnabledCollected, RoleSeparationEnabled, RoleSeparationEnabledCollected, HasBasicConstraints, BasicConstraintPathLength, UnresolvedPublishedTemplates, DNSHostname, CrossCertificatePair, DistinguishedName, DomainFQDN, DomainSID, Sensitive, HighValue, BlocksInheritance, IsACL, IsACLProtected, IsDeleted, Enforced, Department, HasCrossCertificatePair, HasSPN, UnconstrainedDelegation, LastLogon, LastLogonTimestamp, IsPrimaryGroup, HasLAPS, DontRequirePreAuth, LogonType, HasURA, PasswordNeverExpires, PasswordNotRequired, FunctionalLevel, TrustType, SidFiltering, TrustedToAuth, SamAccountName, CertificateMappingMethodsRaw, CertificateMappingMethods, StrongCertificateBindingEnforcementRaw, StrongCertificateBindingEnforcement, EKUs, SubjectAltRequireUPN, SubjectAltRequireDNS, SubjectAltRequireDomainDNS, SubjectAltRequireEmail, SubjectAltRequireSPN, SubjectRequireEmail, AuthorizedSignatures, ApplicationPolicies, IssuancePolicies, SchemaVersion, RequiresManagerApproval, AuthenticationEnabled, SchannelAuthenticationEnabled, EnrolleeSuppliesSubject, CertificateApplicationPolicy, CertificateNameFlag, EffectiveEKUs, EnrollmentFlag, Flags, NoSecurityExtension, RenewalPeriod, ValidityPeriod, OID, HomeDirectory, CertificatePolicy, CertTemplateOID, GroupLinkID, ObjectGUID, ExpirePasswordsOnSmartCardOnlyAccounts, MachineAccountQuota, SupportedKerberosEncryptionTypes, TGTDelegationEnabled, PasswordStoredUsingReversibleEncryption, SmartcardRequired, UseDESKeyOnly, LogonScriptEnabled, LockedOut, UserCannotChangePassword, PasswordExpired, DSHeuristics, UserAccountControl, TrustAttributes, MinPwdLength, PwdProperties, PwdHistoryLength, LockoutThreshold, MinPwdAge, MaxPwdAge, LockoutDuration, LockoutObservationWindow, GMSA, MSA, DoesAnyAceGrantOwnerRights, DoesAnyInheritedAceGrantOwnerRights, OwnerSid} } func ParseProperty(source string) (Property, error) { switch source { @@ -442,6 +443,8 @@ func ParseProperty(source string) (Property, error) { return DoesAnyAceGrantOwnerRights, nil case "doesanyinheritedacegrantownerrights": return DoesAnyInheritedAceGrantOwnerRights, nil + case "ownersid": + return OwnerSid, nil default: return "", errors.New("Invalid enumeration value: " + source) } @@ -658,6 +661,8 @@ func (s Property) String() string { return string(DoesAnyAceGrantOwnerRights) case DoesAnyInheritedAceGrantOwnerRights: return string(DoesAnyInheritedAceGrantOwnerRights) + case OwnerSid: + return string(OwnerSid) default: return "Invalid enumeration case: " + string(s) } @@ -874,6 +879,8 @@ func (s Property) Name() string { return "Does Any ACE Grant Owner Rights" case DoesAnyInheritedAceGrantOwnerRights: return "Does Any Inherited ACE Grant Owner Rights" + case OwnerSid: + return "Owner SID" default: return "Invalid enumeration case: " + string(s) } @@ -893,7 +900,7 @@ func Relationships() []graph.Kind { return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } 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, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights} + return []graph.Kind{AllExtendedRights, ForceChangePassword, AddMember, AddAllowedToAct, GenericAll, WriteDACL, WriteOwner, GenericWrite, ReadLAPSPassword, ReadGMSAPassword, Owns, AddSelf, WriteSPN, AddKeyCredentialLink, GetChanges, GetChangesAll, GetChangesInFilteredSet, WriteAccountRestrictions, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights, OwnsRaw, WriteOwnerRaw} } func PathfindingRelationships() []graph.Kind { return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser, WriteOwnerLimitedRights, OwnsLimitedRights} diff --git a/packages/javascript/bh-shared-ui/src/graphSchema.ts b/packages/javascript/bh-shared-ui/src/graphSchema.ts index bc981e7ea2..b84b12ff87 100644 --- a/packages/javascript/bh-shared-ui/src/graphSchema.ts +++ b/packages/javascript/bh-shared-ui/src/graphSchema.ts @@ -420,6 +420,7 @@ export enum ActiveDirectoryKindProperties { MSA = 'msa', DoesAnyAceGrantOwnerRights = 'doesanyacegrantownerrights', DoesAnyInheritedAceGrantOwnerRights = 'doesanyinheritedacegrantownerrights', + OwnerSid = 'ownersid', } export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKindProperties): string | undefined { switch (value) { @@ -633,6 +634,8 @@ export function ActiveDirectoryKindPropertiesToDisplay(value: ActiveDirectoryKin return 'Does Any ACE Grant Owner Rights'; case ActiveDirectoryKindProperties.DoesAnyInheritedAceGrantOwnerRights: return 'Does Any Inherited ACE Grant Owner Rights'; + case ActiveDirectoryKindProperties.OwnerSid: + return 'Owner SID'; default: return undefined; } From 5409bbdf6d76afba30ebb5f5b95bb1fb8df82613 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Wed, 4 Dec 2024 13:27:53 -0500 Subject: [PATCH 29/33] Fetch dSHeuristics and admin IDs once and do not return on error --- packages/go/analysis/ad/owns.go | 241 +++++++++++++------------------- 1 file changed, 101 insertions(+), 140 deletions(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index cef4b5f285..558e27048e 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -19,7 +19,6 @@ package ad import ( "context" "errors" - "fmt" "github.com/specterops/bloodhound/analysis" "github.com/specterops/bloodhound/analysis/impact" @@ -29,11 +28,24 @@ import ( "github.com/specterops/bloodhound/dawgs/query" "github.com/specterops/bloodhound/graphschema/ad" "github.com/specterops/bloodhound/graphschema/common" + "github.com/specterops/bloodhound/log" ) func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { operation := analysis.NewPostRelationshipOperation(ctx, db, "PostOwnsAndWriteOwner") + // Get the dSHeuristics values for all domains + dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db) + if err != nil { + log.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + } + + adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions) + if err != nil { + // Get the admin group IDs + log.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) + } + // Get all source nodes of Owns ACEs (i.e., owning principals) where the target node has no ACEs granting abusable explicit permissions to OWNER RIGHTS operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { return tx.Relationships().Filter( @@ -44,25 +56,55 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - // Get the dSHeuristics values for all domains - if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) + if anyEnforced { - // If we fail to get the dSHeuristics values, add the Owns edge and return an error - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } - return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + // Get the target node of the OwnsRaw relationship + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue + + } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + // Get the dSHeuristics value for the domain of the target node + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false + } - } else if adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions); err != nil { - // Get the admin group IDs - // If we fail to get the admin group IDs, add the Owns edge and return an error + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge + if !enforced { + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, + } + + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + continue + } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { + // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) + // If the target node is NOT a computer or derived object, add the Owns edge + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.Owns, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, + } + } + } + } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) or we can't fetch the attribute, we can skip this analysis and just add the Owns relationship isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false @@ -73,70 +115,6 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio Kind: ad.Owns, RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } - return fmt.Errorf("failed fetching admin group ids values for postownsandwriteowner: %w", err) - - } else { - - // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) - if anyEnforced { - - // Get the target node of the OwnsRaw relationship - if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { - continue - - } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - // Get the dSHeuristics value for the domain of the target node - continue - } else { - enforced, ok := dsHeuristicsCache[domainSid] - if !ok { - enforced = false - } - - // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the Owns edge - if !enforced { - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } - - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { - // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) - continue - } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { - // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) - // If the target node is NOT a computer or derived object, add the Owns edge - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } - } - } - } else { - // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the Owns relationship - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.Owns, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } - } } } @@ -154,83 +132,66 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio ).Fetch(func(cursor graph.Cursor[*graph.Relationship]) error { for rel := range cursor.Chan() { - // Get the dSHeuristics values for all domains - if dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db); err != nil { + // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) + if anyEnforced { - // If we fail to get the dSHeuristics values, add the WriteOwner edge and return an error - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } - return fmt.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) - - } else { - // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) - if anyEnforced { + // Get the target node of the WriteOwner relationship + if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + continue - // Get the target node of the WriteOwner relationship - if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { - continue + } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { + // Get the dSHeuristics value for the domain of the target node + continue + } else { + enforced, ok := dsHeuristicsCache[domainSid] + if !ok { + enforced = false + } - } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - // Get the dSHeuristics value for the domain of the target node - continue - } else { - enforced, ok := dsHeuristicsCache[domainSid] - if !ok { - enforced = false + // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the WriteOwner edge + if !enforced { + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } - // If THIS domain does NOT enforce BlockOwnerImplicitRights, add the WriteOwner edge - if !enforced { + } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err == nil { + // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) + if !isComputerDerived { isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() if err != nil { isInherited = false } + // If the target node is NOT a computer or derived object, add the WriteOwner edge outC <- analysis.CreatePostRelationshipJob{ FromID: rel.StartID, ToID: rel.EndID, Kind: ad.WriteOwner, RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } - - } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err == nil { - // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) - if !isComputerDerived { - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - // If the target node is NOT a computer or derived object, add the WriteOwner edge - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } - } } } - } else { - // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1), we can skip this analysis and just add the WriteOwner relationship - isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() - if err != nil { - isInherited = false - } - outC <- analysis.CreatePostRelationshipJob{ - FromID: rel.StartID, - ToID: rel.EndID, - Kind: ad.WriteOwner, - RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, - } + } + } else { + // If no domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) or we can't fetch the attribute, we can skip this analysis and just add the WriteOwner relationship + isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() + if err != nil { + isInherited = false + } + outC <- analysis.CreatePostRelationshipJob{ + FromID: rel.StartID, + ToID: rel.EndID, + Kind: ad.WriteOwner, + RelProperties: map[string]any{ad.IsACL.String(): true, common.IsInherited.String(): isInherited}, } } + } return cursor.Error() From a1a0cb8a348e5ab33f8476385c4768eb4178f916 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Wed, 4 Dec 2024 13:36:07 -0500 Subject: [PATCH 30/33] Added null check for adminGroupIds --- packages/go/analysis/ad/owns.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index 558e27048e..a54e62a4c5 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -88,7 +88,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio } else if isComputerDerived, err := isTargetNodeComputerDerived(targetNode); err != nil { // If no abusable permissions are granted to OWNER RIGHTS, check if the target node is a computer or derived object (MSA or GMSA) continue - } else if (isComputerDerived && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { + } else if (isComputerDerived && adminGroupIds != nil && adminGroupIds.Contains(rel.StartID.Uint64())) || !isComputerDerived { // If the target node is a computer or derived object, add the Owns edge if the owning principal is a member of DA/EA (or is either group's SID) // If the target node is NOT a computer or derived object, add the Owns edge isInherited, err := rel.Properties.GetOrDefault(common.IsInherited.String(), false).Bool() From d262e0c04e0b465cfee6a385b68e9a800628d865 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 5 Dec 2024 13:29:02 -0500 Subject: [PATCH 31/33] Remove unused variables --- .../src/components/HelpTexts/OwnsLimitedRights/General.tsx | 2 +- .../components/HelpTexts/WriteOwnerLimitedRights/General.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx index 1f0e1fddaa..dfc334421a 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/OwnsLimitedRights/General.tsx @@ -18,7 +18,7 @@ import { FC } from 'react'; import { EdgeInfoProps } from '../index'; import { Typography } from '@mui/material'; -const General: FC = ({ sourceName, sourceType }) => { +const General: FC = () => { return ( <> diff --git a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx index 4bd262d215..340727af8e 100644 --- a/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx +++ b/packages/javascript/bh-shared-ui/src/components/HelpTexts/WriteOwnerLimitedRights/General.tsx @@ -18,7 +18,7 @@ import { FC } from 'react'; import { EdgeInfoProps } from '../index'; import { Typography } from '@mui/material'; -const General: FC = ({ sourceName, sourceType }) => { +const General: FC = () => { return ( <> From 2b464aa8093328c5b095868100eba41292625867 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 12 Dec 2024 10:34:33 -0500 Subject: [PATCH 32/33] Return/log errors, fix comments, remove raw edges from ACLRelationships to address PR comments --- packages/cue/bh/ad/ad.cue | 4 +--- packages/go/analysis/ad/owns.go | 9 ++++++--- packages/go/graphschema/ad/ad.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cue/bh/ad/ad.cue b/packages/cue/bh/ad/ad.cue index 2e91c42d92..40dbbab8cb 100644 --- a/packages/cue/bh/ad/ad.cue +++ b/packages/cue/bh/ad/ad.cue @@ -1465,9 +1465,7 @@ ACLRelationships: [ WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, - OwnsLimitedRights, - OwnsRaw, - WriteOwnerRaw + OwnsLimitedRights ] // Edges that are used in pathfinding diff --git a/packages/go/analysis/ad/owns.go b/packages/go/analysis/ad/owns.go index a54e62a4c5..e4ac07b8c5 100644 --- a/packages/go/analysis/ad/owns.go +++ b/packages/go/analysis/ad/owns.go @@ -38,6 +38,7 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio dsHeuristicsCache, anyEnforced, err := GetDsHeuristicsCache(ctx, db) if err != nil { log.Errorf("failed fetching dsheuristics values for postownsandwriteowner: %w", err) + return nil, err } adminGroupIds, err := FetchAdminGroupIds(ctx, db, groupExpansions) @@ -61,10 +62,11 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio // Get the target node of the OwnsRaw relationship if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + log.Errorf("failed fetching OwnsRaw target node postownsandwriteowner: %w", err) continue } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - // Get the dSHeuristics value for the domain of the target node + // Get the domain SID of the target node continue } else { enforced, ok := dsHeuristicsCache[domainSid] @@ -135,12 +137,13 @@ func PostOwnsAndWriteOwner(ctx context.Context, db graph.Database, groupExpansio // Check if ANY domain enforces BlockOwnerImplicitRights (dSHeuristics[28] == 1) if anyEnforced { - // Get the target node of the WriteOwner relationship + // Get the target node of the WriteOwnerRaw relationship if targetNode, err := ops.FetchNode(tx, rel.EndID); err != nil { + log.Errorf("failed fetching WriteOwnerRaw target node postownsandwriteowner: %w", err) continue } else if domainSid, err := targetNode.Properties.GetOrDefault(ad.DomainSID.String(), "").String(); err != nil { - // Get the dSHeuristics value for the domain of the target node + // Get the domain SID of the target node continue } else { enforced, ok := dsHeuristicsCache[domainSid] diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index e0f58574d7..5376fe4f4a 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -900,7 +900,7 @@ func Relationships() []graph.Kind { return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, GetChanges, GetChangesAll, GetChangesInFilteredSet, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, LocalToComputer, MemberOfLocalGroup, RemoteInteractiveLogonRight, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, RootCAFor, DCFor, PublishedTo, ManageCertificates, ManageCA, DelegatedEnrollmentAgent, Enroll, HostsCAService, WritePKIEnrollmentFlag, WritePKINameFlag, NTAuthStoreFor, TrustedForNTAuth, EnterpriseCAFor, IssuedSignedBy, GoldenCert, EnrollOnBehalfOf, OIDGroupLink, ExtendedByPolicy, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, SyncedToEntraUser, WriteOwnerLimitedRights, WriteOwnerRaw, OwnsLimitedRights, OwnsRaw} } 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, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights, OwnsRaw, WriteOwnerRaw} + return []graph.Kind{AllExtendedRights, ForceChangePassword, AddMember, AddAllowedToAct, GenericAll, WriteDACL, WriteOwner, GenericWrite, ReadLAPSPassword, ReadGMSAPassword, Owns, AddSelf, WriteSPN, AddKeyCredentialLink, GetChanges, GetChangesAll, GetChangesInFilteredSet, WriteAccountRestrictions, WriteGPLink, SyncLAPSPassword, DCSync, ManageCertificates, ManageCA, Enroll, WritePKIEnrollmentFlag, WritePKINameFlag, WriteOwnerLimitedRights, OwnsLimitedRights} } func PathfindingRelationships() []graph.Kind { return []graph.Kind{Owns, GenericAll, GenericWrite, WriteOwner, WriteDACL, MemberOf, ForceChangePassword, AllExtendedRights, AddMember, HasSession, Contains, GPLink, AllowedToDelegate, CoerceToTGT, TrustedBy, AllowedToAct, AdminTo, CanPSRemote, CanRDP, ExecuteDCOM, HasSIDHistory, AddSelf, DCSync, ReadLAPSPassword, ReadGMSAPassword, DumpSMSAPassword, SQLAdmin, AddAllowedToAct, WriteSPN, AddKeyCredentialLink, SyncLAPSPassword, WriteAccountRestrictions, WriteGPLink, GoldenCert, ADCSESC1, ADCSESC3, ADCSESC4, ADCSESC5, ADCSESC6a, ADCSESC6b, ADCSESC7, ADCSESC9a, ADCSESC9b, ADCSESC10a, ADCSESC10b, ADCSESC13, DCFor, SyncedToEntraUser, WriteOwnerLimitedRights, OwnsLimitedRights} From efc4498864a83678d53ec443d595104b942090d3 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 12 Dec 2024 10:44:30 -0500 Subject: [PATCH 33/33] Prepare for code review --- packages/go/graphschema/ad/ad.go | 1 + packages/go/graphschema/azure/azure.go | 1 + packages/go/graphschema/common/common.go | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/go/graphschema/ad/ad.go b/packages/go/graphschema/ad/ad.go index 5376fe4f4a..1f648bb9f5 100644 --- a/packages/go/graphschema/ad/ad.go +++ b/packages/go/graphschema/ad/ad.go @@ -21,6 +21,7 @@ package ad import ( "errors" + graph "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/go/graphschema/azure/azure.go b/packages/go/graphschema/azure/azure.go index 00b20f190f..787ee392e6 100644 --- a/packages/go/graphschema/azure/azure.go +++ b/packages/go/graphschema/azure/azure.go @@ -21,6 +21,7 @@ package azure import ( "errors" + graph "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/go/graphschema/common/common.go b/packages/go/graphschema/common/common.go index 631871c6bf..73edf123fa 100644 --- a/packages/go/graphschema/common/common.go +++ b/packages/go/graphschema/common/common.go @@ -21,6 +21,7 @@ package common import ( "errors" + graph "github.com/specterops/bloodhound/dawgs/graph" )