Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Daviditkin bhe solution #11569

Merged
merged 52 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f350933
Updating urls to reference daviditkin/Azure-Sentinel the daviditkin-b…
daviditkin Nov 29, 2024
2b37854
updated deploy url to daviditkin
daviditkin Nov 29, 2024
396206a
updated urls
daviditkin Nov 29, 2024
96e8d2f
config error
daviditkin Nov 29, 2024
613a532
remove local debug mods. oops
daviditkin Nov 29, 2024
d261e3a
map path_type to path title for aggregate data data_type finding_export
daviditkin Dec 2, 2024
a19d7c0
typo minor
daviditkin Dec 2, 2024
90ee848
forgot to push bhe-funcapp.zip
daviditkin Dec 2, 2024
cff2254
clean up path title
daviditkin Dec 2, 2024
5b6b7c4
debugging missing path_title for finding_export records.
daviditkin Dec 2, 2024
ada828f
Modifications to workbooks based on craig comments. Not Audit yet.
daviditkin Dec 2, 2024
a0d4ee7
Exposure % and max % correct
daviditkin Dec 2, 2024
89ecf71
remove hardcoded domain when getting path titles.
daviditkin Dec 2, 2024
51dc45f
Updated length of table and width of columns
daviditkin Dec 2, 2024
abe19d3
Time fixed, always show all audit log types regardless of time range.
daviditkin Dec 2, 2024
ef52093
Types are now based on time range selected.
daviditkin Dec 2, 2024
65f9410
Posture times shown in y axis correctly.
daviditkin Dec 2, 2024
93e9e80
Audit logs should show all the possible event types that are in the a…
daviditkin Dec 2, 2024
8f3f1db
Allows custom time range and type selection supports all.
daviditkin Dec 4, 2024
ea2f57a
refactor retrieving asset data for use by tier0 principal name lookup
daviditkin Dec 4, 2024
26e47da
minor check error
daviditkin Dec 4, 2024
19a50a6
tier0 data now properly calculated / filtered
daviditkin Dec 5, 2024
1081c86
New labels
daviditkin Dec 6, 2024
5ad84c3
connector and tier0 mods
daviditkin Dec 6, 2024
b8d3148
skipping any errors retrieving tier0 data, like with audit. TODO pro…
daviditkin Dec 6, 2024
e7e933a
forgot zip deployed funcapp
daviditkin Dec 6, 2024
0143f85
simplify. Are domainsid and domainid redundant?
daviditkin Dec 6, 2024
2f2d8da
update zip deploy of funcapp
daviditkin Dec 6, 2024
dde3a1a
fixed batch size calc.
daviditkin Dec 6, 2024
f56fb74
new zip deploy
daviditkin Dec 6, 2024
175c80c
Batch size calculation simpler and correct
daviditkin Dec 8, 2024
8e36672
Remove some chatty logging
daviditkin Dec 8, 2024
9eba4cb
update zip deploy files
daviditkin Dec 8, 2024
1f77fb8
Fixes to queries / columns. Still need to standardize exposure value…
daviditkin Dec 9, 2024
62a72b5
update funcapp zip deployment
daviditkin Dec 9, 2024
01f3fe8
Corrected range for exposure.
daviditkin Dec 9, 2024
96e770c
ways to document schema
daviditkin Dec 10, 2024
6cd93e0
SOLST-43 proper lookup of Tier 0 asset group
daviditkin Dec 12, 2024
0056e8a
update funcapp zip deploy
daviditkin Dec 12, 2024
a0af05e
tier0 deduplication
daviditkin Dec 12, 2024
a828b2e
Use appropriate data_type and enusre domain_name is available for map…
daviditkin Dec 13, 2024
8f48443
Merge branch 'master' into daviditkin-bhe-solution
daviditkin Dec 16, 2024
396e32e
Updated Workbook Images
daviditkin Dec 16, 2024
07ced1e
Updated version of workbooks and analytic rules.
daviditkin Dec 16, 2024
6a23147
Fix github paths
daviditkin Dec 16, 2024
c7503cc
compile function and add to deploy zip
daviditkin Dec 16, 2024
c150e4a
Merge branch 'master' into daviditkin-bhe-solution
daviditkin Dec 17, 2024
31680e7
merged from main and updated ReleaseNotes.md
daviditkin Dec 17, 2024
9369c66
minor preview image name change
daviditkin Dec 17, 2024
4b1f273
changed func app scheduling to twice per day
daviditkin Dec 18, 2024
40de4b5
Solution packaged
v-prasadboke Dec 23, 2024
28de1bd
Update 3.1.0.zip
v-prasadboke Dec 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ tactics: []
relevantTechniques: []
query: |-
BloodHoundLogs_CL
| where data_type == "posture"
| where data_type == "finding_export"
| where created_at > ago (7d)
| summarize min_critical_risk_count = min(finding_count), arg_max(created_at, current_critical_risk_count = finding_count) by domain_name
| summarize min_critical_risk_count = min(finding_count), arg_max(created_at, current_critical_risk_count = finding_count, domain_name) by domain_name
| extend difference = current_critical_risk_count - min_critical_risk_count
| where difference > 0
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
displayName: domain_name
version: 1.1.0
kind: Scheduled
version: 1.2.0
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ query: |-
BloodHoundLogs_CL
| where data_type == "posture"
| where created_at > ago (7d)
| summarize min(exposure_index), arg_max(created_at, exposure_index) by domain_name
| summarize min(exposure_index), arg_max(created_at, exposure_index, domain_name) by domain_name
| extend min_exposure = min_exposure_index * 100, latest_exposure = exposure_index * 100
| where latest_exposure - min_exposure > 5
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
displayName: domain_name
version: 1.1.0
kind: Scheduled
version: 1.2.0
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ query: |-
BloodHoundLogs_CL
| where data_type == "posture"
| where created_at > ago (7d)
| summarize min_tier_zero = min(tier_zero_count), max_tier_zero = arg_max(created_at, current_tier_zero = tier_zero_count) by domain_name
| summarize min_tier_zero = min(tier_zero_count), max_tier_zero = arg_max(created_at, current_tier_zero = tier_zero_count, domain_name) by domain_name
| extend percent_difference = ((current_tier_zero - min_tier_zero) / min_tier_zero) * 100
| where percent_difference > 5
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
displayName: domain_name
version: 1.1.0
kind: Scheduled
version: 1.2.0
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"name": "myTimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 6,18 * * *"
"schedule": "0 0 1,13 * * *"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
},
{
"title": "",
"description": "**1. Deploy a Function App**\n\n> **NOTE:** You will need to [prepare VS code](https://learn.microsoft.com/azure/azure-functions/create-first-function-vs-code-other#configure-your-environment) for Azure function development.\n\n1. Download the [Azure Function App](https://github.com/Azure/Azure-Sentinel/raw/refs/heads/master/Solutions/BloodHound%20Enterprise/Data%20Connectors/bhe-funcapp.zip) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Build the function using the [following instructions](https://learn.microsoft.com/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cmacos#compile-the-custom-handler-for-azure) \n4. Select the top level folder from extracted files.\n5. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n6. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. bloodhoundenterpriseXX).\n\n\te. **Select a runtime:** Choose Custom.\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft Sentinel is located.\n\n7. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n8. Go to Azure Portal for the Function App configuration."
"description": "**1. Deploy a Function App**\n\n> **NOTE:** You will need to [prepare VS code](https://learn.microsoft.com/azure/azure-functions/create-first-function-vs-code-other#configure-your-environment) for Azure function development.\n\n1. Download the [Azure Function App](https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Solutions/BloodHound%20Enterprise/Data%20Connectors/bhe-funcapp.zip) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Build the function using the [following instructions](https://learn.microsoft.com/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cmacos#compile-the-custom-handler-for-azure) \n4. Select the top level folder from extracted files.\n5. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n6. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. bloodhoundenterpriseXX).\n\n\te. **Select a runtime:** Choose Custom.\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft Sentinel is located.\n\n7. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n8. Go to Azure Portal for the Function App configuration."
},
{
"title": "",
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func helloHandler(w http.ResponseWriter, r *http.Request) {
}

// Initialize the Bloodhound Client connection
// TODO: have our hown bloodhound client type that contains key, domain etc.
bhClient, err := InitializeBloodhoundClient(config.BloodhoundAPIKey,
config.BloodhoundAPIKeyId,
config.BloodhoundDomain)
Expand Down Expand Up @@ -181,7 +182,7 @@ func helloHandler(w http.ResponseWriter, r *http.Request) {
//
// do stuff here
//
logs, err := connector.UploadLogsCallback(bhClient, lastRunTime, azureClient, config.RuleId, config.MAX_UPLOAD_SIZE)
logs, err := connector.UploadLogsCallback(bhClient, config.BloodhoundDomain, lastRunTime, azureClient, config.RuleId, config.MAX_UPLOAD_SIZE)
if err != nil {
sendError(w, fmt.Sprintf("Error in connector: %s logs: %v key: %s keyId: %s", err.Error(), logs, config.BloodhoundAPIKey, config.BloodhoundAPIKeyId))
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package bloodhound
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
Expand Down Expand Up @@ -35,28 +34,41 @@ func InitializeBloodhoundClient(apiKey string, apikeyId string, bloodhoundServer
return client, nil
}

func GetTierZeroPrincipal(client *sdk.ClientWithResponses) (*sdk.ModelUnifiedGraphGraph, error) {
query := "match (n) where n.system_tags contains(\"admin_tier_0\") return n"
x := true
queryBody := sdk.RunCypherQueryJSONRequestBody{
IncludeProperties: &x,
Query: &query,
func GetTierZeroGroup(client *sdk.ClientWithResponses) (int32, error) {
tier0_tag := "eq:admin_tier_0"
params := sdk.ListAssetGroupsParams {
Tag: &tier0_tag,
}
const TIER_ZERO_ASSET_GROUP int64 = 1
response, err := client.ListAssetGroupsWithResponse(context.TODO(), &params)
if err != nil {
return 0, fmt.Errorf("Error getting tier zero group %v", err)
}
if response.StatusCode() != http.StatusOK {
return 0, fmt.Errorf("Error getting tier zero group %s", response.Status())
}
if len(*response.JSON200.Data.AssetGroups) == 1 && (*response.JSON200.Data.AssetGroups)[0].Id != nil {
return *((*response.JSON200.Data.AssetGroups)[0].Id), nil
}
return 0, fmt.Errorf("Error getting tier zero group. Expected 1 group, got %d", len(*response.JSON200.Data.AssetGroups))
}

// assetgrouprspones, err := client.GetAssetGroupWithResponse((context.TODO(), TIERint32(TIER_ZERO_ASSET_GROUP), nil))
response, err := client.RunCypherQueryWithResponse(context.TODO(), nil, queryBody)
func GetTierZeroPrincipals(client *sdk.ClientWithResponses, tierZeroGroup int32) ([]sdk.ModelAssetGroupMember, error) {
limit := 10000
params := &sdk.ListAssetGroupMembersParams{
Limit: &limit,
}
response, err := client.ListAssetGroupMembersWithResponse(context.TODO(), tierZeroGroup, params)
if err != nil {
return nil, err
return []sdk.ModelAssetGroupMember{}, fmt.Errorf("Error getting tier zero principals in group %d %v", tierZeroGroup, err)
}
if response.StatusCode() != 200 {
return nil, errors.New(response.Status())
if response.StatusCode() != http.StatusOK {
return []sdk.ModelAssetGroupMember{}, fmt.Errorf("Error getting tier zero principals in group %d %s", tierZeroGroup, response.Status())
}
return response.JSON200.Data, nil
return *response.JSON200.Data.Members, nil
}

func GetLastAnalysisTime(client *sdk.ClientWithResponses) (*time.Time, error) {
response, err := client.GetDatapipeStatusWithResponse(context.TODO(),nil )
response, err := client.GetDatapipeStatusWithResponse(context.TODO(), nil)
if err != nil {
return nil, fmt.Errorf("Error getting last analysis run time %v", err)
}
Expand Down Expand Up @@ -106,7 +118,6 @@ func GetPostureData(client *sdk.ClientWithResponses, currentState *time.Time) (*
return response.JSON200.Data, nil
}


// Note: there is no last time
func GetAttackPathTypesForDomain(client *sdk.ClientWithResponses, domain_ids []string) (map[string][]string, error) {
m := make(map[string][]string)
Expand Down Expand Up @@ -220,7 +231,6 @@ func GetAuditLog(client *sdk.ClientWithResponses, lastRunTime *time.Time) ([]sdk
}

for _, logEntry := range *response.JSON200.Data.Logs {
log.Printf("%v", logEntry)
// TODO SCRUB LOG
scrubedLogs = append(scrubedLogs, logEntry)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package bloodhound

import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/SpecterOps/bloodhound-go-sdk/sdk"
)

// MockTransport is a custom RoundTripper
type MockTransport struct {
RoundTripFunc func(*http.Request) (*http.Response, error)
}

func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return m.RoundTripFunc(req)
}

func PtrString(s string) *string {
return &s
}

func TestGetDomainMapping(t *testing.T) {
mockedJSONResponse := `{
"data": [
{
"type": "active-directory",
"name": "WRAITH.CORP",
"id": "S-1-5-21-3702535222-3822678775-2090119576",
"collected": true,
"impactValue": 12
},
{
"type": "active-directory",
"name": "CHILD.WRAITH.CORP",
"id": "S-1-5-21-720005541-26010935-3948964793",
"collected": false,
"impactValue": 0
},
{
"type": "active-directory",
"name": "PHANTOM.CORP",
"id": "S-1-5-21-2697957641-2271029196-387917394",
"collected": true,
"impactValue": 99
},
{
"type": "active-directory",
"name": "GHOST.CORP",
"id": "S-1-5-21-2845847946-3451170323-4261139666",
"collected": true,
"impactValue": 0
},
{
"type": "active-directory",
"name": "REVENANT.CORP",
"id": "S-1-5-21-1852147331-818484528-1557274963",
"collected": false,
"impactValue": 0
},
{
"type": "azure",
"name": "PHANTOM CORP",
"id": "6C12B0B0-B2CC-4A73-8252-0B94BFCA2145",
"collected": true,
"impactValue": 56
}
]
}`

mockTransport := &MockTransport{
RoundTripFunc: func(req *http.Request) (*http.Response, error) {
if req.URL.Path == "/api/v2/available-domains" && req.Method == http.MethodGet {
header := http.Header{
"Content-Type": []string{"application/json"},
}
resp := &http.Response{
StatusCode: http.StatusOK,
Header: header,
Body: ioutil.NopCloser(strings.NewReader(mockedJSONResponse)),
}
return resp, nil
}
return nil, fmt.Errorf("unexpected request: %v %v", req.Method, req.URL)
},
}

mockedHTTPClient := &http.Client{
Transport: mockTransport,
}

serverURL := "http://mockedserver.com"

bloodhoundClient, err := sdk.NewClientWithResponses(serverURL, sdk.WithHTTPClient(mockedHTTPClient))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}

mapping, err := GetDomainMapping(bloodhoundClient)
if err != nil {
t.Fatalf("GetDomainMapping failed: %v", err)
}
if len(*mapping) == 0 {
t.Fatalf("GetDomainMapping failed: %v", err)
}

}
Loading
Loading