Skip to content

Commit

Permalink
feat: auto tag T0 parent objects
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasBK committed May 18, 2024
1 parent ee7fe29 commit 949e894
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 6 deletions.
15 changes: 11 additions & 4 deletions cmd/api/src/daemons/datapipe/agi.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/specterops/bloodhound/log"
"github.com/specterops/bloodhound/src/database"
"github.com/specterops/bloodhound/src/model"
"github.com/specterops/bloodhound/src/model/appcfg"
"github.com/specterops/bloodhound/src/services/agi"
)

Expand Down Expand Up @@ -154,20 +155,26 @@ func ParallelTagAzureTierZero(ctx context.Context, db graph.Database) error {
return nil
}

func TagActiveDirectoryTierZero(ctx context.Context, db graph.Database) error {
func TagActiveDirectoryTierZero(ctx context.Context, db database.Database, graphDB graph.Database) error {
defer log.Measure(log.LevelInfo, "Finished tagging Active Directory Tier Zero")()

if domains, err := adAnalysis.FetchAllDomains(ctx, db); err != nil {
autoTagT0ParentObjectsFlag, err := db.GetFlagByKey(ctx, appcfg.FeatureAutoTagT0ParentObjects)
if err != nil {
log.Errorf("error getting AutoTagT0ParentObjects feature flag: %w", err)
return err
}

if domains, err := adAnalysis.FetchAllDomains(ctx, graphDB); err != nil {
return err
} else {
for _, domain := range domains {
if roots, err := adAnalysis.FetchActiveDirectoryTierZeroRoots(ctx, db, domain); err != nil {
if roots, err := adAnalysis.FetchActiveDirectoryTierZeroRoots(ctx, graphDB, domain, autoTagT0ParentObjectsFlag.Enabled); err != nil {
return err
} else {
properties := graph.NewProperties()
properties.Set(common.SystemTags.String(), ad.AdminTierZero)

if err := db.WriteTransaction(ctx, func(tx graph.Transaction) error {
if err := graphDB.WriteTransaction(ctx, func(tx graph.Transaction) error {
return tx.Nodes().Filter(query.InIDs(query.Node(), roots.IDs()...)).Update(properties)
}); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/daemons/datapipe/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func RunAnalysisOperations(ctx context.Context, db database.Database, graphDB gr
collectedErrors = append(collectedErrors, fmt.Errorf("asset group isolation tagging failed: %w", err))
}

if err := TagActiveDirectoryTierZero(ctx, graphDB); err != nil {
if err := TagActiveDirectoryTierZero(ctx, db, graphDB); err != nil {
collectedErrors = append(collectedErrors, fmt.Errorf("active directory tier zero tagging failed: %w", err))
}

Expand Down
8 changes: 8 additions & 0 deletions cmd/api/src/model/appcfg/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
FeatureAdcs = "adcs"
FeatureClearGraphData = "clear_graph_data"
FeatureRiskExposureNewCalculation = "risk_exposure_new_calculation"
FeatureAutoTagT0ParentObjects = "auto_tag_t0_parent_objects"
)

// AvailableFlags returns a FeatureFlagSet of expected feature flags. Feature flag defaults introduced here will become the initial
Expand Down Expand Up @@ -101,6 +102,13 @@ func AvailableFlags() FeatureFlagSet {
Enabled: false,
UserUpdatable: false,
},
FeatureAutoTagT0ParentObjects: {
Key: FeatureAutoTagT0ParentObjects,
Name: "Automatically add parent OUs and containers of Tier Zero AD objects to Tier Zero",
Description: "Parent OUs and containers of Tier Zero AD objects are automatically added to Tier Zero during analysis. Containers are only added if they have a Tier Zero child object with ACL inheritance enabled.",
Enabled: true,
UserUpdatable: true,
},
}
}

Expand Down
84 changes: 83 additions & 1 deletion packages/go/analysis/ad/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ func FetchAllEnforcedGPOs(ctx context.Context, db graph.Database, targets graph.
})
}

func FetchOUContainers(ctx context.Context, db graph.Database, targets graph.NodeSet) (graph.NodeSet, error) {
defer log.Measure(log.LevelInfo, "FetchOUContainers")()

oUs := graph.NewNodeSet()

return oUs, db.ReadTransaction(ctx, func(tx graph.Transaction) error {
for _, attackPathRoot := range targets {
if ou, err := FetchOUContainersOfNode(tx, attackPathRoot); err != nil {
return err
} else if ou != nil {
oUs.AddSet(ou)
}
}

return nil
})
}

func FetchAllDomains(ctx context.Context, db graph.Database) ([]*graph.Node, error) {
var (
nodes []*graph.Node
Expand All @@ -91,7 +109,7 @@ func FetchAllDomains(ctx context.Context, db graph.Database) ([]*graph.Node, err
})
}

func FetchActiveDirectoryTierZeroRoots(ctx context.Context, db graph.Database, domain *graph.Node) (graph.NodeSet, error) {
func FetchActiveDirectoryTierZeroRoots(ctx context.Context, db graph.Database, domain *graph.Node, autoTagT0ParentObjectsFlag bool) (graph.NodeSet, error) {
defer log.LogAndMeasure(log.LevelInfo, "FetchActiveDirectoryTierZeroRoots")()

if domainSID, err := domain.Properties.Get(common.ObjectID.String()).String(); err != nil {
Expand Down Expand Up @@ -130,6 +148,32 @@ func FetchActiveDirectoryTierZeroRoots(ctx context.Context, db graph.Database, d
attackPathRoots.AddSet(enforcedGPOs)
}

if (autoTagT0ParentObjectsFlag) {
// Add the OUs to the attack path roots
if ous, err := FetchOUContainers(ctx, db, attackPathRoots); err != nil {
return nil, err
} else {
attackPathRoots.AddSet(ous)
}

// Add the containers to the attack path roots
db.ReadTransaction(ctx, func(tx graph.Transaction) error {
for _, attackPathRoot := range attackPathRoots {

// Do not add container if ACL inheritance is disabled
isACLProtected, err := attackPathRoot.Properties.Get(ad.IsACLProtected.String()).Bool()
if err != nil || !isACLProtected {
if containers, err := FetchContainersOfNode(tx, attackPathRoot); err != nil {
return err
} else if containers != nil {
attackPathRoots.AddSet(containers)
}
}
}
return nil
})
}

// Find all next-tier assets
return attackPathRoots, nil
}
Expand Down Expand Up @@ -445,6 +489,44 @@ func FetchEnforcedGPOs(tx graph.Transaction, target *graph.Node, skip, limit int
}
}

func FetchOUContainersOfNode(tx graph.Transaction, target *graph.Node) (graph.NodeSet, error) {
oUContainers := graph.NewNodeSet()
if paths, err := ops.TraversePaths(tx, ops.TraversalPlan{
Root: target,
Direction: graph.DirectionInbound,
BranchQuery: func() graph.Criteria {
return query.And(
query.Kind(query.Start(), ad.OU),
query.Kind(query.Relationship(), ad.Contains),
)
},
}); err != nil {
return nil, err
} else {
oUContainers.AddSet(paths.AllNodes())
}
return oUContainers, nil
}

func FetchContainersOfNode(tx graph.Transaction, target *graph.Node) (graph.NodeSet, error) {
containers := graph.NewNodeSet()
if paths, err := ops.TraversePaths(tx, ops.TraversalPlan{
Root: target,
Direction: graph.DirectionInbound,
BranchQuery: func() graph.Criteria {
return query.And(
query.Kind(query.Start(), ad.Container),
query.Kind(query.Relationship(), ad.Contains),
)
},
}); err != nil {
return nil, err
} else {
containers.AddSet(paths.AllNodes())
}
return containers, nil
}

func CreateOUContainedListDelegate(kind graph.Kind) analysis.ListDelegate {
return func(tx graph.Transaction, node *graph.Node, skip, limit int) (graph.NodeSet, error) {
return ops.AcyclicTraverseTerminals(tx, ops.TraversalPlan{
Expand Down

0 comments on commit 949e894

Please sign in to comment.