diff --git a/cmd/api/src/analysis/ad/post.go b/cmd/api/src/analysis/ad/post.go index 82125b75ba..668e3594be 100644 --- a/cmd/api/src/analysis/ad/post.go +++ b/cmd/api/src/analysis/ad/post.go @@ -18,133 +18,13 @@ package ad import ( "context" - "fmt" "github.com/specterops/bloodhound/analysis" adAnalysis "github.com/specterops/bloodhound/analysis/ad" - "github.com/specterops/bloodhound/analysis/impact" "github.com/specterops/bloodhound/dawgs/graph" - "github.com/specterops/bloodhound/dawgs/util/channels" "github.com/specterops/bloodhound/graphschema/ad" - "github.com/specterops/bloodhound/log" ) -func PostLocalGroups(ctx context.Context, db graph.Database, localGroupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { - var ( - adminGroupSuffix = "-544" - psRemoteGroupSuffix = "-580" - dcomGroupSuffix = "-562" - ) - - if computers, err := adAnalysis.FetchComputers(ctx, db); err != nil { - return &analysis.AtomicPostProcessingStats{}, err - } else { - var ( - threadSafeLocalGroupExpansions = impact.NewThreadSafeAggregator(localGroupExpansions) - operation = analysis.NewPostRelationshipOperation(ctx, db, "LocalGroup Post Processing") - ) - - for idx, computer := range computers.ToArray() { - computerID := graph.ID(computer) - - if idx > 0 && idx%10000 == 0 { - log.Infof("Post processed %d active directory computers", idx) - } - - if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - if entities, err := adAnalysis.FetchLocalGroupBitmapForComputer(tx, computerID, dcomGroupSuffix); err != nil { - return err - } else { - for _, admin := range entities.Slice() { - nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(admin), - ToID: computerID, - Kind: ad.ExecuteDCOM, - } - - if !channels.Submit(ctx, outC, nextJob) { - return nil - } - } - - return nil - } - }); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) - } - - if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - if entities, err := adAnalysis.FetchLocalGroupBitmapForComputer(tx, computerID, psRemoteGroupSuffix); err != nil { - return err - } else { - for _, admin := range entities.Slice() { - nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(admin), - ToID: computerID, - Kind: ad.CanPSRemote, - } - - if !channels.Submit(ctx, outC, nextJob) { - return nil - } - } - - return nil - } - }); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) - } - - if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - if entities, err := adAnalysis.FetchLocalGroupBitmapForComputer(tx, computerID, adminGroupSuffix); err != nil { - return err - } else { - for _, admin := range entities.Slice() { - nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(admin), - ToID: computerID, - Kind: ad.AdminTo, - } - - if !channels.Submit(ctx, outC, nextJob) { - return nil - } - } - - return nil - } - }); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) - } - - if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { - if entities, err := adAnalysis.FetchRDPEntityBitmapForComputerWithUnenforcedURA(tx, computerID, threadSafeLocalGroupExpansions); err != nil { - return err - } else { - for _, rdp := range entities.Slice() { - nextJob := analysis.CreatePostRelationshipJob{ - FromID: graph.ID(rdp), - ToID: computerID, - Kind: ad.CanRDP, - } - - if !channels.Submit(ctx, outC, nextJob) { - return nil - } - } - } - - return nil - }); err != nil { - return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) - } - } - - log.Infof("Finished post-processing %d active directory computers", computers.GetCardinality()) - return &operation.Stats, operation.Done() - } -} - func Post(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessingStats, error) { aggregateStats := analysis.NewAtomicPostProcessingStats() if stats, err := analysis.DeleteTransitEdges(ctx, db, ad.Entity, ad.Entity, adAnalysis.PostProcessedRelationships()...); err != nil { @@ -155,7 +35,7 @@ func Post(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessin return &aggregateStats, err } else if groupExpansions, err := adAnalysis.ExpandAllRDPLocalGroups(ctx, db); err != nil { return &aggregateStats, err - } else if localGroupStats, err := PostLocalGroups(ctx, db, groupExpansions); err != nil { + } else if localGroupStats, err := adAnalysis.PostLocalGroups(ctx, db, groupExpansions); err != nil { return &aggregateStats, err } else if adcsStats, err := adAnalysis.PostADCS(ctx, db, groupExpansions); err != nil { return &aggregateStats, err diff --git a/packages/go/analysis/ad/post.go b/packages/go/analysis/ad/post.go index a24546ac53..203d42f59c 100644 --- a/packages/go/analysis/ad/post.go +++ b/packages/go/analysis/ad/post.go @@ -18,6 +18,7 @@ package ad import ( "context" + "fmt" "github.com/RoaringBitmap/roaring/roaring64" "github.com/specterops/bloodhound/analysis" @@ -190,6 +191,122 @@ func getLAPSComputersForDomain(tx graph.Transaction, domain *graph.Node) ([]grap } } +func PostLocalGroups(ctx context.Context, db graph.Database, localGroupExpansions impact.PathAggregator) (*analysis.AtomicPostProcessingStats, error) { + var ( + adminGroupSuffix = "-544" + psRemoteGroupSuffix = "-580" + dcomGroupSuffix = "-562" + ) + + if computers, err := FetchComputers(ctx, db); err != nil { + return &analysis.AtomicPostProcessingStats{}, err + } else { + var ( + threadSafeLocalGroupExpansions = impact.NewThreadSafeAggregator(localGroupExpansions) + operation = analysis.NewPostRelationshipOperation(ctx, db, "LocalGroup Post Processing") + ) + + for idx, computer := range computers.ToArray() { + computerID := graph.ID(computer) + + if idx > 0 && idx%10000 == 0 { + log.Infof("Post processed %d active directory computers", idx) + } + + if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + if entities, err := FetchLocalGroupBitmapForComputer(tx, computerID, dcomGroupSuffix); err != nil { + return err + } else { + for _, admin := range entities.Slice() { + nextJob := analysis.CreatePostRelationshipJob{ + FromID: graph.ID(admin), + ToID: computerID, + Kind: ad.ExecuteDCOM, + } + + if !channels.Submit(ctx, outC, nextJob) { + return nil + } + } + + return nil + } + }); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) + } + + if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + if entities, err := FetchLocalGroupBitmapForComputer(tx, computerID, psRemoteGroupSuffix); err != nil { + return err + } else { + for _, admin := range entities.Slice() { + nextJob := analysis.CreatePostRelationshipJob{ + FromID: graph.ID(admin), + ToID: computerID, + Kind: ad.CanPSRemote, + } + + if !channels.Submit(ctx, outC, nextJob) { + return nil + } + } + + return nil + } + }); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) + } + + if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + if entities, err := FetchLocalGroupBitmapForComputer(tx, computerID, adminGroupSuffix); err != nil { + return err + } else { + for _, admin := range entities.Slice() { + nextJob := analysis.CreatePostRelationshipJob{ + FromID: graph.ID(admin), + ToID: computerID, + Kind: ad.AdminTo, + } + + if !channels.Submit(ctx, outC, nextJob) { + return nil + } + } + + return nil + } + }); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) + } + + if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error { + if entities, err := FetchRDPEntityBitmapForComputerWithUnenforcedURA(tx, computerID, threadSafeLocalGroupExpansions); err != nil { + return err + } else { + for _, rdp := range entities.Slice() { + nextJob := analysis.CreatePostRelationshipJob{ + FromID: graph.ID(rdp), + ToID: computerID, + Kind: ad.CanRDP, + } + + if !channels.Submit(ctx, outC, nextJob) { + return nil + } + } + } + + return nil + }); err != nil { + return &analysis.AtomicPostProcessingStats{}, fmt.Errorf("failed submitting reader for operation involving computer %d: %w", computerID, err) + } + } + + log.Infof("Finished post-processing %d active directory computers", computers.GetCardinality()) + return &operation.Stats, operation.Done() + } +} + func ExpandLocalGroupMembership(tx graph.Transaction, candidates graph.NodeSet) (graph.NodeSet, error) { if paths, err := ExpandLocalGroupMembershipPaths(tx, candidates); err != nil { return nil, err diff --git a/packages/go/dawgs/graph/mocks/graph.go b/packages/go/dawgs/graph/mocks/graph.go index f2bc543dee..0dfb17b396 100644 --- a/packages/go/dawgs/graph/mocks/graph.go +++ b/packages/go/dawgs/graph/mocks/graph.go @@ -186,6 +186,21 @@ func (mr *MockPropertyValueMockRecorder) String() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockPropertyValue)(nil).String)) } +// StringSlice mocks base method. +func (m *MockPropertyValue) StringSlice() ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StringSlice") + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StringSlice indicates an expected call of StringSlice. +func (mr *MockPropertyValueMockRecorder) StringSlice() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StringSlice", reflect.TypeOf((*MockPropertyValue)(nil).StringSlice)) +} + // Time mocks base method. func (m *MockPropertyValue) Time() (time.Time, error) { m.ctrl.T.Helper()