Skip to content

Commit

Permalink
BED-5036 implement post processing for CoerceAndRelayNTLMToSMB (#1015)
Browse files Browse the repository at this point in the history
* BED-5036 implement post processing for CoerceAndRelayNTLMToSMB
  • Loading branch information
mvlipka authored Jan 8, 2025
1 parent 5c5c503 commit 3d45e28
Show file tree
Hide file tree
Showing 10 changed files with 544 additions and 5 deletions.
138 changes: 138 additions & 0 deletions cmd/api/src/analysis/ad/ntlm_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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

//go:build integration
// +build integration

package ad_test

import (
"context"
"strings"
"testing"

"github.com/specterops/bloodhound/analysis"
ad2 "github.com/specterops/bloodhound/analysis/ad"
"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"
"github.com/specterops/bloodhound/graphschema/ad"
"github.com/specterops/bloodhound/src/test/integration"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPostNTLM(t *testing.T) {
testContext := integration.NewGraphTestContext(t, graphschema.DefaultGraphSchema())

testContext.DatabaseTestWithSetup(func(harness *integration.HarnessDetails) error {
harness.NTLMCoerceAndRelayNTLMToSMB.Setup(testContext)
return nil
}, func(harness integration.HarnessDetails, db graph.Database) {
operation := analysis.NewPostRelationshipOperation(context.Background(), db, "NTLM Post Process Test - CoerceAndRelayNTLMToSMB")

groupExpansions, computers, domains, authenticatedUsers, err := fetchNTLMPrereqs(db)
require.NoError(t, err)

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

err = operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
for _, computer := range computers {
innerComputer := computer
domainSid, _ := innerDomain.Properties.Get(ad.Domain.String()).String()
authenticatedUserID := authenticatedUsers[domainSid]

if err = ad2.PostCoerceAndRelayNTLMToSMB(tx, outC, groupExpansions, innerComputer, authenticatedUserID); err != nil {
t.Logf("failed post processig for %s: %v", ad.CoerceAndRelayNTLMToSMB.String(), err)
}
}
return nil
})
require.NoError(t, err)
}

err = operation.Done()
require.NoError(t, err)

// Test start node
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.CoerceAndRelayNTLMToSMB)
})); err != nil {
t.Fatalf("error fetching ntlm to smb edges in integration test; %v", err)
} else {
require.Len(t, results, 1)
resultIds := results.IDs()

objectId := results.Get(resultIds[0]).Properties.Get("objectid")
require.False(t, objectId.IsNil())

objectIdStr, err := objectId.String()
require.NoError(t, err)
assert.True(t, strings.HasSuffix(objectIdStr, ad2.AuthenticatedUsersSuffix))
}
return nil
})

// Test end node
db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if results, err := ops.FetchEndNodes(tx.Relationships().Filterf(func() graph.Criteria {
return query.Kind(query.Relationship(), ad.CoerceAndRelayNTLMToSMB)
})); err != nil {
t.Fatalf("error fetching ntlm to smb edges in integration test; %v", err)
} else {
require.Len(t, results, 1)
resultIds := results.IDs()

objectId := results.Get(resultIds[0]).Properties.Get("objectid")
require.False(t, objectId.IsNil())

smbSigning, err := results.Get(resultIds[0]).Properties.Get(ad.SMBSigning.String()).Bool()
require.NoError(t, err)

restrictOutbountNtlm, err := results.Get(resultIds[0]).Properties.Get(ad.RestrictOutboundNTLM.String()).Bool()
require.NoError(t, err)

assert.False(t, smbSigning)
assert.False(t, restrictOutbountNtlm)
}
return nil
})
})
}

