diff --git a/actions/09_Bloodhound/bh_kerberoastable_users.yml b/actions/09_Bloodhound/bh_kerberoastable_users.yml new file mode 100644 index 0000000..c4125a7 --- /dev/null +++ b/actions/09_Bloodhound/bh_kerberoastable_users.yml @@ -0,0 +1,19 @@ +Name: Kerberoastable users +ID: BH_SPN_Users +Description: This action lists all users with an SPN +Author: FalconForce +Version: '1.0' +Info: | + This is intended to be used as an enrichment list to quantify the impact of an alert on a user that owns a resource. +Active: false # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: BloodHound +Query: | + MATCH (a:User {hasspn:true}) RETURN a +Targets: + - Name: CSV + Enabled: true + Path: output/kerberoastable_users.csv + - Name: Sentinel + Enabled: false + # TODO: Add filtering for output - WIP for now \ No newline at end of file diff --git a/actions/09_Bloodhound/bh_tier0_users.yml b/actions/09_Bloodhound/bh_tier0_users.yml new file mode 100644 index 0000000..89aed2d --- /dev/null +++ b/actions/09_Bloodhound/bh_tier0_users.yml @@ -0,0 +1,22 @@ +Name: Kerberoastable users +ID: BH_Tier0_Users +Description: This action lists all users with an SPN +Author: FalconForce +Version: '1.0' +Info: | + This is intended to be used as an enrichment list to quantify the impact of an alert on a user that owns a resource. +Active: false # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: BloodHound +Query: | + MATCH (x:Group) + WHERE (coalesce(x.system_tags,'') CONTAINS 'admin_tier_0') + WITH x.objectid as ObjectID, x.name as Name + MATCH (y:Group {objectid:ObjectID}) + MATCH (u:User) + MATCH (u)-[MemberOf]->(y) + RETURN u +Targets: + - Name: CSV + Enabled: true + Path: output/tier0_users.csv \ No newline at end of file diff --git a/input_processor/bloodhound.go b/input_processor/bloodhound.go index 6741f08..e508be7 100644 --- a/input_processor/bloodhound.go +++ b/input_processor/bloodhound.go @@ -5,6 +5,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" + "encoding/json" "falconhound/internal" "fmt" "io" @@ -40,7 +41,7 @@ func BHRequest(query string, creds internal.Credentials) (internal.QueryResults, method := "POST" uri := "/api/v2/graphs/cypher" - queryBody := fmt.Sprintf("{\"query\":\"%s\"}", query) + queryBody := fmt.Sprintf("{\"query\":\"%s\", \"include_properties\": true}", query) log.Println("Query body:", queryBody) body := []byte(queryBody) @@ -91,7 +92,25 @@ func BHRequest(query string, creds internal.Credentials) (internal.QueryResults, fmt.Println("Error reading response body:", err) } - fmt.Println("Response:", string(respbody)) - // TODO parse response body into QueryResults - return nil, nil + //fmt.Println("Response:", string(respbody)) + + var bhresults internal.BHQueryResults + err = json.Unmarshal(respbody, &bhresults) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("error unmarshalling response body: %w", err) + } + + // append each node in bhresults to the results + results := make(internal.QueryResults, 0) + for _, node := range bhresults.Data.Nodes { + result := make(map[string]interface{}) + result["label"] = node.Label + result["kind"] = node.Kind + result["objectId"] = node.ObjectID + result["isTierZero"] = node.IsTierZero + result["lastSeen"] = node.LastSeen + result["properties"] = node.Properties + results = append(results, result) + } + return results, nil } diff --git a/internal/bh_types.go b/internal/bh_types.go index 06d510a..bcdc588 100644 --- a/internal/bh_types.go +++ b/internal/bh_types.go @@ -182,3 +182,48 @@ type OU struct { ChildObjects []TypedPrincipal Links []GPLink } + +type Node struct { + Label string `json:"label"` + Kind string `json:"kind"` + ObjectID string `json:"objectId"` + IsTierZero bool `json:"isTierZero"` + LastSeen string `json:"lastSeen"` + Properties NodeProperties `json:"properties"` +} + +type NodeProperties struct { + AdminCount bool `json:"admincount"` + Description string `json:"description"` + DistinguishedName string `json:"distinguishedname"` + Domain string `json:"domain"` + DomainSID string `json:"domainsid"` + DontReqPreAuth bool `json:"dontreqpreauth"` + Enabled bool `json:"enabled"` + HasSPN bool `json:"hasspn"` + IsACLProtected bool `json:"isaclprotected"` + LastLogon int64 `json:"lastlogon"` + LastLogonTimestamp int64 `json:"lastlogontimestamp"` + LastSeen string `json:"lastseen"` + Name string `json:"name"` + ObjectID string `json:"objectid"` + PasswordNotRequired bool `json:"passwordnotreqd"` + PwdLastSet int64 `json:"pwdlastset"` + PwdNeverExpires bool `json:"pwdneverexpires"` + SAMAccountName string `json:"samaccountname"` + Sensitive bool `json:"sensitive"` + ServicePrincipalNames []string `json:"serviceprincipalnames"` + SIDHistory []string `json:"sidhistory"` + TrustedToAuth bool `json:"trustedtoauth"` + UnconstrainedDelegation bool `json:"unconstraineddelegation"` + WhenCreated int64 `json:"whencreated"` +} + +type Data struct { + Nodes map[string]Node `json:"nodes"` + Edges []interface{} `json:"edges"` // Assuming edges are not used in this example +} + +type BHQueryResults struct { + Data Data `json:"data"` +} diff --git a/internal/version.go b/internal/version.go index b1270ed..b3c73be 100644 --- a/internal/version.go +++ b/internal/version.go @@ -1,3 +1,3 @@ package internal -const Version = "FalconHound v1.4.0" +const Version = "FalconHound v1.4.1"