Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into BED-3961
Browse files Browse the repository at this point in the history
  • Loading branch information
elikmiller committed Jan 18, 2024
2 parents 80cbc20 + a2fca90 commit 4930f61
Show file tree
Hide file tree
Showing 55 changed files with 2,756 additions and 270 deletions.
149 changes: 132 additions & 17 deletions cmd/api/src/analysis/ad/adcs_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package ad_test

import (
"context"

"github.com/specterops/bloodhound/analysis"

ad2 "github.com/specterops/bloodhound/analysis/ad"
Expand Down Expand Up @@ -55,6 +54,9 @@ func TestADCSESC1(t *testing.T) {
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

Expand All @@ -64,14 +66,8 @@ func TestADCSESC1(t *testing.T) {
return err
} else {
for _, enterpriseCA := range enterpriseCAs {
t.Logf("here is one of the looped over ecas: %v", enterpriseCA.ID)
if validPaths, err := ad2.FetchEnterpriseCAsCertChainPathToDomain(tx, enterpriseCA, innerDomain); err != nil {
t.Logf("error fetching paths from enterprise ca %d to domain %d: %v", enterpriseCA.ID, innerDomain.ID, err)
} else if validPaths.Len() == 0 {
t.Logf("0 valid paths for eca: %v", enterpriseCA.Properties.Get("name"))
continue
} else {
if err := ad2.PostADCSESC1(ctx, tx, outC, db, groupExpansions, enterpriseCertAuthorities, certTemplates, enterpriseCA, innerDomain); err != nil {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
if err := ad2.PostADCSESC1(ctx, tx, outC, groupExpansions, enterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC1.String(), err)
} else {
return nil
Expand Down Expand Up @@ -395,7 +391,7 @@ func TestEnrollOnBehalfOf(t *testing.T) {
results, err := ad2.EnrollOnBehalfOfVersionOne(tx, v1Templates, certTemplates)
require.Nil(t, err)

require.Len(t, results, 2)
require.Len(t, results, 3)

require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate11.ID,
Expand All @@ -409,6 +405,12 @@ func TestEnrollOnBehalfOf(t *testing.T) {
Kind: ad.EnrollOnBehalfOf,
})

require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
ToID: harness.EnrollOnBehalfOfHarnessOne.CertTemplate12.ID,
Kind: ad.EnrollOnBehalfOf,
})

return nil
})

Expand Down Expand Up @@ -442,19 +444,132 @@ func TestEnrollOnBehalfOf(t *testing.T) {
Kind: ad.EnrollOnBehalfOf,
})

results, err = ad2.EnrollOnBehalfOfSelfControl(tx, v1Templates)
require.Nil(t, err)
return nil
})

require.Len(t, results, 1)
require.Contains(t, results, analysis.CreatePostRelationshipJob{
FromID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate25.ID,
ToID: harness.EnrollOnBehalfOfHarnessTwo.CertTemplate25.ID,
Kind: ad.EnrollOnBehalfOf,
return nil
})
}

func TestADCSESC3(t *testing.T) {
testContext := integration.NewGraphTestContext(t)
testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
harness.ESC3Harness1.Setup(testContext)
}, func(harness integration.HarnessDetails, db graph.Database) error {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC3")

groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA)
require.Nil(t, err)
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
if enterpriseCAs, err := ad2.FetchEnterpriseCAsTrustedForNTAuthToDomain(tx, innerDomain); err != nil {
return err
} else {
for _, enterpriseCA := range enterpriseCAs {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
if err := ad2.PostADCSESC3(ctx, tx, outC, groupExpansions, enterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC1.String(), err)
} else {
return nil
}
}
}
}
return nil
})
}
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC3)
})); err != nil {
t.Fatalf("error fetching esc3 edges in integration test; %v", err)
} else {
assert.Equal(t, 3, len(results))

require.True(t, results.Contains(harness.ESC3Harness1.Computer1))
require.True(t, results.Contains(harness.ESC3Harness1.Group2))
require.True(t, results.Contains(harness.ESC3Harness1.User1))

}
return nil
})
return nil
})

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) {
harness.ESC3Harness2.Setup(testContext)
}, func(harness integration.HarnessDetails, db graph.Database) error {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "ADCS Post Process Test - ESC3")

groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
enterpriseCertAuthorities, err := ad2.FetchNodesByKind(context.Background(), db, ad.EnterpriseCA)
require.Nil(t, err)
certTemplates, err := ad2.FetchNodesByKind(context.Background(), db, ad.CertTemplate)
require.Nil(t, err)
domains, err := ad2.FetchNodesByKind(context.Background(), db, ad.Domain)

cache := ad2.NewADCSCache()
cache.BuildCache(context.Background(), db, enterpriseCertAuthorities, certTemplates)

for _, domain := range domains {
innerDomain := domain

operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
if enterpriseCAs, err := ad2.FetchEnterpriseCAsTrustedForNTAuthToDomain(tx, innerDomain); err != nil {
return err
} else {
for _, enterpriseCA := range enterpriseCAs {
if cache.DoesCAChainProperlyToDomain(enterpriseCA, innerDomain) {
if err := ad2.PostADCSESC3(ctx, tx, outC, groupExpansions, enterpriseCA, innerDomain, cache); err != nil {
t.Logf("failed post processing for %s: %v", ad.ADCSESC1.String(), err)
} else {
return nil
}
}
}
}
return nil
})
}
operation.Done()

db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchStartNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC3)
})); err != nil {
t.Fatalf("error fetching esc3 edges in integration test; %v", err)
} else {
assert.Equal(t, 1, len(results))

require.True(t, results.Contains(harness.ESC3Harness2.User1))
}

