diff --git a/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseCriticalAttackPaths.yaml b/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseCriticalAttackPaths.yaml index 306b27a2343..27fe22816be 100644 --- a/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseCriticalAttackPaths.yaml +++ b/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseCriticalAttackPaths.yaml @@ -16,9 +16,9 @@ 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: @@ -26,5 +26,5 @@ entityMappings: fieldMappings: - identifier: DomainName displayName: domain_name -version: 1.1.0 -kind: Scheduled \ No newline at end of file +version: 1.2.0 +kind: Scheduled diff --git a/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseExposure.yaml b/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseExposure.yaml index d37a5ee3beb..4ef2ad1395d 100644 --- a/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseExposure.yaml +++ b/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseExposure.yaml @@ -18,7 +18,7 @@ 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: @@ -26,5 +26,5 @@ entityMappings: fieldMappings: - identifier: DomainName displayName: domain_name -version: 1.1.0 -kind: Scheduled \ No newline at end of file +version: 1.2.0 +kind: Scheduled diff --git a/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseTierZeroAssets.yaml b/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseTierZeroAssets.yaml index 298322783b8..dfefa6da60d 100644 --- a/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseTierZeroAssets.yaml +++ b/Solutions/BloodHound Enterprise/Analytic Rules/BloodHoundEnterpriseTierZeroAssets.yaml @@ -18,7 +18,7 @@ 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: @@ -26,5 +26,5 @@ entityMappings: fieldMappings: - identifier: DomainName displayName: domain_name -version: 1.1.0 -kind: Scheduled \ No newline at end of file +version: 1.2.0 +kind: Scheduled diff --git a/Solutions/BloodHound Enterprise/Data Connectors/AzureFunctionBloodHoundEnterprise/function.json b/Solutions/BloodHound Enterprise/Data Connectors/AzureFunctionBloodHoundEnterprise/function.json index b5ad50b0463..10d7172e6a7 100644 --- a/Solutions/BloodHound Enterprise/Data Connectors/AzureFunctionBloodHoundEnterprise/function.json +++ b/Solutions/BloodHound Enterprise/Data Connectors/AzureFunctionBloodHoundEnterprise/function.json @@ -4,7 +4,7 @@ "name": "myTimer", "type": "timerTrigger", "direction": "in", - "schedule": "0 0 6,18 * * *" + "schedule": "0 0 1,13 * * *" } ] } diff --git a/Solutions/BloodHound Enterprise/Data Connectors/BloodHoundEnterprise_API_FunctionApp.json b/Solutions/BloodHound Enterprise/Data Connectors/BloodHoundEnterprise_API_FunctionApp.json index 0f293111587..0c0e4829550 100644 --- a/Solutions/BloodHound Enterprise/Data Connectors/BloodHoundEnterprise_API_FunctionApp.json +++ b/Solutions/BloodHound Enterprise/Data Connectors/BloodHoundEnterprise_API_FunctionApp.json @@ -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": "", diff --git a/Solutions/BloodHound Enterprise/Data Connectors/bhe-funcapp.zip b/Solutions/BloodHound Enterprise/Data Connectors/bhe-funcapp.zip index 8f784b54f23..7309bff4189 100644 Binary files a/Solutions/BloodHound Enterprise/Data Connectors/bhe-funcapp.zip and b/Solutions/BloodHound Enterprise/Data Connectors/bhe-funcapp.zip differ diff --git a/Solutions/BloodHound Enterprise/Data Connectors/handler.go b/Solutions/BloodHound Enterprise/Data Connectors/handler.go index 99ac2088d7b..65b94e50099 100644 --- a/Solutions/BloodHound Enterprise/Data Connectors/handler.go +++ b/Solutions/BloodHound Enterprise/Data Connectors/handler.go @@ -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) @@ -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 diff --git a/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client.go b/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client.go index 2097d57b204..c9e9a484c45 100644 --- a/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client.go +++ b/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client.go @@ -3,7 +3,6 @@ package bloodhound import ( "context" "encoding/json" - "errors" "fmt" "log" "net/http" @@ -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(), ¶ms) + 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) } @@ -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) @@ -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) } diff --git a/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client_test.go b/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client_test.go new file mode 100644 index 00000000000..b5836d8008f --- /dev/null +++ b/Solutions/BloodHound Enterprise/Data Connectors/pkg/bloodhound/client_test.go @@ -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) + } + +} \ No newline at end of file diff --git a/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main.go b/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main.go index d1b20650abb..dd2df051d0b 100644 --- a/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main.go +++ b/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main.go @@ -106,12 +106,12 @@ func transformPostureData(domainMap *map[string]sdk.ModelDomainSelector, posture bhe_sentinel_data := BloodhoundEnterpriseData{ DataType: "posture", - TimeGenerated: *postureStat.CreatedAt, DomainSID: *postureStat.DomainSid, DomainName: *selector.Name, DomainType: *selector.Type, DomainID: *selector.Id, ExposureIndex: math.Round(*postureStat.ExposureIndex * 100), + Exposure: *postureStat.ExposureIndex, FindingCount: float64(*postureStat.CriticalRiskCount), // TODO: wrong TierZeroCount: float64(*postureStat.TierZeroCount), // TODO: wrong UpdatedAt: *postureStat.UpdatedAt, @@ -155,8 +155,8 @@ func transformConfigurationFinding(domainMap *map[string]sdk.ModelDomainSelector Principal: *findingProps.Name, TierZeroPrincipal: *findingProps.Name, PathType: pathType, - PathTitle: pathTitle, - EventDetails: pathTitle, + PathTitle: pathTitle, + EventDetails: pathTitle, ID: int64(*finding.Id), } return bhe_sentinel_data, nil @@ -177,8 +177,8 @@ func transformRelationshipFinding(domainMap *map[string]sdk.ModelDomainSelector, TierZeroPrincipal: toProps.Name, NonTierZeroPrincipal: fromProps.Name, PathType: pathType, - PathTitle: pathTitle, - EventDetails: pathTitle, + PathTitle: pathTitle, + EventDetails: pathTitle, // DeletedAt: finding.DeletedAt, DeletedAtV: *finding.DeletedAt.Valid, // DeletedAtV: finding.DeletedAt != nil, @@ -195,8 +195,6 @@ func transformAttackPathData(domainMap *map[string]sdk.ModelDomainSelector, data if !ok { pathTitle = "Unknown" log.Printf("Error path title not found for %s", pathType) - } else { - log.Printf("Found path title for %s", pathTitle) } for _, attackPathJson := range attackPathDataArray { x := AttackFindingProperties{} @@ -250,12 +248,17 @@ func transformAttackPathData(domainMap *map[string]sdk.ModelDomainSelector, data return bhd, nil } -func transformAttackAggregator(domainMap *map[string]sdk.ModelDomainSelector, data map[string]map[string][]sdk.ModelRiskCounts) ([]BloodhoundEnterpriseData, error) { +func transformAttackAggregator(domainMap *map[string]sdk.ModelDomainSelector, data map[string]map[string][]sdk.ModelRiskCounts, pathTypeMap *map[string]string) ([]BloodhoundEnterpriseData, error) { bhd := make([]BloodhoundEnterpriseData, 0) for domainId, pathMap := range data { for pathType, riskArray := range pathMap { for _, risk := range riskArray { - data, err := transformModelRiskCountr(domainMap, domainId, pathType, risk) + pathTitle, ok := (*pathTypeMap)[pathType] + if !ok || pathTitle == "" { + pathTitle = "Unknown" + log.Printf("Error path title not found for %s", pathType) + } + data, err := transformModelRiskCountr(domainMap, domainId, pathType, pathTitle, risk) if err != nil { log.Printf("Error transforming risk counter, skipping record: %v", err) continue @@ -267,7 +270,7 @@ func transformAttackAggregator(domainMap *map[string]sdk.ModelDomainSelector, da return bhd, nil } -func transformModelRiskCountr(domainMap *map[string]sdk.ModelDomainSelector, _ string, path_type string, data sdk.ModelRiskCounts) (BloodhoundEnterpriseData, error) { +func transformModelRiskCountr(domainMap *map[string]sdk.ModelDomainSelector, _ string, path_type string, path_title string, data sdk.ModelRiskCounts) (BloodhoundEnterpriseData, error) { selector := (*domainMap)[*data.DomainSID] bhe_sentinel_data := BloodhoundEnterpriseData{ @@ -277,7 +280,6 @@ func transformModelRiskCountr(domainMap *map[string]sdk.ModelDomainSelector, _ s DomainName: *selector.Name, DomainType: *selector.Type, DomainID: *selector.Id, - ExposureIndex: math.Round(*data.CompositeRisk * 100), Exposure: *data.CompositeRisk, FindingCount: float64(*data.FindingCount), DomainImpactValue: float64(*data.ImpactedAssetCount), @@ -287,6 +289,8 @@ func transformModelRiskCountr(domainMap *map[string]sdk.ModelDomainSelector, _ s DeletedAtV: *data.DeletedAt.Valid, ID: *data.Id, PathType: path_type, + PathTitle: path_title, + EventDetails: path_title, } return bhe_sentinel_data, nil @@ -348,107 +352,111 @@ func transformAudiLogs(logs []sdk.ModelAuditLog) ([]BloodhoundEnterpriseData, er return records, nil } -func transformTierZeroPrincipal(graph *sdk.ModelUnifiedGraphGraph, domainMap *map[string]sdk.ModelDomainSelector) ([]BloodhoundEnterpriseData, error) { - records := make([]BloodhoundEnterpriseData, 0) - nodes := graph.Nodes - for _, node := range *nodes { - if *node.IsTierZero == false { - continue - } - props := (*node.Properties) - var selector *sdk.ModelDomainSelector = nil +func memberOf(elem *string, set map[string]bool) bool { + _, ok := set[string(*elem)] + return ok +} - if *node.Kind == "Meta" { - continue +func in(elem string, set map[string]bool) bool { + _, ok := set[elem] + return ok +} + +func transformTierZeroPrincipal(tierZeroGroupmembers []sdk.ModelAssetGroupMember, domainMap map[string]sdk.ModelDomainSelector) ([]BloodhoundEnterpriseData, error) { + ENVIRONMENT_KINDS := map[string]bool{"Domain": true, "AZTenant": true} + records := make([]BloodhoundEnterpriseData, 0) + for _, groupMember := range tierZeroGroupmembers { + var environment_sid = groupMember.EnvironmentId + if groupMember.PrimaryKind != nil && memberOf(groupMember.PrimaryKind, ENVIRONMENT_KINDS) { + environment_sid = groupMember.ObjectId } - if *node.Kind == "Base" { + if environment_sid == nil || *environment_sid == "" { + log.Printf("Error tier zero principal missing environment sid skipping %s", *groupMember.Name) continue } - sid, ok := props["domainsid"].(string) - if ok == true { - s, ok := (*domainMap)[sid] - if ok == true { - selector = &s - } - } - - if selector == nil { - sid, ok := props["tenantid"].(string) - if ok == true { - s, ok := (*domainMap)[sid] - if ok == true { - selector = &s - } - } - } - if selector == nil { - s, ok := (*domainMap)[*node.ObjectId] - if ok == true { - selector = &s - } - } - if selector == nil { + // Use the SID to lookup the name, id and type + selector, ok := domainMap[*environment_sid] + if !ok { + log.Printf("Error tier zero principal could not find domain record for sid %s skipping %s", *environment_sid, *groupMember.Name) continue } + environment_name := *selector.Name + environment_id := selector.Id + environment_kind := selector.Type - var domainType = selector.Name - if strings.HasPrefix(*domainType, "AZ") { - s := strings.ReplaceAll(*domainType, "AZ", "") - domainType = &s - } - bheRecord := BloodhoundEnterpriseData{ + record := BloodhoundEnterpriseData{ + TierZeroPrincipal: *groupMember.Name, + FindingID: *groupMember.ObjectId, // TODO: move this to a new field called ObjectID (backfill required for other data_types??) + EventDetails: *groupMember.PrimaryKind, // TODO: move this to a new field called ObjectKind (backfill required for other data_types??) + DomainSID: *environment_sid, + DomainID: *environment_id, // TODO: DomainSID and DomainID seem redundant + DomainType: *environment_kind, + DomainName: environment_name, DataType: "t0_export", TimeGenerated: time.Now(), - DomainSID: *node.ObjectId, - DomainName: *selector.Name, - DomainType: *selector.Type, - DomainID: *selector.Id, - CreatedAt: time.Now(), - EventDetails: *node.Kind, - TierZeroPrincipal: *node.Label, - // TODO update updated time } - records = append(records, bheRecord) + records = append(records, record) } return records, nil } -func CreateBatches(records []BloodhoundEnterpriseData, maxUploadSize int64) ([][]BloodhoundEnterpriseData, error) { +func CreateJsonBatches(records []BloodhoundEnterpriseData, maxUploadSize int64) ([][]byte, error) { if len(records) == 0 { - return make([][]BloodhoundEnterpriseData, 0), nil - } - - // We test a single record for size - // Get size of record list - singleRecordJson, err := json.Marshal(records[0]) - if err != nil { - return nil, fmt.Errorf("Error marshaling the data %v", err) - } - n := int64(len(singleRecordJson)) - - if n >= maxUploadSize { - return nil, fmt.Errorf("Error marshalling the data. A single record[bytes] %d is too large to upload. maxUploadSize[bytes] %d. ", n, maxUploadSize) - } - - // We limit ourselves to maxUploadSize and then we are conservative and reduce the number of records per batch - recordsPerBatch := int(maxUploadSize / n) - if recordsPerBatch > 2 { - recordsPerBatch -= 2 + return make([][]byte, 0), nil } - var batchedRecords = make([][]BloodhoundEnterpriseData, 0) - - for i := 0; i < len(records); i += recordsPerBatch { - end := i + recordsPerBatch - - if end > len(records) { - end = len(records) + var jsonRecords = make([][]byte, len(records)) + for i, record := range records { + jsonRecord, err := json.Marshal(record) + if err != nil { + // TODO maybe skip + return nil, fmt.Errorf("Error marshaling the data %v", err) + } + jsonRecords[i] = jsonRecord + } + + batches := make([][]byte, 1) + batches = batches[:0] + + batchRecordCounts := make([]int, 0) + + batch := make([]byte, maxUploadSize) + batch = batch[:0] + batch = append(batch, "["...) + joinSeparator := []byte("") + batchRecordCount := 0 + i := 0 + for i < len(jsonRecords) { + jsonRecord := jsonRecords[i] + bytesToAppend := len(joinSeparator) + len(jsonRecord) + len("]") + recordFits := len(batch) + bytesToAppend + len("]") < cap(batch) + lastRecord := i == len(jsonRecords)-1 + if recordFits { + batch = append(batch, joinSeparator...) + batch = append(batch, jsonRecord...) + batchRecordCount++ + joinSeparator = []byte(",") + } else if batchRecordCount == 0 { + // Error case, a single record is too large to fit in a batch + return nil, fmt.Errorf("Error marshaling the data. A single record is too large to upload. maxUploadSize[bytes] %d. ", maxUploadSize) + } + if !recordFits || lastRecord { + // cap it off + batch = append(batch, "]"...) + batches = append(batches, batch) + batchRecordCounts = append(batchRecordCounts, batchRecordCount) + batch = make([]byte, maxUploadSize) + batch = batch[:0] + batch = append(batch, "["...) + joinSeparator = []byte("") + batchRecordCount = 0 + } + if recordFits { + i ++ } - batchedRecords = append(batchedRecords, records[i:end]) } - - return batchedRecords, nil + return batches, nil } func printSlice(s [][]BloodhoundEnterpriseData) { @@ -459,105 +467,85 @@ func printSlice(s [][]BloodhoundEnterpriseData) { fmt.Printf("ptr=%p len=%d cap=%d \n", ptr, len(s), cap(s)) } -// Create batches of marshaled json records and gaurantee that they will fix the maxUploadSize -// We take approximate batches generated by CreateBatches and then individually test size and rebatch with -// maxUploadSize reduced by half -func CreateBatchesGauranteedToFit(records []BloodhoundEnterpriseData, maxUploadSize int64) ([][]byte, error) { - - var batchesToMarshal, err = CreateBatches(records, maxUploadSize) - if err != nil { - return nil, err - } - - log.Printf("Original batch size %d", len(batchesToMarshal)) - var jsonBatches = make([][]byte, 0) - for len(batchesToMarshal) > 0 { - // POP of a batch, marshal it and then check if it fits - batch := batchesToMarshal[len(batchesToMarshal)-1] - batchesToMarshal = batchesToMarshal[:len(batchesToMarshal)-1] - batchJSON, err := json.Marshal(batch) - if err != nil { - return nil, err - } - - // if a single marshaled JSON is still too big, - // reduce the maxUploadSize rebatch, adding them back to batchesToMarshal so they will be tested again - if int64(len(batchJSON)) > maxUploadSize { - log.Printf("Warning needing redo a large batch %d", len(batchJSON)) - maxUploadSize := maxUploadSize - (int64(len(batchJSON)) - maxUploadSize) - smallBatches, err := CreateBatches(batch, maxUploadSize) - if err != nil { - return nil, err - } - batchesToMarshal = append(batchesToMarshal, smallBatches...) - } else { - jsonBatches = append(jsonBatches, batchJSON) - } - } - return jsonBatches, nil -} - func ApplyEditors(ctx context.Context, c *sdk.Client, req *http.Request, additionalEditors []sdk.RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { + for _, editor := range c.RequestEditors { + if err := editor(ctx, req); err != nil { return err } } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { + for _, editor := range additionalEditors { + if err := editor(ctx, req); err != nil { return err } } return nil } -// UploadLogsCallback returns a curried function that can be used as a callback -func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, lastRun *time.Time, azLogsClient *azlogs.Client, ruleId string, maxUploadSize int64) ([]string, error) { - // TODO is there a generic sdk client type +func getAssetData(bloodhoundServer string, bloodhoundClient *sdk.ClientWithResponses, uri string) (*http.Response, error) { + // TODO Better error / retry handling + if !strings.HasPrefix(bloodhoundServer, "https") { + bloodhoundServer = "https://" + bloodhoundServer + } + var client = bloodhoundClient.ClientInterface.(*sdk.Client) - if lastRun == nil { - log.Print("Starting log processing lastRun is nil") - } else { - log.Printf("Starting log processing lastRun is %v", lastRun) + req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", bloodhoundServer, uri), nil) + if err != nil { + return nil, fmt.Errorf("failed to build request %v", err) } - var responseLogs = make([]string, 0) - bloodhoundRecordData := make(map[string][]BloodhoundEnterpriseData) + err = ApplyEditors(context.TODO(), client, req, client.RequestEditors) + if err != nil { + return nil, fmt.Errorf("failed to apply request editors %v", err) + } + response, err := client.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make request %v", err) + } + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed request with status %v", response.Status) + } - // Get attack path type to attack path title mapping - // TODO: lift this and it really should be defined in the API - // TODO: Retry? Handle 429? - var client = bloodhoundClient.ClientInterface.(*sdk.Client) + return response, nil +} + +func getAttackPathTitles(bloodhoundServer string, bloodhoundClient *sdk.ClientWithResponses, responseLogs []string) (map[string]string, error) { pathTypes, err := bloodhoundClient.ListAttackPathTypesWithResponse(context.TODO(), nil) + var pathMap = make(map[string]string) if err != nil { responseLogs = append(responseLogs, fmt.Sprintf("failed to get attack path types %v", err)) - return responseLogs, err + return pathMap, err } - var pathMap = make(map[string]string) responseLogs = append(responseLogs, fmt.Sprintf("got %d attack path types", len(*pathTypes.JSON200.Data))) for _, pathType := range *pathTypes.JSON200.Data { // I'm going to get these one at a time TOOD: check if there is a better way - req, err := http.NewRequest("GET",fmt.Sprintf("https://demo.bloodhoundenterprise.io/api/v2/assets/findings/%s/title.md", pathType), nil) - if (err != nil) { - responseLogs = append(responseLogs, fmt.Sprintf("failed to get attack path title %v", err)) - return responseLogs, err + uri := fmt.Sprintf("api/v2/assets/findings/%s/title.md", pathType) + response, err := getAssetData(bloodhoundServer, bloodhoundClient, uri) + if err != nil { + log.Printf("Error getting attack path title for path type %s so skipping %v", pathType, err) + continue } - ApplyEditors(context.TODO(), client, req, client.RequestEditors) - response, err := client.Client.Do(req) - if (err != nil) { - responseLogs = append(responseLogs, fmt.Sprintf("failed to get attack path title %v", err)) - return responseLogs, err - } - if (response.StatusCode != http.StatusOK) { - responseLogs = append(responseLogs, fmt.Sprintf("failed to get attack path title %v", response.Status)) - return responseLogs, err - } - var responseBytes = make([]byte, 1024) - count, err := response.Body.Read(responseBytes) + var responseBytes = make([]byte, 1024) // TODO check if this is a good size or will expand + count, err := response.Body.Read(responseBytes) pathMap[pathType] = string(responseBytes[:count]) } + return pathMap, nil +} + +// UploadLogsCallback returns a curried function that can be used as a callback +// hack passing the bh domain / config. Wont be needed when bloodhoundClient handles assset retrieval. Need to modify the sdk. +func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, bloodhoundServer string, lastRun *time.Time, azLogsClient *azlogs.Client, ruleId string, maxUploadSize int64) ([]string, error) { + // TODO is there a generic sdk client type + + if lastRun == nil { + log.Print("Starting log processing lastRun is nil") + } else { + log.Printf("Starting log processing lastRun is %v", lastRun) + } + var responseLogs = make([]string, 0) + + bloodhoundRecordData := make(map[string][]BloodhoundEnterpriseData) lastAnalysisTime, err := bloodhound.GetLastAnalysisTime(bloodhoundClient) if err != nil { @@ -572,8 +560,6 @@ func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, lastRun *time } else { responseLogs = append(responseLogs, fmt.Sprintf("last ingest time %v before last analysis time %v. We will continue", lastRun, lastAnalysisTime)) } - } else { - log.Printf("UploadLogsCallback lastRun is nil, not doing compare") } mapping, err := bloodhound.GetDomainMapping(bloodhoundClient) @@ -583,6 +569,13 @@ func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, lastRun *time } responseLogs = append(responseLogs, fmt.Sprintf("got %d domain mappings", len(*mapping))) + pathMap, err := getAttackPathTitles(bloodhoundServer, bloodhoundClient, responseLogs) + if err != nil { + responseLogs = append(responseLogs, fmt.Sprintf("failed to get attack path titles %v", err)) + // We will continue without the path titles + } + responseLogs = append(responseLogs, fmt.Sprintf("got %d attack path titles", len(pathMap))) + domainIds := make([]string, 0, len(*mapping)) for k, _ := range *mapping { domainIds = append(domainIds, k) @@ -655,21 +648,36 @@ func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, lastRun *time } responseLogs = append(responseLogs, fmt.Sprintf("got %d attack path aggregator records", len(aggregatorData))) - attackPathAggregateBHERecords, err := transformAttackAggregator(mapping, aggregatorData) + attackPathAggregateBHERecords, err := transformAttackAggregator(mapping, aggregatorData, &pathMap) if err != nil { responseLogs = append(responseLogs, fmt.Sprintf("Error transforming attack path aggregator data %v", err)) return responseLogs, err } + + if len(attackPathAggregateBHERecords) > 0 { + responseLogs = append(responseLogs, fmt.Sprintf("Got %d attack path aggregator records PathTitle of first is %s", len(attackPathAggregateBHERecords), attackPathAggregateBHERecords[0].PathTitle)) + } else { + responseLogs = append(responseLogs, fmt.Sprintf("Got 0 attack path aggregator records")) + } + bloodhoundRecordData["attackPathAggregateData"] = attackPathAggregateBHERecords - tierZeroData, err := bloodhound.GetTierZeroPrincipal(bloodhoundClient) + var tierZeroData []sdk.ModelAssetGroupMember = make([]sdk.ModelAssetGroupMember, 0) + + // Get Tier Zero asset group and then its members + tierZeroGroup, err := bloodhound.GetTierZeroGroup(bloodhoundClient) if err != nil { - responseLogs = append(responseLogs, fmt.Sprintf("Error getting tier zero principals %v", err)) - return responseLogs, err + responseLogs = append(responseLogs, fmt.Sprintf("Error getting tier zero group skipping %v", err)) + } else { + tierZeroData, err = bloodhound.GetTierZeroPrincipals(bloodhoundClient, tierZeroGroup) + if err != nil { + responseLogs = append(responseLogs, fmt.Sprintf("Error getting tier zero principals skipping %v", err)) + } + responseLogs = append(responseLogs, fmt.Sprintf("Got %d cypher query graph nodes", len(tierZeroData))) } - responseLogs = append(responseLogs, fmt.Sprintf("Got %d cypher query graph nodes", len(*tierZeroData.Nodes))) - tier0BHERecords, err := transformTierZeroPrincipal(tierZeroData, mapping) + + tier0BHERecords, err := transformTierZeroPrincipal(tierZeroData, *mapping) if err != nil { responseLogs = append(responseLogs, fmt.Sprintf("Error transforming tier zero principal data %v", err)) return responseLogs, err @@ -682,7 +690,7 @@ func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, lastRun *time for kind, recordList := range bloodhoundRecordData { log.Printf("About to upload %s data %d records ", kind, len(recordList)) - recordsJSON, err := CreateBatchesGauranteedToFit(recordList, maxUploadSize) + recordsJSON, err := CreateJsonBatches(recordList, maxUploadSize) if err != nil { responseLogs = append(responseLogs, fmt.Sprintf("failed to generate batched json for %s data Error: %v", kind, err)) lastError = err @@ -700,4 +708,3 @@ func UploadLogsCallback(bloodhoundClient *sdk.ClientWithResponses, lastRun *time } return responseLogs, lastError } - diff --git a/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main_test.go b/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main_test.go index 930dff18c7c..b654cd9864f 100644 --- a/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main_test.go +++ b/Solutions/BloodHound Enterprise/Data Connectors/pkg/connector/main_test.go @@ -2,11 +2,17 @@ package connector import ( "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" "testing" "time" - . "function/pkg/model" + "github.com/SpecterOps/bloodhound-go-sdk/sdk" + . "function/pkg/bloodhound" + . "function/pkg/model" ) // Testing some time formatting here @@ -34,7 +40,7 @@ func TestCreateBatches(t *testing.T) { jsonRecords, _ := json.Marshal(testRecords) t.Logf("size of record list %d", len(jsonRecords)) - batches, err := CreateBatches(testRecords, 1000000) + batches, err := CreateJsonBatches(testRecords, 1000000) if err != nil { t.Fatalf("Error creating batches %v", err) } @@ -55,43 +61,22 @@ func TestCreateBatches(t *testing.T) { } } -func TestPop(t *testing.T) { - testRecord := BloodhoundEnterpriseData{ - EventType: "a;lksdjfa;lskdjf;alskdjf;alsdkjf;alsdkjf;alskdjf;alskdjfa;lsdkjf;alskjdfalskjdfalksdjf", - EventDetails: "a;lskdfja;sldkfja;sldkfja;sldkfja;lsdkjfa;sldkfja;lsdkjfa;lsdkjfa;lskdjfalskdfjaldkjf", - } - var testRecords = make([]BloodhoundEnterpriseData, 50000) - for i := range 50000 { - testRecords[i] = testRecord - } - - jsonRecords, _ := json.Marshal(testRecords) - t.Logf("size of record list %d", len(jsonRecords)) - - var batchesToMarshal, _ = CreateBatches(testRecords, 1000000) - originalLength := len(batchesToMarshal) - // var batch = make([]BloodhoundEnterpriseData, 1) - batch, batchesToMarshal := batchesToMarshal[len(batchesToMarshal)-1], batchesToMarshal[:len(batchesToMarshal)-1] - if len(batchesToMarshal) != originalLength - 1 { - t.Fatalf("Batch size not reduced after pop orig: %d new: %d", originalLength, len(batchesToMarshal)) - } - if batch[0].ID != 0 { - t.Fatalf("First batch should be 0, %d", batch[0].ID) - } +// MockTransport is a custom RoundTripper +type MockTransport struct { + RoundTripFunc func(*http.Request) (*http.Response, error) +} - batchJSON, err := json.Marshal(batch) - if err != nil { - t.Fatal() - } - if len(batchJSON) > 1000000 { - t.Fatal() - } +func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return m.RoundTripFunc(req) +} +func PtrString(s string) *string { + return &s } -func TestCreateBatchesGauranteed(t *testing.T) { +func TestRecordsTooBig(t *testing.T) { testRecord := BloodhoundEnterpriseData{ EventType: "a;lksdjfa;lskdjf;alskdjf;alsdkjf;alsdkjf;alskdjf;alskdjfa;lsdkjf;alskjdfalskjdfalksdjf", EventDetails: "a;lskdfja;sldkfja;sldkfja;sldkfja;lsdkjfa;sldkfja;lsdkjfa;lsdkjfa;lskdjfalskdfjaldkjf", @@ -101,72 +86,219 @@ func TestCreateBatchesGauranteed(t *testing.T) { testRecords[i] = testRecord } - jsonRecords, _ := json.Marshal(testRecords) - t.Logf("size of record list %d", len(jsonRecords)) + maxRecordSize := int64(10) + _, err := CreateJsonBatches(testRecords, maxRecordSize) + if err == nil { + t.Fatal("Error creating batches should fail when single record not fit max record size ") + } +} - possibleBatchesToMarshal, err := CreateBatches(testRecords, 1000000) - if err != nil { - t.Fatal() - } +func TestGetTier0Data(t *testing.T) { + doomainMockedJSONResponse := `{ + "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 + } + ] + }` - numApproxSizedBatches := len(possibleBatchesToMarshal) - if err != nil { - t.Fatal() - } - batchesToMarshal, err := CreateBatchesGauranteedToFit(testRecords, 1000000) - numGauranteedBatches := len(batchesToMarshal) - if err != nil { - t.Fatal() - } + assetgroup1MockedJSONResponse := `{ + "count": 169, + "limit": 100, + "skip": 0, + "data": { + "members": [ + { + "asset_group_id": 1, + "object_id": "CN=ENTERPRISE ADMINS,OU=GROUPS,OU=TIER0,DC=PHANTOM,DC=CORP", + "primary_kind": "Base", + "kinds": [ + "Base" + ], + "environment_id": "", + "environment_kind": "", + "name": "", + "custom_member": false + }, + { + "asset_group_id": 1, + "object_id": "D2C3BD7E-7CEC-4905-B22C-B657C026F638", + "primary_kind": "AZServicePrincipal", + "kinds": [ + "AZServicePrincipal", + "AZBase" + ], + "environment_id": "6C12B0B0-B2CC-4A73-8252-0B94BFCA2145", + "environment_kind": "AZTenant", + "name": "48AADCF6-PRIVILEGED ROLE ADMINISTRATOR@PHANTOMCORP", + "custom_member": false + }, + { + "asset_group_id": 1, + "object_id": "S-1-5-21-3702535222-3822678775-2090119576-519", + "primary_kind": "Group", + "kinds": [ + "Base", + "Group" + ], + "environment_id": "S-1-5-21-3702535222-3822678775-2090119576", + "environment_kind": "Domain", + "name": "ENTERPRISE ADMINS@WRAITH.CORP", + "custom_member": false + }, + { + "asset_group_id": 1, + "object_id": "S-1-5-21-1852147331-818484528-1557274963", + "primary_kind": "Domain", + "kinds": [ + "Domain", + "Base" + ], + "environment_id": "", + "environment_kind": "", + "name": "REVENANT.CORP", + "custom_member": false + }, + { + "asset_group_id": 1, + "object_id": "S-1-5-21-1852147331-818484528-1557274963", + "primary_kind": "Domain", + "kinds": [ + "Domain", + "Base" + ], + "environment_id": "", + "environment_kind": "", + "name": "REVENANT.CORP", + "custom_member": false + }, + { + "asset_group_id": 1, + "object_id": "315C2B07-E593-474F-BBDC-F3CA5EC52813", + "primary_kind": "AZApp", + "kinds": [ + "AZApp", + "AZBase" + ], + "environment_id": "6C12B0B0-B2CC-4A73-8252-0B94BFCA2145", + "environment_kind": "AZTenant", + "name": "47795D55-PRIVILEGED ROLE ADMINISTRATOR@PHANTOMCORP.ONMICROSOFT.COM", + "custom_member": false + }, + { + "asset_group_id": 1, + "object_id": "C68C6ED4-652F-4D39-A9A4-DBDA72B09D50", + "primary_kind": "AZApp", + "kinds": [ + "AZApp", + "AZBase" + ], + "environment_id": "6C12B0B0-B2CC-4A73-8252-0B94BFCA2145", + "environment_kind": "AZTenant", + "name": "48AADCF6-PRIVILEGED ROLE ADMINISTRATOR@PHANTOMCORP.ONMICROSOFT.COM", + "custom_member": false + } + ] + } +}` - // These numbers might not match if the approximate batch sizes creted by CreateBatchs are a little too big - // they will be split by CreateBatchesGauranteedToFit - if numApproxSizedBatches != numGauranteedBatches { - t.Logf("num approx: %d num gauranteed: %d", numApproxSizedBatches, numGauranteedBatches) + 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(doomainMockedJSONResponse)), + } + return resp, nil + } else if req.URL.Path == "/api/v2/asset-groups/1/members" && 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(assetgroup1MockedJSONResponse)), + } + return resp, nil + } + return nil, fmt.Errorf("unexpected request: %v %v", req.Method, req.URL) + }, } - var c = 0 - for _, b := range batchesToMarshal { - testRecords := make([]BloodhoundEnterpriseData, 0) - json.Unmarshal(b, &testRecords) - c += len(testRecords) + mockedHTTPClient := &http.Client{ + Transport: mockTransport, } - if c != len(testRecords) { - t.Logf("unmarshaled num records %d original num records %d", c, len(testRecords)) - t.Fatal() - } + serverURL := "http://mockedserver.com" - if len(batchesToMarshal) == 0 { - t.Fatal() + 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("Failed to get domain mapping: %v", err) -func TestRecordsTooBig(t *testing.T) { - testRecord := BloodhoundEnterpriseData{ - EventType: "a;lksdjfa;lskdjf;alskdjf;alsdkjf;alsdkjf;alskdjf;alskdjfa;lsdkjf;alskjdfalskjdfalksdjf", - EventDetails: "a;lskdfja;sldkfja;sldkfja;sldkfja;lsdkjfa;sldkfja;lsdkjfa;lsdkjfa;lskdjfalskdfjaldkjf", - } - var testRecords = make([]BloodhoundEnterpriseData, 50000) - for i := range 50000 { - testRecords[i] = testRecord } - singleRecordSize, _ := json.Marshal(testRecords[0]) + tierZeroGroupmembers, err := GetTierZeroPrincipals(bloodhoundClient) + bheRecords, err := transformTierZeroPrincipal(tierZeroGroupmembers, *mapping) - maxRecordSize := int64(10) - _, err := CreateBatches(testRecords, maxRecordSize) - if err == nil { - t.Logf("Error creating batches should fail when single record size %d will not fit max record size %d", singleRecordSize, maxRecordSize) + if err != nil { + t.Fatalf("Failed to create client: %v", err) } - - _, anerr := CreateBatchesGauranteedToFit(testRecords, maxRecordSize) - if anerr == nil { - t.Logf("Error creating gauranteed batches should fail when single record size %d will not fit max record size %d", singleRecordSize, maxRecordSize) + if len(tierZeroGroupmembers) != 7 { + t.Fatalf("Expected 1 record, got %d", len(bheRecords)) } - - if err == nil || anerr == nil { - t.Fatal() + if len(bheRecords) != 6 { + t.Fatalf("Expected 1 record, got %d", len(bheRecords)) + } + if bheRecords[0].DataType != "t0_export" { + t.Fatalf("Expected data_type to be t0_export, got %s", bheRecords[0].DataType) } + } diff --git a/Solutions/BloodHound Enterprise/Data/Solution_BloodHoundEnterprise.json b/Solutions/BloodHound Enterprise/Data/Solution_BloodHoundEnterprise.json index 6683ffd7a39..9d18f901d1e 100644 --- a/Solutions/BloodHound Enterprise/Data/Solution_BloodHoundEnterprise.json +++ b/Solutions/BloodHound Enterprise/Data/Solution_BloodHoundEnterprise.json @@ -26,7 +26,7 @@ "Hunting Queries": [], "Watchlists": [], "BasePath": "C:\\GitHub\\Azure-Sentinel\\Solutions\\BloodHound Enterprise", - "Version": "3.1.0", + "Version": "3.1.1", "Metadata": "SolutionMetadata.json", "TemplateSpec": true } diff --git a/Solutions/BloodHound Enterprise/Package/3.1.1.zip b/Solutions/BloodHound Enterprise/Package/3.1.1.zip new file mode 100644 index 00000000000..eba63042334 Binary files /dev/null and b/Solutions/BloodHound Enterprise/Package/3.1.1.zip differ diff --git a/Solutions/BloodHound Enterprise/Package/mainTemplate.json b/Solutions/BloodHound Enterprise/Package/mainTemplate.json index 62ee685728d..76a2f14eaf3 100644 --- a/Solutions/BloodHound Enterprise/Package/mainTemplate.json +++ b/Solutions/BloodHound Enterprise/Package/mainTemplate.json @@ -73,29 +73,29 @@ "email": "support@specterops.io", "_email": "[variables('email')]", "_solutionName": "BloodHound Enterprise", - "_solutionVersion": "3.1.0", + "_solutionVersion": "3.1.1", "solutionId": "azuresentinel.azure-sentinel-solution-bloodhoundenterprise", "_solutionId": "[variables('solutionId')]", - "workbookVersion1": "1.0", + "workbookVersion1": "2.0", "workbookContentId1": "BloodHoundEnterpriseAttackPathDetails", "workbookId1": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId1'))]", "workbookTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId1'))))]", "_workbookContentId1": "[variables('workbookContentId1')]", "workspaceResourceId": "[resourceId('microsoft.OperationalInsights/Workspaces', parameters('workspace'))]", "_workbookcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId1'),'-', variables('workbookVersion1'))))]", - "workbookVersion2": "1.0", + "workbookVersion2": "2.0", "workbookContentId2": "BloodHoundEnterpriseAttackPathOverview", "workbookId2": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId2'))]", "workbookTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId2'))))]", "_workbookContentId2": "[variables('workbookContentId2')]", "_workbookcontentProductId2": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId2'),'-', variables('workbookVersion2'))))]", - "workbookVersion3": "1.0", + "workbookVersion3": "2.0", "workbookContentId3": "BloodHoundEnterpriseAuditLogs", "workbookId3": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId3'))]", "workbookTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId3'))))]", "_workbookContentId3": "[variables('workbookContentId3')]", "_workbookcontentProductId3": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId3'),'-', variables('workbookVersion3'))))]", - "workbookVersion4": "1.0", + "workbookVersion4": "2.0", "workbookContentId4": "BloodHoundEnterprisePosture", "workbookId4": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId4'))]", "workbookTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId4'))))]", @@ -108,25 +108,25 @@ "_workbookContentId5": "[variables('workbookContentId5')]", "_workbookcontentProductId5": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId5'),'-', variables('workbookVersion5'))))]", "analyticRuleObject1": { - "analyticRuleVersion1": "1.1.0", + "analyticRuleVersion1": "1.2.0", "_analyticRulecontentId1": "df292d06-f348-41ad-b780-0abb5acfe9ab", "analyticRuleId1": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'df292d06-f348-41ad-b780-0abb5acfe9ab')]", "analyticRuleTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('df292d06-f348-41ad-b780-0abb5acfe9ab')))]", - "_analyticRulecontentProductId1": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','df292d06-f348-41ad-b780-0abb5acfe9ab','-', '1.1.0')))]" + "_analyticRulecontentProductId1": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','df292d06-f348-41ad-b780-0abb5acfe9ab','-', '1.2.0')))]" }, "analyticRuleObject2": { - "analyticRuleVersion2": "1.1.0", + "analyticRuleVersion2": "1.2.0", "_analyticRulecontentId2": "b1f6aed2-ebb9-4fe4-bd7c-6657d02a0cc8", "analyticRuleId2": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'b1f6aed2-ebb9-4fe4-bd7c-6657d02a0cc8')]", "analyticRuleTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('b1f6aed2-ebb9-4fe4-bd7c-6657d02a0cc8')))]", - "_analyticRulecontentProductId2": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','b1f6aed2-ebb9-4fe4-bd7c-6657d02a0cc8','-', '1.1.0')))]" + "_analyticRulecontentProductId2": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','b1f6aed2-ebb9-4fe4-bd7c-6657d02a0cc8','-', '1.2.0')))]" }, "analyticRuleObject3": { - "analyticRuleVersion3": "1.1.0", + "analyticRuleVersion3": "1.2.0", "_analyticRulecontentId3": "13424be6-aed7-448b-afe5-c03d8b29b4fe", "analyticRuleId3": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '13424be6-aed7-448b-afe5-c03d8b29b4fe')]", "analyticRuleTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('13424be6-aed7-448b-afe5-c03d8b29b4fe')))]", - "_analyticRulecontentProductId3": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','13424be6-aed7-448b-afe5-c03d8b29b4fe','-', '1.1.0')))]" + "_analyticRulecontentProductId3": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','13424be6-aed7-448b-afe5-c03d8b29b4fe','-', '1.2.0')))]" }, "uiConfigId1": "BloodHoundEnterprise", "_uiConfigId1": "[variables('uiConfigId1')]", @@ -149,7 +149,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseAttackPathDetails Workbook with template version 3.1.0", + "description": "BloodHoundEnterpriseAttackPathDetails Workbook with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion1')]", @@ -167,7 +167,7 @@ }, "properties": { "displayName": "[parameters('workbook1-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Attack Path Details\\n--- \\n\"},\"name\":\"attack-path-details\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| where exposure != 0\\n| where path_type != \\\"\\\"\\n| extend Severity = case(\\n exposure < 40.0, 'low',\\n exposure >= 40.0 and exposure <= 79.0, 'medium',\\n exposure > 79.0 and exposure <= 94.0, 'high',\\n exposure > 94.0, 'critical',\\n 'unknown'\\n )\\n| summarize arg_max(updated_at, *), Count = count() by domain_name, path_type\\n| project \\n ['Domain Name'] = domain_name,\\n ['Attack Path Type'] = path_type,\\n Severity,\\n ['Exposure %'] = round(exposure, 2)\\n\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"attack-path-details-query1\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Attack Path Details\\n--- \\n\"},\"name\":\"attack-path-details\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| extend Severity = case(\\n exposure < 40.0, 'low',\\n exposure >= 40.0 and exposure <= 79.0, 'medium',\\n exposure > 79.0 and exposure <= 94.0, 'high',\\n exposure > 94.0, 'critical',\\n 'unknown'\\n )\\n| summarize arg_max(updated_at, *) by domain_id, domain_name, path_type, path_title\\n| sort by round(exposure) desc\\n| project \\n ['Domain'] = domain_name,\\n ['Attack path'] = path_title,\\n Severity,\\n ['Exposure (%)'] = round(exposure, 2)\\n\\n\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"attack-path-details-query1\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -178,7 +178,7 @@ "apiVersion": "2022-01-01-preview", "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId1'),'/'))))]", "properties": { - "description": "@{workbookKey=BloodHoundEnterpriseAttackPathDetails; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise attack path details.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0; title=BloodHound Enterprise Attack Path Details; templateRelativePath=BloodHoundEnterpriseAttackPathDetails.json; subtitle=Detailed View; provider=SpecterOps}.description", + "description": "@{workbookKey=BloodHoundEnterpriseAttackPathDetails; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise attack path details.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=2.0; title=BloodHound Enterprise Attack Path Details; templateRelativePath=BloodHoundEnterpriseAttackPathDetails.json; subtitle=Detailed View; provider=SpecterOps}.description", "parentId": "[variables('workbookId1')]", "contentId": "[variables('_workbookContentId1')]", "kind": "Workbook", @@ -237,7 +237,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseAttackPathOverview Workbook with template version 3.1.0", + "description": "BloodHoundEnterpriseAttackPathOverview Workbook with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion2')]", @@ -255,7 +255,7 @@ }, "properties": { "displayName": "[parameters('workbook2-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Attack Path Overview\"},\"name\":\"attack-path-overview\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"f83ce0ef-a752-49c8-9e57-38e59621e984\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"time_range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":2592000000},\"label\":\"Time Range\",\"value\":{\"durationMs\":31708800000,\"endTime\":\"2024-11-27T13:57:00Z\"}},{\"id\":\"39b26570-b884-4143-9b01-38a84d7f2663\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"domain_name\",\"label\":\"Domain Name\",\"type\":2,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL \\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| distinct domain_name\\n| order by domain_name\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"PHANTOM CORP\"},{\"id\":\"afbf7d11-42d2-4e8c-8b1a-9628bc27ab96\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"attack_path\",\"label\":\"Attack path\",\"type\":2,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where domain_name startswith \\\"{domain_name}\\\"\\n| distinct path_type\\n| order by path_type\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"AzureT0MGAddOwner\"},{\"id\":\"cde4481a-66a9-4c6e-b126-02fa1c4b1b87\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"relationship_type\",\"label\":\"Relation Type\",\"type\":1,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where domain_name startswith \\\"{domain_name}\\\"\\n| where created_at {time_range}\\n | where data_type == \\\"posture_path\\\"\\n| where path_type == \\\"{attack_path}\\\"\\n| extend PathCase = case(\\n path_type hasprefix \\\"LargeDefault\\\", \\\"LargeDefaultRelational\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Relational\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Configuration\\\",\\n \\\"Unknown\\\"\\n)\\n| limit 1\\n| project PathCase\\n| distinct PathCase\",\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where path_type == \\\"{attack_path}\\\"\\n| where domain_name == \\\"{domain_name}\\\"\\n| summarize arg_max(\\\"updated_at\\\", *) by non_tier_zero_principal, tier_zero_principal, principal\\n| extend PathType = path_type\\n| extend PathCase = case(\\n PathType hasprefix \\\"LargeDefault\\\", \\\"Case1\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Case2\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Case3\\\",\\n \\\"Unknown\\\"\\n)\\n// Create columns based on the PathCase\\n| extend [\\\"Group\\\"] = iff(PathCase == \\\"Case1\\\", non_tier_zero_principal, dynamic(null))\\n| extend [\\\"Principal\\\"] = iff(PathCase == \\\"Case1\\\", tier_zero_principal, dynamic(null))\\n| extend [\\\"Non-Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", non_tier_zero_principal, dynamic(null))\\n| extend [\\\"Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", tier_zero_principal, dynamic(null))\\n| extend [\\\"Principal (Tier Zero)\\\"] = iff(PathCase == \\\"Case3\\\", principal, dynamic(null))\\n| extend [\\\"Column Check Error\\\"] = iff(PathCase == \\\"Unknown\\\", tier_zero_principal, dynamic(null))\\n| extend [\\\"PATH_CASE\\\"] = PathCase\\n// Project the relevant columns\\n| project\\n [\\\"Non-Tier Zero Principal\\\"],\\n [\\\"Tier Zero Principal\\\"]\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"relationship_type\",\"comparison\":\"isEqualTo\",\"value\":\"Relational\"},\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where path_type == \\\"{attack_path}\\\"\\n| where domain_name == \\\"{domain_name}\\\"\\n| extend PathType = path_type\\n| extend PathCase = case(\\n PathType hasprefix \\\"LargeDefault\\\", \\\"Case1\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Case2\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Case3\\\",\\n \\\"Unknown\\\"\\n)\\n// Create columns based on the PathCase\\n| extend [\\\"Group\\\"] = iff(PathCase == \\\"Case1\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal\\\"] = iff(PathCase == \\\"Case1\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Non-Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal (Tier Zero)\\\"] = iff(PathCase == \\\"Case3\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Column Check Error\\\"] = iff(PathCase == \\\"Unknown\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"PATH_CASE\\\"] = PathCase\\n// Project the relevant columns\\n| project\\n [\\\"Principal (Tier Zero)\\\"]\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"relationship_type\",\"comparison\":\"isEqualTo\",\"value\":\"Configuration\"},\"name\":\"query - 3 - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where path_type == \\\"{attack_path}\\\"\\n| where domain_name == \\\"{domain_name}\\\"\\n| extend PathType = path_type\\n| extend PathCase = case(\\n PathType hasprefix \\\"LargeDefault\\\", \\\"Case1\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Case2\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Case3\\\",\\n \\\"Unknown\\\"\\n)\\n// Create columns based on the PathCase\\n| extend [\\\"Group\\\"] = iff(PathCase == \\\"Case1\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal\\\"] = iff(PathCase == \\\"Case1\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Non-Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal (Tier Zero)\\\"] = iff(PathCase == \\\"Case3\\\", principal, \\\"\\\")\\n| extend [\\\"Column Check Error\\\"] = iff(PathCase == \\\"Unknown\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"PATH_CASE\\\"] = PathCase\\n// Project the relevant columns\\n| project\\n [\\\"Group\\\"],\\n [\\\"Principal\\\"]\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"relationship_type\",\"comparison\":\"isEqualTo\",\"value\":\"LargeDefaultRelational\"},\"name\":\"query - 3 - Copy - Copy\"},{\"type\":1,\"content\":{\"json\":\"# Exposure %\"},\"name\":\"text - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| where created_at {time_range} \\n| where domain_name startswith \\\"{domain_name}\\\"\\n | where created_at {time_range} \\n| summarize max(exposure/100) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xAxis\":\"created_at\",\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimeNoMsPattern\",\"showUtcTime\":false}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"percent\",\"useGrouping\":true}}}}},\"name\":\"query - 5\"},{\"type\":1,\"content\":{\"json\":\"# Findings\\n## count\"},\"name\":\"text - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| where created_at {time_range} \\n| where domain_name startswith \\\"{domain_name}\\\"\\n | where created_at {time_range} \\n| summarize max(finding_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":2,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xAxis\":\"created_at\",\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimeNoMsPattern\",\"showUtcTime\":false}}}},\"name\":\"query - 8\"},{\"type\":1,\"content\":{\"json\":\"# Principals\\n## count\"},\"name\":\"text - 9\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where domain_name startswith \\\"{domain_name}\\\"\\n| where created_at {time_range} \\n| where path_type == \\\"{attack_path}\\\"\\n| summarize max(principal_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":2,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xAxis\":\"created_at\",\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimeNoMsPattern\",\"showUtcTime\":false}}}},\"name\":\"query - 10\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Attack Path Overview\"},\"name\":\"attack-path-overview\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"f83ce0ef-a752-49c8-9e57-38e59621e984\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"time_range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":2592000000},\"label\":\"Time Range\",\"value\":{\"durationMs\":31708800000,\"endTime\":\"2024-12-09T10:37:00Z\"}},{\"id\":\"39b26570-b884-4143-9b01-38a84d7f2663\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"domain_name\",\"label\":\"Domain Name\",\"type\":2,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL \\n| where data_type == \\\"posture_path\\\"\\n| extend domainNameLabel = strcat(domain_name, \\\" (\\\", domain_type, \\\")\\\")\\n| distinct domain_id, domainNameLabel\\n\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"7Y67V8G4-G4DD-6Y87-8764-8S23KJRE9834\"},{\"id\":\"afbf7d11-42d2-4e8c-8b1a-9628bc27ab96\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"attack_path\",\"label\":\"Attack path\",\"type\":2,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where domain_id == \\\"{domain_name}\\\"\\n| distinct path_type, path_title\\n| order by path_title\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"AzureT0MGAddOwner\"},{\"id\":\"cde4481a-66a9-4c6e-b126-02fa1c4b1b87\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"relationship_type\",\"label\":\"Relation Type\",\"type\":1,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where domain_id == \\\"{domain_name}\\\"\\n| where data_type == \\\"posture_path\\\"\\n| where path_type == \\\"{attack_path}\\\"\\n| extend PathCase = case(\\n path_type hasprefix \\\"LargeDefault\\\", \\\"LargeDefaultRelational\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Relational\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Configuration\\\",\\n \\\"Unknown\\\"\\n)\\n| limit 1\\n| project PathCase\\n| distinct PathCase\",\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range}\\n| where path_type == \\\"{attack_path}\\\"\\n| where domain_id == \\\"{domain_name}\\\"\\n| summarize arg_max(\\\"updated_at\\\", *) by non_tier_zero_principal, tier_zero_principal, principal\\n| extend PathType = path_type\\n| extend PathCase = case(\\n PathType hasprefix \\\"LargeDefault\\\", \\\"Case1\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Case2\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Case3\\\",\\n \\\"Unknown\\\"\\n)\\n// Create columns based on the PathCase\\n| extend [\\\"Group\\\"] = iff(PathCase == \\\"Case1\\\", non_tier_zero_principal, dynamic(null))\\n| extend [\\\"Principal\\\"] = iff(PathCase == \\\"Case1\\\", tier_zero_principal, dynamic(null))\\n| extend [\\\"Non-Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", non_tier_zero_principal, dynamic(null))\\n| extend [\\\"Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", tier_zero_principal, dynamic(null))\\n| extend [\\\"Principal (Tier Zero)\\\"] = iff(PathCase == \\\"Case3\\\", principal, dynamic(null))\\n| extend [\\\"Column Check Error\\\"] = iff(PathCase == \\\"Unknown\\\", tier_zero_principal, dynamic(null))\\n| extend [\\\"PATH_CASE\\\"] = PathCase\\n// Project the relevant columns\\n| project\\n [\\\"Non-Tier Zero Principal\\\"],\\n [\\\"Tier Zero Principal\\\"]\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\"},\"conditionalVisibility\":{\"parameterName\":\"relationship_type\",\"comparison\":\"isEqualTo\",\"value\":\"Relational\"},\"customWidth\":\"100\",\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where path_type == \\\"{attack_path}\\\"\\n| where domain_id == \\\"{domain_name}\\\"\\n| extend PathType = path_type\\n| extend PathCase = case(\\n PathType hasprefix \\\"LargeDefault\\\", \\\"Case1\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Case2\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Case3\\\",\\n \\\"Unknown\\\"\\n)\\n// Create columns based on the PathCase\\n| extend [\\\"Group\\\"] = iff(PathCase == \\\"Case1\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal\\\"] = iff(PathCase == \\\"Case1\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Non-Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal (Tier Zero)\\\"] = iff(PathCase == \\\"Case3\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Column Check Error\\\"] = iff(PathCase == \\\"Unknown\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"PATH_CASE\\\"] = PathCase\\n// Project the relevant columns\\n| project\\n [\\\"Principal (Tier Zero)\\\"]\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"relationship_type\",\"comparison\":\"isEqualTo\",\"value\":\"Configuration\"},\"customWidth\":\"100\",\"name\":\"query - 3 - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture_path\\\"\\n| where created_at {time_range} \\n| where path_type == \\\"{attack_path}\\\"\\n| where domain_id == \\\"{domain_name}\\\"\\n| extend PathType = path_type\\n| extend PathCase = case(\\n PathType hasprefix \\\"LargeDefault\\\", \\\"Case1\\\",\\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \\\"Case2\\\",\\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \\\"Case3\\\",\\n \\\"Unknown\\\"\\n)\\n// Create columns based on the PathCase\\n| extend [\\\"Group\\\"] = iff(PathCase == \\\"Case1\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal\\\"] = iff(PathCase == \\\"Case1\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Non-Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", non_tier_zero_principal, \\\"\\\")\\n| extend [\\\"Tier Zero Principal\\\"] = iff(PathCase == \\\"Case2\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"Principal (Tier Zero)\\\"] = iff(PathCase == \\\"Case3\\\", principal, \\\"\\\")\\n| extend [\\\"Column Check Error\\\"] = iff(PathCase == \\\"Unknown\\\", tier_zero_principal, \\\"\\\")\\n| extend [\\\"PATH_CASE\\\"] = PathCase\\n// Project the relevant columns\\n| project\\n [\\\"Group\\\"],\\n [\\\"Principal\\\"]\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\"},\"conditionalVisibility\":{\"parameterName\":\"relationship_type\",\"comparison\":\"isEqualTo\",\"value\":\"LargeDefaultRelational\"},\"customWidth\":\"100\",\"name\":\"query - 3 - Copy - Copy\"},{\"type\":1,\"content\":{\"json\":\"# Exposure %\"},\"name\":\"text - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| where created_at {time_range}\\n| where domain_id == \\\"{domain_name}\\\"\\n| extend exposureAsPercent = exposure/100\\n| summarize max(exposureAsPercent) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xAxis\":\"created_at\",\"yAxis\":[\"max_exposureAsPercent\"],\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimeNoMsPattern\",\"showUtcTime\":false}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"percent\",\"useGrouping\":true},\"missingSparkDataOption\":\"Zero\"},\"min\":0,\"max\":1}}},\"name\":\"query - 5\"},{\"type\":1,\"content\":{\"json\":\"# Findings\\n## count\"},\"name\":\"text - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| where created_at {time_range} \\n| where domain_id == \\\"{domain_name}\\\"\\n| summarize max(finding_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":2,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xAxis\":\"created_at\",\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimeNoMsPattern\",\"showUtcTime\":false}}}},\"name\":\"query - 8\"},{\"type\":1,\"content\":{\"json\":\"# Principals\\n## count\"},\"name\":\"text - 9\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"finding_export\\\"\\n| where created_at {time_range} \\n| where domain_id == \\\"{domain_name}\\\"\\n| where path_type == \\\"{attack_path}\\\"\\n| summarize max(domain_impact_value) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":2,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xAxis\":\"created_at\",\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimeNoMsPattern\",\"showUtcTime\":false}}}},\"name\":\"query - 10\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -266,7 +266,7 @@ "apiVersion": "2022-01-01-preview", "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId2'),'/'))))]", "properties": { - "description": "@{workbookKey=BloodHoundEnterpriseAttackPathOverview; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise attack path.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0; title=BloodHound Enterprise Attack Path Overview; templateRelativePath=BloodHoundEnterpriseAttackPathOverview.json; subtitle=Overview; provider=SpecterOps}.description", + "description": "@{workbookKey=BloodHoundEnterpriseAttackPathOverview; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise attack path.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=2.0; title=BloodHound Enterprise Attack Path Overview; templateRelativePath=BloodHoundEnterpriseAttackPathOverview.json; subtitle=Overview; provider=SpecterOps}.description", "parentId": "[variables('workbookId2')]", "contentId": "[variables('_workbookContentId2')]", "kind": "Workbook", @@ -325,7 +325,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseAuditLogs Workbook with template version 3.1.0", + "description": "BloodHoundEnterpriseAuditLogs Workbook with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion3')]", @@ -343,7 +343,7 @@ }, "properties": { "displayName": "[parameters('workbook3-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Audit Logs\\n---\\n\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"f20bb42b-e116-4bc5-9f3d-2fd42491a340\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"time_param\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}]},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":7776000000}},{\"id\":\"44ebb9da-a987-4690-8fc5-f844a0c6daee\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"event_type_param\",\"label\":\"Type\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"audit_log\\\"\\n| where created_at {time_param}\\n| distinct event_type\\n| project event_type\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"LoginAttempt\",\"CreateAuthToken\",\"UpdateAuthSecret\",\"CreateUser\",\"AcceptEULA\",\"UpdateParameter\",\"UnacceptRisk\",\"AcceptRisk\",\"UnauthorizedAccessAttempt\",\"UpdateUser\",\"DeleteAuthToken\",\"ExportRelationshipRisks\"]}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"audit_log\\\"\\n| where created_at {time_param}\\n| where event_type in ({event_type_param})\\n| project [\\\"Time\\\"]=created_at, [\\\"Event\\\"]=event_details\",\"size\":1,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 2\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Audit Logs\\n---\\n\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"f20bb42b-e116-4bc5-9f3d-2fd42491a340\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"time_param\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":172800000}},{\"id\":\"44ebb9da-a987-4690-8fc5-f844a0c6daee\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"event_type_param\",\"label\":\"Type\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"audit_log\\\"\\n| distinct event_type\\n| project value = event_type\\n| sort by value asc\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"audit_log\\\"\\n| where created_at {time_param}\\n| where event_type in ({event_type_param})\\n| project [\\\"Time\\\"]=created_at, [\\\"Event\\\"]=event_details\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Event\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"90%\"}}]}},\"name\":\"query - 2\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -354,7 +354,7 @@ "apiVersion": "2022-01-01-preview", "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId3'),'/'))))]", "properties": { - "description": "@{workbookKey=BloodHoundEnterpriseAuditLogs; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise audit logs.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0; title=BloodHound Enterprise Audit Logs; templateRelativePath=BloodHoundEnterpriseAuditLogs.json; subtitle=; provider=SpecterOps}.description", + "description": "@{workbookKey=BloodHoundEnterpriseAuditLogs; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise audit logs.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=2.0; title=BloodHound Enterprise Audit Logs; templateRelativePath=BloodHoundEnterpriseAuditLogs.json; subtitle=; provider=SpecterOps}.description", "parentId": "[variables('workbookId3')]", "contentId": "[variables('_workbookContentId3')]", "kind": "Workbook", @@ -413,7 +413,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterprisePosture Workbook with template version 3.1.0", + "description": "BloodHoundEnterprisePosture Workbook with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion4')]", @@ -431,7 +431,7 @@ }, "properties": { "displayName": "[parameters('workbook4-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Posture\"},\"name\":\"posture\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"868cc59a-f969-451f-9d87-e6c554cb54e3\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"time_range\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":2592000000}},{\"id\":\"586cb0f9-15b3-4887-bf7a-842121615cb0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"domain_name\",\"label\":\"Domain\",\"type\":2,\"description\":\"Domain to display\",\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range} \\n| distinct domain_name\\n| order by domain_name\\n\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"SPECTEROPS DEVELOPMENT \"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":1,\"content\":{\"json\":\"## Domain Exposure %\"},\"name\":\"text - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range}\\n| where isnull('{domain_name}') or domain_name == '{domain_name}'\\n| summarize max(exposure_index) by bin(created_at, 1d), domain_name\",\"size\":1,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\"},\"name\":\"query - 2\"},{\"type\":1,\"content\":{\"json\":\"## Critical Attack Paths\"},\"name\":\"text - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range} \\n| where domain_name == '{domain_name}'\\n| summarize max(finding_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\"},\"name\":\"query - 4\"},{\"type\":1,\"content\":{\"json\":\"## Tier Zero Count\"},\"name\":\"text - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range} \\n| where domain_name == '{domain_name}'\\n| summarize max(tier_zero_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\"},\"name\":\"query - 7\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Posture\"},\"name\":\"posture\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"868cc59a-f969-451f-9d87-e6c554cb54e3\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"time_range\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":7776000000}},{\"id\":\"586cb0f9-15b3-4887-bf7a-842121615cb0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"domain_name\",\"label\":\"Domain\",\"type\":2,\"description\":\"Domain to display\",\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| extend domainNameType = strcat(domain_name, \\\" (\\\", domain_type, \\\")\\\")\\n| distinct domain_id, domainNameType\\n| order by domainNameType\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"S-1-5-21-3702535222-3822678775-2090119576\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":1,\"content\":{\"json\":\"## Domain Exposure %\"},\"name\":\"text - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range}\\n| where domain_id == '{domain_name}'\\n| summarize max(exposure_index/100) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimePattern\",\"showUtcTime\":true}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"percent\",\"useGrouping\":true}},\"min\":0,\"max\":1}}},\"name\":\"query - 2\"},{\"type\":1,\"content\":{\"json\":\"## Critical Attack Paths\"},\"name\":\"text - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range} \\n| where domain_id == '{domain_name}'\\n| summarize max(finding_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimePattern\",\"showUtcTime\":true}}}},\"name\":\"query - 4\"},{\"type\":1,\"content\":{\"json\":\"## Tier Zero Count\"},\"name\":\"text - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"posture\\\"\\n| where created_at {time_range} \\n| where domain_id == '{domain_name}'\\n| summarize max(tier_zero_count) by bin(created_at, 1d), domain_name\",\"size\":0,\"aggregation\":5,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"areachart\",\"chartSettings\":{\"xSettings\":{\"dateFormatSettings\":{\"formatName\":\"shortDateTimePattern\",\"showUtcTime\":true}}}},\"name\":\"query - 7\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -442,7 +442,7 @@ "apiVersion": "2022-01-01-preview", "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId4'),'/'))))]", "properties": { - "description": "@{workbookKey=BloodHoundEnterprisePosture; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise domain posture.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0; title=BloodHound Enterprise Domain Posture; templateRelativePath=BloodHoundEnterprisePosture.json; subtitle=; provider=SpecterOps}.description", + "description": "@{workbookKey=BloodHoundEnterprisePosture; logoFileName=BHE_Logo.svg; description=Gain insights into BloodHound Enterprise domain posture.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=2.0; title=BloodHound Enterprise Domain Posture; templateRelativePath=BloodHoundEnterprisePosture.json; subtitle=; provider=SpecterOps}.description", "parentId": "[variables('workbookId4')]", "contentId": "[variables('_workbookContentId4')]", "kind": "Workbook", @@ -501,7 +501,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseTierZeroSearch Workbook with template version 3.1.0", + "description": "BloodHoundEnterpriseTierZeroSearch Workbook with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion5')]", @@ -519,7 +519,7 @@ }, "properties": { "displayName": "[parameters('workbook5-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Tier Zero Search\\n---\\n\"},\"name\":\"tier_zero\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"6daab3de-20af-4001-b60c-f726da5e1a36\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"domain_name_param\",\"label\":\"Domain Name\",\"type\":2,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"t0_export\\\"\\n| distinct domain_name\\n| project domain_name\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"SPECTEROPS DEVELOPMENT \"},{\"id\":\"cd7af2df-a53e-46a2-bdea-ad99c3f2034f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"type_param\",\"label\":\"Type\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"BloodHoundLogs_CL\\n| where data_type ==\\\"t0_export\\\"\\n| distinct event_details\\n| project event_details\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"GPO\",\"Computer\",\"AZApp\",\"AZServicePrincipal\",\"OU\",\"Domain\",\"Container\",\"Group\",\"User\",\"AZUser\",\"AZRole\",\"AZTenant\",\"AZGroup\"]}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type ==\\\"t0_export\\\"\\n| where domain_name == \\\"{domain_name_param}\\\"\\n| where event_details in ({type_param})\\n| project [\\\"Name\\\"]=tier_zero_principal, [\\\"Domain Name\\\"]=domain_name, [\\\"Type\\\"]=path_type, [\\\"Object ID\\\"]=domain_sid\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 3\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Tier Zero Search\\n---\\n\"},\"name\":\"tier_zero\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"6daab3de-20af-4001-b60c-f726da5e1a36\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"domain_name_param\",\"label\":\"Domain Name\",\"type\":2,\"isRequired\":true,\"query\":\"BloodHoundLogs_CL\\n| where data_type == \\\"t0_export\\\"\\n| extend domainNameType = strcat(domain_name, \\\" (\\\", domain_type, \\\")\\\")\\n| distinct domain_id, domainNameType\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"S-1-5-21-2697957641-2271029196-387917394\"},{\"id\":\"cd7af2df-a53e-46a2-bdea-ad99c3f2034f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"type_param\",\"label\":\"Type\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"BloodHoundLogs_CL\\n| where data_type ==\\\"t0_export\\\"\\n| distinct event_details\\n| sort by event_details asc\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"BloodHoundLogs_CL\\n| where data_type ==\\\"t0_export\\\"\\n| where domain_id == \\\"{domain_name_param}\\\"\\n| where event_details in ({type_param})\\n| summarize arg_max(\\\"updated_at\\\", *) by tier_zero_principal, domain_id, event_details\\n| project [\\\"Name\\\"]=tier_zero_principal, [\\\"Environment Name\\\"]=domain_name, [\\\"Type\\\"]=event_details, [\\\"Object ID\\\"]=finding_id\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"sortBy\":[{\"itemKey\":\"Object ID\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"Object ID\",\"sortOrder\":1}]},\"name\":\"query - 3\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -589,7 +589,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseCriticalAttackPaths_AnalyticalRules Analytics Rule with template version 3.1.0", + "description": "BloodHoundEnterpriseCriticalAttackPaths_AnalyticalRules Analytics Rule with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('analyticRuleObject1').analyticRuleVersion1]", @@ -606,7 +606,7 @@ "description": "The number of critical attack paths has increased over the past 7 days.", "displayName": "BloodHound Enterprise - Number of critical attack paths increase", "enabled": false, - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at > ago (7d)\n| summarize min_critical_risk_count = min(finding_count), arg_max(created_at, current_critical_risk_count = finding_count) by domain_name\n| extend difference = current_critical_risk_count - min_critical_risk_count\n| where difference > 0", + "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where created_at > ago (7d)\n| summarize min_critical_risk_count = min(finding_count), arg_max(created_at, current_critical_risk_count = finding_count, domain_name) by domain_name\n| extend difference = current_critical_risk_count - min_critical_risk_count\n| where difference > 0", "queryFrequency": "P7D", "queryPeriod": "P7D", "severity": "Medium", @@ -687,7 +687,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseExposure_AnalyticalRules Analytics Rule with template version 3.1.0", + "description": "BloodHoundEnterpriseExposure_AnalyticalRules Analytics Rule with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('analyticRuleObject2').analyticRuleVersion2]", @@ -704,7 +704,7 @@ "description": "The exposure for a domain has increased by more than 5% over the past 7 days.", "displayName": "BloodHound Enterprise - Exposure increase", "enabled": false, - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at > ago (7d)\n| summarize min(exposure_index), arg_max(created_at, exposure_index) by domain_name\n| extend min_exposure = min_exposure_index * 100, latest_exposure = exposure_index * 100\n| where latest_exposure - min_exposure > 5", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at > ago (7d)\n| summarize min(exposure_index), arg_max(created_at, exposure_index, domain_name) by domain_name\n| extend min_exposure = min_exposure_index * 100, latest_exposure = exposure_index * 100\n| where latest_exposure - min_exposure > 5", "queryFrequency": "P7D", "queryPeriod": "P7D", "severity": "High", @@ -785,7 +785,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHoundEnterpriseTierZeroAssets_AnalyticalRules Analytics Rule with template version 3.1.0", + "description": "BloodHoundEnterpriseTierZeroAssets_AnalyticalRules Analytics Rule with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('analyticRuleObject3').analyticRuleVersion3]", @@ -802,7 +802,7 @@ "description": "The number of Tier Zero assets has increased by more than 5% over the past 7 days.", "displayName": "BloodHound Enterprise - Number of Tier Zero assets increase", "enabled": false, - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at > ago (7d)\n| summarize min_tier_zero = min(tier_zero_count), max_tier_zero = arg_max(created_at, current_tier_zero = tier_zero_count) by domain_name\n| extend percent_difference = ((current_tier_zero - min_tier_zero) / min_tier_zero) * 100\n| where percent_difference > 5", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at > ago (7d)\n| 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\n| extend percent_difference = ((current_tier_zero - min_tier_zero) / min_tier_zero) * 100\n| where percent_difference > 5", "queryFrequency": "P7D", "queryPeriod": "P7D", "severity": "Medium", @@ -883,7 +883,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "BloodHound Enterprise data connector with template version 3.1.0", + "description": "BloodHound Enterprise data connector with template version 3.1.1", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion1')]", @@ -995,7 +995,7 @@ "description": "**Option 2 - Manual Deployment of Azure Functions**\n\nUse the following step-by-step instructions to deploy the BloodHound Enterprise connector manually with Azure Functions." }, { - "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." }, { "description": "**2. Configure the Function App**\n\n 1. In the Function App, select the Function App Name and select **Settings**, then **Environment variables**.\n\n 2. In the **Environment variables** tab, add or modify each of the following variables individually, with their respective string values (case-sensitive): \n\t\t BHEDomain\n\t\t BHETokenId\n\t\t BHETokenKey\n\t\t workspaceID\n\t\t dcrImmutableId\n\t\t logsIngestionUrl\n\t\t logAnalyticsUri (optional)\n\n 3. Once all variables have been set, click **Apply**." @@ -1183,7 +1183,7 @@ "description": "**Option 2 - Manual Deployment of Azure Functions**\n\nUse the following step-by-step instructions to deploy the BloodHound Enterprise connector manually with Azure Functions." }, { - "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." }, { "description": "**2. Configure the Function App**\n\n 1. In the Function App, select the Function App Name and select **Settings**, then **Environment variables**.\n\n 2. In the **Environment variables** tab, add or modify each of the following variables individually, with their respective string values (case-sensitive): \n\t\t BHEDomain\n\t\t BHETokenId\n\t\t BHETokenKey\n\t\t workspaceID\n\t\t dcrImmutableId\n\t\t logsIngestionUrl\n\t\t logAnalyticsUri (optional)\n\n 3. Once all variables have been set, click **Apply**." @@ -1198,7 +1198,7 @@ "apiVersion": "2023-04-01-preview", "location": "[parameters('workspace-location')]", "properties": { - "version": "3.1.0", + "version": "3.1.1", "kind": "Solution", "contentSchemaVersion": "3.0.0", "displayName": "BloodHound Enterprise", diff --git a/Solutions/BloodHound Enterprise/ReleaseNotes.md b/Solutions/BloodHound Enterprise/ReleaseNotes.md index 633bb366349..94c4108f218 100644 --- a/Solutions/BloodHound Enterprise/ReleaseNotes.md +++ b/Solutions/BloodHound Enterprise/ReleaseNotes.md @@ -2,3 +2,4 @@ |-------------|--------------------------------|---------------------------------------------| | 3.0.0 | 20-07-2023 | Initial Solution Release | | 3.1.0 | 17-11-2024 | Updated Solution: table schema updated, new workbooks, new golang function app uses bloodhound-golang-sdk | +| 3.1.1 | 17-12-2024 | Updated workbooks- principals now shown properly, percentages calculated correctly, function app mapping to custom table fixed | diff --git a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathDetails.json b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathDetails.json index be71bdbef68..7cd6659136f 100644 --- a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathDetails.json +++ b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathDetails.json @@ -12,7 +12,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where exposure != 0\n| where path_type != \"\"\n| extend Severity = case(\n exposure < 40.0, 'low',\n exposure >= 40.0 and exposure <= 79.0, 'medium',\n exposure > 79.0 and exposure <= 94.0, 'high',\n exposure > 94.0, 'critical',\n 'unknown'\n )\n| summarize arg_max(updated_at, *), Count = count() by domain_name, path_type\n| project \n ['Domain Name'] = domain_name,\n ['Attack Path Type'] = path_type,\n Severity,\n ['Exposure %'] = round(exposure, 2)\n", + "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| extend Severity = case(\n exposure < 40.0, 'low',\n exposure >= 40.0 and exposure <= 79.0, 'medium',\n exposure > 79.0 and exposure <= 94.0, 'high',\n exposure > 94.0, 'critical',\n 'unknown'\n )\n| summarize arg_max(updated_at, *) by domain_id, domain_name, path_type, path_title\n| sort by round(exposure) desc\n| project \n ['Domain'] = domain_name,\n ['Attack path'] = path_title,\n Severity,\n ['Exposure (%)'] = round(exposure, 2)\n\n", "size": 3, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" diff --git a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathOverview.json b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathOverview.json index 72c21c0b92c..ef8f2f5b279 100644 --- a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathOverview.json +++ b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAttackPathOverview.json @@ -75,7 +75,7 @@ "label": "Time Range", "value": { "durationMs": 31708800000, - "endTime": "2024-11-27T13:57:00.000Z" + "endTime": "2024-12-09T10:37:00.000Z" } }, { @@ -85,14 +85,13 @@ "label": "Domain Name", "type": 2, "isRequired": true, - "query": "BloodHoundLogs_CL \n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| distinct domain_name\n| order by domain_name", + "query": "BloodHoundLogs_CL \n| where data_type == \"posture_path\"\n| extend domainNameLabel = strcat(domain_name, \" (\", domain_type, \")\")\n| distinct domain_id, domainNameLabel\n", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "value": "PHANTOM CORP" + "value": "7Y67V8G4-G4DD-6Y87-8764-8S23KJRE9834" }, { "id": "afbf7d11-42d2-4e8c-8b1a-9628bc27ab96", @@ -101,9 +100,8 @@ "label": "Attack path", "type": 2, "isRequired": true, - "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where domain_name startswith \"{domain_name}\"\n| distinct path_type\n| order by path_type", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where domain_id == \"{domain_name}\"\n| distinct path_type, path_title\n| order by path_title", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -117,7 +115,7 @@ "label": "Relation Type", "type": 1, "isRequired": true, - "query": "BloodHoundLogs_CL\n| where domain_name startswith \"{domain_name}\"\n| where created_at {time_range}\n | where data_type == \"posture_path\"\n| where path_type == \"{attack_path}\"\n| extend PathCase = case(\n path_type hasprefix \"LargeDefault\", \"LargeDefaultRelational\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Relational\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Configuration\",\n \"Unknown\"\n)\n| limit 1\n| project PathCase\n| distinct PathCase", + "query": "BloodHoundLogs_CL\n| where domain_id == \"{domain_name}\"\n| where data_type == \"posture_path\"\n| where path_type == \"{attack_path}\"\n| extend PathCase = case(\n path_type hasprefix \"LargeDefault\", \"LargeDefaultRelational\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Relational\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Configuration\",\n \"Unknown\"\n)\n| limit 1\n| project PathCase\n| distinct PathCase", "isHiddenWhenLocked": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" @@ -133,23 +131,25 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where path_type == \"{attack_path}\"\n| where domain_name == \"{domain_name}\"\n| summarize arg_max(\"updated_at\", *) by non_tier_zero_principal, tier_zero_principal, principal\n| extend PathType = path_type\n| extend PathCase = case(\n PathType hasprefix \"LargeDefault\", \"Case1\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Case2\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Case3\",\n \"Unknown\"\n)\n// Create columns based on the PathCase\n| extend [\"Group\"] = iff(PathCase == \"Case1\", non_tier_zero_principal, dynamic(null))\n| extend [\"Principal\"] = iff(PathCase == \"Case1\", tier_zero_principal, dynamic(null))\n| extend [\"Non-Tier Zero Principal\"] = iff(PathCase == \"Case2\", non_tier_zero_principal, dynamic(null))\n| extend [\"Tier Zero Principal\"] = iff(PathCase == \"Case2\", tier_zero_principal, dynamic(null))\n| extend [\"Principal (Tier Zero)\"] = iff(PathCase == \"Case3\", principal, dynamic(null))\n| extend [\"Column Check Error\"] = iff(PathCase == \"Unknown\", tier_zero_principal, dynamic(null))\n| extend [\"PATH_CASE\"] = PathCase\n// Project the relevant columns\n| project\n [\"Non-Tier Zero Principal\"],\n [\"Tier Zero Principal\"]", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range}\n| where path_type == \"{attack_path}\"\n| where domain_id == \"{domain_name}\"\n| summarize arg_max(\"updated_at\", *) by non_tier_zero_principal, tier_zero_principal, principal\n| extend PathType = path_type\n| extend PathCase = case(\n PathType hasprefix \"LargeDefault\", \"Case1\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Case2\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Case3\",\n \"Unknown\"\n)\n// Create columns based on the PathCase\n| extend [\"Group\"] = iff(PathCase == \"Case1\", non_tier_zero_principal, dynamic(null))\n| extend [\"Principal\"] = iff(PathCase == \"Case1\", tier_zero_principal, dynamic(null))\n| extend [\"Non-Tier Zero Principal\"] = iff(PathCase == \"Case2\", non_tier_zero_principal, dynamic(null))\n| extend [\"Tier Zero Principal\"] = iff(PathCase == \"Case2\", tier_zero_principal, dynamic(null))\n| extend [\"Principal (Tier Zero)\"] = iff(PathCase == \"Case3\", principal, dynamic(null))\n| extend [\"Column Check Error\"] = iff(PathCase == \"Unknown\", tier_zero_principal, dynamic(null))\n| extend [\"PATH_CASE\"] = PathCase\n// Project the relevant columns\n| project\n [\"Non-Tier Zero Principal\"],\n [\"Tier Zero Principal\"]", "size": 0, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "visualization": "table" }, "conditionalVisibility": { "parameterName": "relationship_type", "comparison": "isEqualTo", "value": "Relational" }, + "customWidth": "100", "name": "query - 3" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where path_type == \"{attack_path}\"\n| where domain_name == \"{domain_name}\"\n| extend PathType = path_type\n| extend PathCase = case(\n PathType hasprefix \"LargeDefault\", \"Case1\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Case2\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Case3\",\n \"Unknown\"\n)\n// Create columns based on the PathCase\n| extend [\"Group\"] = iff(PathCase == \"Case1\", non_tier_zero_principal, \"\")\n| extend [\"Principal\"] = iff(PathCase == \"Case1\", tier_zero_principal, \"\")\n| extend [\"Non-Tier Zero Principal\"] = iff(PathCase == \"Case2\", non_tier_zero_principal, \"\")\n| extend [\"Tier Zero Principal\"] = iff(PathCase == \"Case2\", tier_zero_principal, \"\")\n| extend [\"Principal (Tier Zero)\"] = iff(PathCase == \"Case3\", tier_zero_principal, \"\")\n| extend [\"Column Check Error\"] = iff(PathCase == \"Unknown\", tier_zero_principal, \"\")\n| extend [\"PATH_CASE\"] = PathCase\n// Project the relevant columns\n| project\n [\"Principal (Tier Zero)\"]", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where path_type == \"{attack_path}\"\n| where domain_id == \"{domain_name}\"\n| extend PathType = path_type\n| extend PathCase = case(\n PathType hasprefix \"LargeDefault\", \"Case1\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Case2\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Case3\",\n \"Unknown\"\n)\n// Create columns based on the PathCase\n| extend [\"Group\"] = iff(PathCase == \"Case1\", non_tier_zero_principal, \"\")\n| extend [\"Principal\"] = iff(PathCase == \"Case1\", tier_zero_principal, \"\")\n| extend [\"Non-Tier Zero Principal\"] = iff(PathCase == \"Case2\", non_tier_zero_principal, \"\")\n| extend [\"Tier Zero Principal\"] = iff(PathCase == \"Case2\", tier_zero_principal, \"\")\n| extend [\"Principal (Tier Zero)\"] = iff(PathCase == \"Case3\", tier_zero_principal, \"\")\n| extend [\"Column Check Error\"] = iff(PathCase == \"Unknown\", tier_zero_principal, \"\")\n| extend [\"PATH_CASE\"] = PathCase\n// Project the relevant columns\n| project\n [\"Principal (Tier Zero)\"]", "size": 0, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" @@ -159,22 +159,25 @@ "comparison": "isEqualTo", "value": "Configuration" }, + "customWidth": "100", "name": "query - 3 - Copy" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where path_type == \"{attack_path}\"\n| where domain_name == \"{domain_name}\"\n| extend PathType = path_type\n| extend PathCase = case(\n PathType hasprefix \"LargeDefault\", \"Case1\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Case2\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Case3\",\n \"Unknown\"\n)\n// Create columns based on the PathCase\n| extend [\"Group\"] = iff(PathCase == \"Case1\", non_tier_zero_principal, \"\")\n| extend [\"Principal\"] = iff(PathCase == \"Case1\", tier_zero_principal, \"\")\n| extend [\"Non-Tier Zero Principal\"] = iff(PathCase == \"Case2\", non_tier_zero_principal, \"\")\n| extend [\"Tier Zero Principal\"] = iff(PathCase == \"Case2\", tier_zero_principal, \"\")\n| extend [\"Principal (Tier Zero)\"] = iff(PathCase == \"Case3\", principal, \"\")\n| extend [\"Column Check Error\"] = iff(PathCase == \"Unknown\", tier_zero_principal, \"\")\n| extend [\"PATH_CASE\"] = PathCase\n// Project the relevant columns\n| project\n [\"Group\"],\n [\"Principal\"]", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where path_type == \"{attack_path}\"\n| where domain_id == \"{domain_name}\"\n| extend PathType = path_type\n| extend PathCase = case(\n PathType hasprefix \"LargeDefault\", \"Case1\",\n isnotempty(tier_zero_principal) and isnotempty(non_tier_zero_principal), \"Case2\",\n isnotempty(tier_zero_principal) and isempty(non_tier_zero_principal), \"Case3\",\n \"Unknown\"\n)\n// Create columns based on the PathCase\n| extend [\"Group\"] = iff(PathCase == \"Case1\", non_tier_zero_principal, \"\")\n| extend [\"Principal\"] = iff(PathCase == \"Case1\", tier_zero_principal, \"\")\n| extend [\"Non-Tier Zero Principal\"] = iff(PathCase == \"Case2\", non_tier_zero_principal, \"\")\n| extend [\"Tier Zero Principal\"] = iff(PathCase == \"Case2\", tier_zero_principal, \"\")\n| extend [\"Principal (Tier Zero)\"] = iff(PathCase == \"Case3\", principal, \"\")\n| extend [\"Column Check Error\"] = iff(PathCase == \"Unknown\", tier_zero_principal, \"\")\n| extend [\"PATH_CASE\"] = PathCase\n// Project the relevant columns\n| project\n [\"Group\"],\n [\"Principal\"]", "size": 0, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "visualization": "table" }, "conditionalVisibility": { "parameterName": "relationship_type", "comparison": "isEqualTo", "value": "LargeDefaultRelational" }, + "customWidth": "100", "name": "query - 3 - Copy - Copy" }, { @@ -188,7 +191,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where created_at {time_range} \n| where domain_name startswith \"{domain_name}\"\n | where created_at {time_range} \n| summarize max(exposure/100) by bin(created_at, 1d), domain_name", + "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where created_at {time_range}\n| where domain_id == \"{domain_name}\"\n| extend exposureAsPercent = exposure/100\n| summarize max(exposureAsPercent) by bin(created_at, 1d), domain_name", "size": 0, "aggregation": 5, "queryType": 0, @@ -196,6 +199,9 @@ "visualization": "areachart", "chartSettings": { "xAxis": "created_at", + "yAxis": [ + "max_exposureAsPercent" + ], "xSettings": { "dateFormatSettings": { "formatName": "shortDateTimeNoMsPattern", @@ -208,8 +214,11 @@ "options": { "style": "percent", "useGrouping": true - } - } + }, + "missingSparkDataOption": "Zero" + }, + "min": 0, + "max": 1 } } }, @@ -226,7 +235,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where created_at {time_range} \n| where domain_name startswith \"{domain_name}\"\n | where created_at {time_range} \n| summarize max(finding_count) by bin(created_at, 1d), domain_name", + "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where created_at {time_range} \n| where domain_id == \"{domain_name}\"\n| summarize max(finding_count) by bin(created_at, 1d), domain_name", "size": 0, "aggregation": 2, "queryType": 0, @@ -255,7 +264,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture_path\"\n| where created_at {time_range} \n| where domain_name startswith \"{domain_name}\"\n| where created_at {time_range} \n| where path_type == \"{attack_path}\"\n| summarize max(principal_count) by bin(created_at, 1d), domain_name", + "query": "BloodHoundLogs_CL\n| where data_type == \"finding_export\"\n| where created_at {time_range} \n| where domain_id == \"{domain_name}\"\n| where path_type == \"{attack_path}\"\n| summarize max(domain_impact_value) by bin(created_at, 1d), domain_name", "size": 0, "aggregation": 2, "queryType": 0, diff --git a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAuditLogs.json b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAuditLogs.json index bcfd33c45ce..426f39c1a80 100644 --- a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAuditLogs.json +++ b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseAuditLogs.json @@ -67,13 +67,14 @@ { "durationMs": 7776000000 } - ] + ], + "allowCustom": true }, "timeContext": { "durationMs": 86400000 }, "value": { - "durationMs": 7776000000 + "durationMs": 172800000 } }, { @@ -86,26 +87,18 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "BloodHoundLogs_CL\n| where data_type == \"audit_log\"\n| where created_at {time_param}\n| distinct event_type\n| project event_type", + "query": "BloodHoundLogs_CL\n| where data_type == \"audit_log\"\n| distinct event_type\n| project value = event_type\n| sort by value asc", "typeSettings": { - "additionalResourceOptions": [], + "additionalResourceOptions": [ + "value::all" + ], "showDefault": false }, + "defaultValue": "value::all", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "value": [ - "LoginAttempt", - "CreateAuthToken", - "UpdateAuthSecret", - "CreateUser", - "AcceptEULA", - "UpdateParameter", - "UnacceptRisk", - "AcceptRisk", - "UnauthorizedAccessAttempt", - "UpdateUser", - "DeleteAuthToken", - "ExportRelationshipRisks" + "value::all" ] } ], @@ -120,9 +113,20 @@ "content": { "version": "KqlItem/1.0", "query": "BloodHoundLogs_CL\n| where data_type == \"audit_log\"\n| where created_at {time_param}\n| where event_type in ({event_type_param})\n| project [\"Time\"]=created_at, [\"Event\"]=event_details", - "size": 1, + "size": 3, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "gridSettings": { + "formatters": [ + { + "columnMatch": "Event", + "formatter": 0, + "formatOptions": { + "customColumnWidthSetting": "90%" + } + } + ] + } }, "name": "query - 2" } diff --git a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterprisePosture.json b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterprisePosture.json index 6031cd86023..a0ae4f60eb7 100644 --- a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterprisePosture.json +++ b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterprisePosture.json @@ -74,7 +74,7 @@ "durationMs": 86400000 }, "value": { - "durationMs": 2592000000 + "durationMs": 7776000000 } }, { @@ -85,14 +85,13 @@ "type": 2, "description": "Domain to display", "isRequired": true, - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range} \n| distinct domain_name\n| order by domain_name\n", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| extend domainNameType = strcat(domain_name, \" (\", domain_type, \")\")\n| distinct domain_id, domainNameType\n| order by domainNameType", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "value": "SPECTEROPS DEVELOPMENT " + "value": "S-1-5-21-3702535222-3822678775-2090119576" } ], "style": "above", @@ -112,12 +111,31 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range}\n| where isnull('{domain_name}') or domain_name == '{domain_name}'\n| summarize max(exposure_index) by bin(created_at, 1d), domain_name", - "size": 1, + "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range}\n| where domain_id == '{domain_name}'\n| summarize max(exposure_index/100) by bin(created_at, 1d), domain_name", + "size": 0, "aggregation": 5, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "areachart" + "visualization": "areachart", + "chartSettings": { + "xSettings": { + "dateFormatSettings": { + "formatName": "shortDateTimePattern", + "showUtcTime": true + } + }, + "ySettings": { + "numberFormatSettings": { + "unit": 0, + "options": { + "style": "percent", + "useGrouping": true + } + }, + "min": 0, + "max": 1 + } + } }, "name": "query - 2" }, @@ -132,12 +150,20 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range} \n| where domain_name == '{domain_name}'\n| summarize max(finding_count) by bin(created_at, 1d), domain_name", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range} \n| where domain_id == '{domain_name}'\n| summarize max(finding_count) by bin(created_at, 1d), domain_name", "size": 0, "aggregation": 5, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "areachart" + "visualization": "areachart", + "chartSettings": { + "xSettings": { + "dateFormatSettings": { + "formatName": "shortDateTimePattern", + "showUtcTime": true + } + } + } }, "name": "query - 4" }, @@ -152,12 +178,20 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range} \n| where domain_name == '{domain_name}'\n| summarize max(tier_zero_count) by bin(created_at, 1d), domain_name", + "query": "BloodHoundLogs_CL\n| where data_type == \"posture\"\n| where created_at {time_range} \n| where domain_id == '{domain_name}'\n| summarize max(tier_zero_count) by bin(created_at, 1d), domain_name", "size": 0, "aggregation": 5, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "areachart" + "visualization": "areachart", + "chartSettings": { + "xSettings": { + "dateFormatSettings": { + "formatName": "shortDateTimePattern", + "showUtcTime": true + } + } + } }, "name": "query - 7" } diff --git a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseTierZeroSearch.json b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseTierZeroSearch.json index afb93f8c579..6e86738b8cb 100644 --- a/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseTierZeroSearch.json +++ b/Solutions/BloodHound Enterprise/Workbooks/BloodHoundEnterpriseTierZeroSearch.json @@ -20,13 +20,10 @@ "label": "Domain Name", "type": 2, "isRequired": true, - "query": "BloodHoundLogs_CL\n| where data_type == \"t0_export\"\n| distinct domain_name\n| project domain_name", - "typeSettings": { - "additionalResourceOptions": [] - }, + "query": "BloodHoundLogs_CL\n| where data_type == \"t0_export\"\n| extend domainNameType = strcat(domain_name, \" (\", domain_type, \")\")\n| distinct domain_id, domainNameType", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "value": "SPECTEROPS DEVELOPMENT " + "value": "S-1-5-21-2697957641-2271029196-387917394" }, { "id": "cd7af2df-a53e-46a2-bdea-ad99c3f2034f", @@ -38,27 +35,17 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "BloodHoundLogs_CL\n| where data_type ==\"t0_export\"\n| distinct event_details\n| project event_details", + "query": "BloodHoundLogs_CL\n| where data_type ==\"t0_export\"\n| distinct event_details\n| sort by event_details asc", "typeSettings": { - "additionalResourceOptions": [], + "additionalResourceOptions": [ + "value::all" + ], "showDefault": false }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "value": [ - "GPO", - "Computer", - "AZApp", - "AZServicePrincipal", - "OU", - "Domain", - "Container", - "Group", - "User", - "AZUser", - "AZRole", - "AZTenant", - "AZGroup" + "value::all" ] } ], @@ -72,10 +59,24 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "BloodHoundLogs_CL\n| where data_type ==\"t0_export\"\n| where domain_name == \"{domain_name_param}\"\n| where event_details in ({type_param})\n| project [\"Name\"]=tier_zero_principal, [\"Domain Name\"]=domain_name, [\"Type\"]=path_type, [\"Object ID\"]=domain_sid", - "size": 0, + "query": "BloodHoundLogs_CL\n| where data_type ==\"t0_export\"\n| where domain_id == \"{domain_name_param}\"\n| where event_details in ({type_param})\n| summarize arg_max(\"updated_at\", *) by tier_zero_principal, domain_id, event_details\n| project [\"Name\"]=tier_zero_principal, [\"Environment Name\"]=domain_name, [\"Type\"]=event_details, [\"Object ID\"]=finding_id", + "size": 3, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "gridSettings": { + "sortBy": [ + { + "itemKey": "Object ID", + "sortOrder": 1 + } + ] + }, + "sortBy": [ + { + "itemKey": "Object ID", + "sortOrder": 1 + } + ] }, "name": "query - 3" } diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png index 8b9b8b91c33..c60858cb6ad 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png index 89a050e663d..cf48ce3c9c9 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png index d1da2c02eff..e566d557957 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png index f87543dd0b1..a2638949707 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-Black.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-Black.png new file mode 100644 index 00000000000..f75b835576a Binary files /dev/null and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-Black.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png index d8ad5a2358c..ccfe6ed2b02 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogsDark-Black.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogsDark-Black.png deleted file mode 100644 index a4bf2c1e910..00000000000 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogsDark-Black.png and /dev/null differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png index 34e1562aeb2..49edf331f49 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png index fc042bb7544..34f058fd719 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png index 42ad17e49ec..74241a86533 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png differ diff --git a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png index e662f3ded38..e530d08cd69 100644 Binary files a/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png and b/Solutions/BloodHound Enterprise/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png index 8b9b8b91c33..c60858cb6ad 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-Black.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png index 89a050e663d..cf48ce3c9c9 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathDetails-White.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png index d1da2c02eff..e566d557957 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-Black.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png index f87543dd0b1..a2638949707 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseAttackPathOverview-White.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-Black.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-Black.png new file mode 100644 index 00000000000..f75b835576a Binary files /dev/null and b/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-Black.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png index d8ad5a2358c..ccfe6ed2b02 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogs-White.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogsDark-Black.png b/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogsDark-Black.png deleted file mode 100644 index a4bf2c1e910..00000000000 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseAuditLogsDark-Black.png and /dev/null differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png b/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png index 34e1562aeb2..49edf331f49 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png and b/Workbooks/Images/Preview/BloodHoundEnterprisePosture-Black.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png b/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png index fc042bb7544..34f058fd719 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png and b/Workbooks/Images/Preview/BloodHoundEnterprisePosture-White.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png b/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png index 42ad17e49ec..74241a86533 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-Black.png differ diff --git a/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png b/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png index e662f3ded38..e530d08cd69 100644 Binary files a/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png and b/Workbooks/Images/Preview/BloodHoundEnterpriseTierZeroSearch-White.png differ diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json index 6bfd9205142..c1958d60393 100644 --- a/Workbooks/WorkbooksMetadata.json +++ b/Workbooks/WorkbooksMetadata.json @@ -6922,7 +6922,7 @@ "BloodHoundEnterpriseAttackPathDetails-White.png" ], - "version": "1.0", + "version": "2.0", "title": "BloodHound Enterprise Attack Path Details", "templateRelativePath": "BloodHoundEnterpriseAttackPathDetails.json", "subtitle": "Detailed View", @@ -6942,7 +6942,7 @@ "BloodHoundEnterpriseAttackPathOverview-Black.png", "BloodHoundEnterpriseAttackPathOverview-White.png" ], - "version": "1.0", + "version": "2.0", "title": "BloodHound Enterprise Attack Path Overview", "templateRelativePath": "BloodHoundEnterpriseAttackPathOverview.json", "subtitle": "Overview", @@ -6960,9 +6960,9 @@ ], "previewImagesFileNames": [ "BloodHoundEnterpriseAuditLogs-White.png", - "BloodHoundEnterpriseAuditLogsDark-Black.png" + "BloodHoundEnterpriseAuditLogs-Black.png" ], - "version": "1.0", + "version": "2.0", "title": "BloodHound Enterprise Audit Logs", "templateRelativePath": "BloodHoundEnterpriseAuditLogs.json", "subtitle": "", @@ -6982,7 +6982,7 @@ "BloodHoundEnterprisePosture-Black.png", "BloodHoundEnterprisePosture-White.png" ], - "version": "1.0", + "version": "2.0", "title": "BloodHound Enterprise Domain Posture", "templateRelativePath": "BloodHoundEnterprisePosture.json", "subtitle": "",