func fetchNTLMPrereqs(db graph.Database) (expansions impact.PathAggregator, computers []*graph.Node, domains []*graph.Node, authenticatedUsers map[string]graph.ID, err error) {
cache := make(map[string]graph.ID)
if expansions, err = ad2.ExpandAllRDPLocalGroups(context.Background(), db); err != nil {
return nil, nil, nil, cache, err
} else if computers, err = ad2.FetchNodesByKind(context.Background(), db, ad.Computer); err != nil {
return nil, nil, nil, cache, err
} else if err = db.ReadTransaction(context.Background(), func(tx graph.Transaction) error {
if cache, err = ad2.FetchAuthUsersMappedToDomains(tx); err != nil {
return err
}
return nil
}); err != nil {
return nil, nil, nil, cache, err
} else if domains, err = ad2.FetchNodesByKind(context.Background(), db, ad.Domain); err != nil {
return nil, nil, nil, cache, err
} else {
return expansions, computers, domains, cache, nil
}
}
2 changes: 1 addition & 1 deletion cmd/api/src/api/v2/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
"github.com/specterops/bloodhound/log"
"github.com/specterops/bloodhound/mediatypes"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/api/v2"
v2 "github.com/specterops/bloodhound/src/api/v2"
"github.com/specterops/bloodhound/src/config"
"github.com/specterops/bloodhound/src/ctx"
"github.com/specterops/bloodhound/src/database"
Expand Down
2 changes: 1 addition & 1 deletion cmd/api/src/api/v2/auth/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
"github.com/specterops/bloodhound/log"
"github.com/specterops/bloodhound/mediatypes"
"github.com/specterops/bloodhound/src/api"
"github.com/specterops/bloodhound/src/api/v2"
v2 "github.com/specterops/bloodhound/src/api/v2"
"github.com/specterops/bloodhound/src/auth"
"github.com/specterops/bloodhound/src/ctx"
"github.com/specterops/bloodhound/src/database"
Expand Down
37 changes: 37 additions & 0 deletions cmd/api/src/test/integration/harnesses.go
Original file line number Diff line number Diff line change
Expand Up @@ -8456,6 +8456,42 @@ func (s *ESC10bHarnessDC2) Setup(graphTestContext *GraphTestContext) {
graphTestContext.UpdateNode(s.DC1)
}

type NTLMCoerceAndRelayNTLMToSMB struct {
AuthenticatedUsers *graph.Node
DomainAdminsUser *graph.Node
ServerAdmins *graph.Node
computer3 *graph.Node
computer8 *graph.Node
}

func (s *NTLMCoerceAndRelayNTLMToSMB) Setup(graphTestContext *GraphTestContext) {
domainSid := RandomDomainSID()
s.AuthenticatedUsers = graphTestContext.NewActiveDirectoryGroup("Authenticated Users", domainSid)
s.AuthenticatedUsers.Properties.Set("objectid", fmt.Sprintf("authenticated-users%s", adAnalysis.AuthenticatedUsersSuffix))
s.AuthenticatedUsers.Properties.Set("Domain", domainSid)
graphTestContext.UpdateNode(s.AuthenticatedUsers)

s.DomainAdminsUser = graphTestContext.NewActiveDirectoryUser("Domain Admins User", domainSid)

s.ServerAdmins = graphTestContext.NewActiveDirectoryDomain("Server Admins", domainSid, false, true)
s.ServerAdmins.Properties.Set("objectid", fmt.Sprintf("server-admins%s", adAnalysis.AuthenticatedUsersSuffix))
s.ServerAdmins.Properties.Set("Domain", domainSid)
graphTestContext.UpdateNode(s.ServerAdmins)

s.DomainAdminsUser.Properties.Set("objectid", fmt.Sprintf("domainadminuser-users%s", adAnalysis.AuthenticatedUsersSuffix))
s.computer3 = graphTestContext.NewActiveDirectoryComputer("computer3", domainSid)

s.computer8 = graphTestContext.NewActiveDirectoryComputer("computer8", domainSid)
s.computer8.Properties.Set(ad.SMBSigning.String(), false)
s.computer8.Properties.Set(ad.RestrictOutboundNTLM.String(), false)
graphTestContext.UpdateNode(s.computer8)

graphTestContext.NewRelationship(s.computer3, s.ServerAdmins, ad.MemberOf)
graphTestContext.NewRelationship(s.ServerAdmins, s.computer8, ad.AdminTo)
graphTestContext.NewRelationship(s.AuthenticatedUsers, s.computer8, ad.CoerceAndRelayNTLMToSMB)
graphTestContext.NewRelationship(s.computer8, s.DomainAdminsUser, ad.HasSession)
}