if edge, err := tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.ADCSESC3)
}).First(); err != nil {
t.Fatalf("error fetching esc3 edges in integration test; %v", err)
} else {
comp, err := ad2.GetADCSESC3EdgeComposition(context.Background(), db, edge)
assert.Nil(t, err)
assert.Equal(t, 8, len(comp.AllNodes()))
assert.False(t, comp.AllNodes().Contains(harness.ESC3Harness2.User2))
}
return nil
})
return nil
})
}
2 changes: 1 addition & 1 deletion cmd/api/src/analysis/post_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestCrossProduct(t *testing.T) {
secondSet := []*graph.Node{testContext.Harness.ShortcutHarness.Group2}
groupExpansions, err := ad2.ExpandAllRDPLocalGroups(context.Background(), db)
require.Nil(t, err)
results := ad2.CalculateCrossProductNodeSets(firstSet, secondSet, groupExpansions)
results := ad2.CalculateCrossProductNodeSets(groupExpansions, firstSet, secondSet)
require.True(t, results.Contains(harness.ShortcutHarness.Group3.ID.Uint32()))

return nil
Expand Down
13 changes: 7 additions & 6 deletions cmd/api/src/api/registration/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package registration

import (
"net/http"

"github.com/specterops/bloodhound/cache"
"github.com/specterops/bloodhound/dawgs/graph"
"github.com/specterops/bloodhound/src/api"
Expand All @@ -28,19 +30,18 @@ import (
"github.com/specterops/bloodhound/src/config"
"github.com/specterops/bloodhound/src/daemons/datapipe"
"github.com/specterops/bloodhound/src/database"
"net/http"
)

func RegisterFossGlobalMiddleware(routerInst *router.Router, cfg config.Configuration, identityResolver auth.IdentityResolver, authenticator api.Authenticator) {
// Set up logging
if cfg.EnableAPILogging {
routerInst.UsePrerouting(middleware.LoggingMiddleware(cfg, identityResolver))
}

// Set up the middleware stack
routerInst.UsePrerouting(middleware.ContextMiddleware)
routerInst.UsePrerouting(middleware.CORSMiddleware())

// Set up logging. This must be done after ContextMiddleware is initialized so the context can be accessed in the log logic
if cfg.EnableAPILogging {
routerInst.UsePrerouting(middleware.LoggingMiddleware(cfg, identityResolver))
}

routerInst.UsePostrouting(
middleware.PanicHandler,
middleware.AuthMiddleware(authenticator),
Expand Down
18 changes: 14 additions & 4 deletions cmd/api/src/database/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,24 @@ func (s *BloodhoundDB) ListAuditLogs(before, after time.Time, offset, limit int,
// This code went through a partial refactor when adding support for new fields.
// See the comments here for more information: https://github.com/SpecterOps/BloodHound/pull/297#issuecomment-1887640827

if filter.SQLString != "" {
result = s.db.Model(&auditLogs).Where(filter.SQLString, filter.Params).Count(&count)
} else {
result = s.db.Model(&auditLogs).Count(&count)
}

if result.Error != nil {
return nil, 0, CheckError(result)
}

if order != "" && filter.SQLString == "" {
result = cursor.Order(order).Find(&auditLogs).Count(&count)
result = cursor.Order(order).Find(&auditLogs)
} else if order != "" && filter.SQLString != "" {
result = cursor.Where(filter.SQLString, filter.Params).Order(order).Find(&auditLogs).Count(&count)
result = cursor.Where(filter.SQLString, filter.Params).Order(order).Find(&auditLogs)
} else if order == "" && filter.SQLString != "" {
result = cursor.Where(filter.SQLString, filter.Params).Find(&auditLogs).Count(&count)
result = cursor.Where(filter.SQLString, filter.Params).Find(&auditLogs)
} else {
result = cursor.Find(&auditLogs).Count(&count)
result = cursor.Find(&auditLogs)
}

return auditLogs, int(count), CheckError(result)
Expand Down
73 changes: 73 additions & 0 deletions cmd/api/src/database/audit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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

//go:build integration
// +build integration

package database_test

import (
"github.com/specterops/bloodhound/src/auth"
"github.com/specterops/bloodhound/src/ctx"
"github.com/specterops/bloodhound/src/model"
"github.com/specterops/bloodhound/src/test/integration"
"testing"
"time"
)

func TestDatabase_ListAuditLogs(t *testing.T) {
var (
dbInst = integration.OpenDatabase(t)

auditLogIdFilter = model.QueryParameterFilter{
Name: "id",
Operator: model.GreaterThan,
Value: "4",
IsStringData: false,
}
auditLogIdFilterMap = model.QueryParameterFilterMap{auditLogIdFilter.Name: model.QueryParameterFilters{auditLogIdFilter}}
)

if err := integration.Prepare(dbInst); err != nil {
t.Fatalf("Failed preparing DB: %v", err)
}

mockCtx := ctx.Context{
RequestID: "requestID",
AuthCtx: auth.Context{
Owner: model.User{},
Session: model.UserSession{},
},
}
for i := 0; i < 7; i++ {
if err := dbInst.AppendAuditLog(mockCtx, "CreateUser", model.User{}); err != nil {
t.Fatalf("Error creating audit log: %v", err)
}
}

if _, count, err := dbInst.ListAuditLogs(time.Now(), time.Now(), 0, 10, "", model.SQLFilter{}); err != nil {
t.Fatalf("Failed to list all audit logs: %v", err)
} else if count != 7 {
t.Fatalf("Expected 7 audit logs to be returned")
} else if filter, err := auditLogIdFilterMap.BuildSQLFilter(); err != nil {
t.Fatalf("Failed to generate SQL Filter: %v", err)
// Limit is set to 1 to verify that count is total filtered count, not response size
} else if _, count, err = dbInst.ListAuditLogs(time.Now(), time.Now(), 0, 1, "", filter); err != nil {
t.Fatalf("Failed to list filtered events: %v", err)
} else if count != 3 {
t.Fatalf("Expected 3 audit logs to be returned")
}
}
17 changes: 10 additions & 7 deletions cmd/api/src/database/saved_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,33 @@ package database
import (
"github.com/gofrs/uuid"
"github.com/specterops/bloodhound/src/model"
"gorm.io/gorm"
)

func (s *BloodhoundDB) ListSavedQueries(userID uuid.UUID, order string, filter model.SQLFilter, skip, limit int) (model.SavedQueries, int, error) {
var (
queries model.SavedQueries
result *gorm.DB
count int64
cursor = s.Scope(Paginate(skip, limit)).Where("user_id = ?", userID)
)

cursor := s.Scope(Paginate(skip, limit)).Where("user_id = ?", userID)

if filter.SQLString != "" {
cursor = cursor.Where(filter.SQLString, filter.Params)
result = s.db.Model(&queries).Where("user_id = ?", userID).Where(filter.SQLString, filter.Params).Count(&count)
} else {
result = s.db.Model(&queries).Where("user_id = ?", userID).Count(&count)
}

if order != "" {
cursor = cursor.Order(order)
}

result := s.db.Where("user_id = ?", userID).Find(&queries).Count(&count)
if result.Error != nil {
return queries, 0, result.Error
}

if order != "" {
cursor = cursor.Order(order)
}
result = cursor.Find(&queries)

return queries, int(count), CheckError(result)
}

Expand Down
Loading

0 comments on commit 4930f61

Please sign in to comment.