Skip to content

Commit

Permalink
Merge pull request #11569 from daviditkin/daviditkin-bhe-solution
Browse files Browse the repository at this point in the history
Daviditkin bhe solution
  • Loading branch information
v-atulyadav authored Dec 26, 2024
2 parents bda79d8 + 28de1bd commit 18d5196
Show file tree
Hide file tree
Showing 43 changed files with 709 additions and 400 deletions.
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 modified Solutions/BloodHound Enterprise/Data Connectors/bhe-funcapp.zip
Binary file not shown.
3 changes: 2 additions & 1 deletion Solutions/BloodHound Enterprise/Data Connectors/handler.go
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

0 comments on commit 18d5196

Please sign in to comment.