type HarnessDetails struct {
RDP RDPHarness
RDPB RDPHarness2
Expand Down Expand Up @@ -8555,4 +8591,5 @@ type HarnessDetails struct {
DCSyncHarness DCSyncHarness
SyncLAPSPasswordHarness SyncLAPSPasswordHarness
HybridAttackPaths HybridAttackPaths
NTLMCoerceAndRelayNTLMToSMB NTLMCoerceAndRelayNTLMToSMB
}
150 changes: 150 additions & 0 deletions cmd/api/src/test/integration/harnesses/CoerceAndRelayNTLMToSMB.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
{
"style": {
"font-family": "sans-serif",
"background-color": "#ffffff",
"background-image": "",
"background-size": "100%",
"node-color": "#ffffff",
"border-width": 4,
"border-color": "#000000",
"radius": 50,
"node-padding": 5,
"node-margin": 2,
"outside-position": "auto",
"node-icon-image": "",
"node-background-image": "",
"icon-position": "outside",
"icon-size": 64,
"caption-position": "inside",
"caption-max-width": 200,
"caption-color": "#000000",
"caption-font-size": 50,
"caption-font-weight": "normal",
"label-position": "inside",
"label-display": "pill",
"label-color": "#000000",
"label-background-color": "#ffffff",
"label-border-color": "#000000",
"label-border-width": 4,
"label-font-size": 40,
"label-padding": 5,
"label-margin": 4,
"directionality": "directed",
"detail-position": "inline",
"detail-orientation": "parallel",
"arrow-width": 5,
"arrow-color": "#000000",
"margin-start": 5,
"margin-end": 5,
"margin-peer": 20,
"attachment-start": "normal",
"attachment-end": "normal",
"relationship-icon-image": "",
"type-color": "#000000",
"type-background-color": "#ffffff",
"type-border-color": "#000000",
"type-border-width": 0,
"type-font-size": 16,
"type-padding": 5,
"property-position": "outside",
"property-alignment": "colon",
"property-color": "#000000",
"property-font-size": 16,
"property-font-weight": "normal"
},
"nodes": [
{
"id": "n0",
"position": {
"x": 0,
"y": 0
},
"caption": "computer3",
"style": {},
"labels": [],
"properties": {}
},
{
"id": "n1",
"position": {
"x": 284.5,
"y": 0
},
"caption": "Server Admins",
"style": {},
"labels": [],
"properties": {}
},
{
"id": "n2",
"position": {
"x": 485.67187924757275,
"y": -201.17187924757275
},
"caption": "computer8",
"style": {},
"labels": [],
"properties": {
"smb_signing": "false"
}
},
{
"id": "n3",
"position": {
"x": 0,
"y": -201.17187924757275
},
"caption": "Authenticated Users",
"style": {},
"labels": [],
"properties": {
"objectid": "authenticatedusers-S-1-5-11"
}
},
{
"id": "n4",
"position": {
"x": 665.8359396237863,
"y": -381.3359396237863
},
"caption": "Domain Admins User",
"style": {},
"labels": [],
"properties": {}
}
],
"relationships": [
{
"id": "n0",
"type": "MemberOf",
"style": {},
"properties": {},
"fromId": "n0",
"toId": "n1"
},
{
"id": "n1",
"type": "AdminTo",
"style": {},
"properties": {},
"fromId": "n1",
"toId": "n2"
},
{
"id": "n2",
"type": "CoerceAndRelayNTLMToSMB",
"style": {},
"properties": {},
"fromId": "n3",
"toId": "n2"
},
{
"id": "n3",
"type": "HasSession",
"style": {},
"properties": {},
"fromId": "n2",
"toId": "n4"
}
]
}
18 changes: 18 additions & 0 deletions cmd/api/src/test/integration/harnesses/CoerceAndRelayNTLMToSMB.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 3d45e28

Please sign in to comment.