Skip to content

Commit

Permalink
BloodHound API support (#11)
Browse files Browse the repository at this point in the history
* adding limacharlie output

* refinement

* Added clean skip if secret or password is missing for the input processor

* Added ADX batchsize configurability and ElasticCloud querying

* updated template to demonstrate ADX batching

* typo fix to ADX schema

* added path creation and date-variable to csv

* added options for 2x date-variable to csv

* ignore report folder in git

* added markdown table output per action

* updated gitignore

* updated README to add supported files

* update to Markdown output

* module updates

* updates to docs

* huge speed boost by altering write process

* year update

* sample report update

* version bump

* added msgraphapi SDK support and initial actions

* improvement to array output in fields

* adding dynamic groups

* refined error reporting

* additional stats event

* disable by default due to req and speed

* change to array to accommodate maintenance

* adding auth device id

* add log based MFA updates and new edges

* default off

* version bumps etc

* cypher refinement to win performance

* improvement to path parsing for config

* more MFA based rules

* update to gitignore

* update to gitignore

* added mfa reports

* added mfa queries and add them to paths

* app consent and roles

* refined queries to make BHCE compatible

* new actions

* added max path lenght to improve performance

* added support for user and azuser

* added multi tenant support

* removed experimental reference

* small kql optimization

* small query fix

* many improvements and new features

* many improvements and new features

* clean up code

* address dependency vulnerability

* renamed title

* add user removed from group action

* added 2 actions

* added 3 actions, updated one
  • Loading branch information
olafhartong authored Jun 11, 2024
1 parent 547c0cc commit ba44895
Show file tree
Hide file tree
Showing 25 changed files with 728 additions and 26 deletions.
35 changes: 35 additions & 0 deletions actions/01-Sentinel/sen_get_aad_device_registration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Name: New device registered in EntraID
ID: SEN_AAD_Device_Registration
Description: Gets all device registrations and their owners
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: |
let timeframe = 15m;
AuditLogs
| where ingestion_time() >= ago(timeframe)
| where OperationName contains "Register device"
| mv-expand TargetResources
| extend AdditionalDetails=parse_json(AdditionalDetails), InitiatedBy=parse_json(InitiatedBy)
| mv-expand AdditionalDetails
| where AdditionalDetails contains "Device Id"
| extend deviceId=AdditionalDetails.value, ownerId=InitiatedBy.user.id
| project TimeGenerated,deviceId, ownerId, TenantId
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH toUpper($ownerId) AS OwnerID, toUpper($deviceId) AS DeviceID, toUpper($TenantId) AS TenantID, $TimeGenerated AS TimeGenerated
MERGE (d:AZDevice {objectid: DeviceID})
ON CREATE SET d.tenantid = TenantID, d.lastseen = TimeGenerated, d.label = "AZBase"
MERGE (u:AZUser {objectid: OwnerID})
MERGE (u)-[r:AZOwns]->(d)
ON CREATE SET r.added = TimeGenerated, r.source = 'falconhound'
Parameters:
ownerId: ownerId
deviceId: deviceId
TimeGenerated: TimeGenerated
TenantId: TenantId
31 changes: 31 additions & 0 deletions actions/01-Sentinel/sen_get_aad_group_create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Name: New groups added to EntraID
ID: SEN_AAD_Group_Creations
Description: Gets all group creation events
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: |
let timeframe = 15m;
AuditLogs
| where ingestion_time() >= ago(timeframe)
| where OperationName contains "Add group"
| mv-expand TargetResources
| extend TargetResources=parse_json(TargetResources), InitiatedBy=parse_json(InitiatedBy)
| extend ObjectId = TargetResources.id, displayName=TargetResources.displayName, createdBy=InitiatedBy.user.id,creatorUserPrincipalName=InitiatedBy.user.userPrincipalName
| project TimeGenerated,ObjectId,displayName,createdBy, creatorUserPrincipalName
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH toUpper($ObjectId) AS ObjectId, toUpper($displayName) AS DisplayName, toUpper($createdBy) AS CreatedBy, toUpper($creatorUserPrincipalName) AS CreatorUserPrincipalName, $TimeGenerated AS TimeGenerated
MERGE (g:AZGroup {objectid: ObjectId})
ON CREATE SET g.displayname = DisplayName, g.createdby = CreatedBy, g.creatoruserprincipalname = CreatorUserPrincipalName, g.source = 'falconhound', g.whencreated = TimeGenerated, g.label = 'AZBase'
Parameters:
displayName: displayName
ObjectId: ObjectId
TimeGenerated: TimeGenerated
createdBy: createdBy
creatorUserPrincipalName: creatorUserPrincipalName
33 changes: 33 additions & 0 deletions actions/01-Sentinel/sen_get_aad_user_addedtogroup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Name: AAD user added to group
ID: SEN_AAD_User_Added_To_Group
Description: Collects AAD / EntraId user accounts added to a group.
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: |
let timeframe = 15m;
AuditLogs
| where ingestion_time() >= ago(timeframe)
| where OperationName =~ "Add member to group"
| mv-expand TargetResources
| extend TargetResources=parse_json(TargetResources)
| extend ObjectId = TargetResources.id, userPrincipalName=TargetResources.userPrincipalName
| where TargetResources.modifiedProperties contains "Group.ObjectId"
| extend groupObjectId=trim('\"',tostring(TargetResources.modifiedProperties[0].newValue))
| project TimeGenerated,ObjectId,userPrincipalName,groupObjectId
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH toUpper($ObjectId) AS ObjectId, toUpper($userPrincipalName) AS UserPrincipalName, toUpper($groupObjectId) AS GroupObjectId, $TimeGenerated AS TimeGenerated
MERGE (u:AZUser {objectid: ObjectId}) ON CREATE SET u.userPrincipalName = UserPrincipalName, u.displayName = UserPrincipalName
MERGE (g:AZGroup {objectid: GroupObjectId}) ON CREATE SET g.displayName = GroupObjectId
MERGE (u)-[r:AZMemberOf]->(g) SET r.added = TimeGenerated, r.source = 'falconhound'
Parameters:
ObjectId: ObjectId
userPrincipalName: userPrincipalName
GroupObjectId: groupObjectId
TimeGenerated: TimeGenerated
28 changes: 28 additions & 0 deletions actions/01-Sentinel/sen_get_aad_user_delete.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Name: New AAD user deletions
ID: SEN_AAD_New_User_Deletions
Description: Collects deleted AAD / EntraId user accounts created in the last 15 minutes and removes them from the graph.
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: |
let timeframe = 15m;
AuditLogs
| where ingestion_time() >= ago(timeframe)
| where OperationName =~ "Delete user"
| extend TargetResources=parse_json(TargetResources)
| extend
ObjectId = TargetResources.[0].id,
userPrincipalName=TargetResources.[0].userPrincipalName
| project ObjectId, userPrincipalName
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH toUpper($ObjectId) AS ObjectId
MATCH (x:AZUser {objectid:ObjectId})
DELETE x
Parameters:
ObjectId: ObjectId
31 changes: 31 additions & 0 deletions actions/01-Sentinel/sen_get_aad_user_removedfromgroup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Name: AAD user removed from group
ID: SEN_AAD_User_Removed_from_Group
Description: Collects AAD / EntraId user accounts removed from a group.
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: |
let timeframe = 15m;
AuditLogs
| where ingestion_time() >= ago(timeframe)
| where OperationName contains "Remove member from group"
| mv-expand TargetResources
| extend TargetResources=parse_json(TargetResources)
| extend ObjectId = TargetResources.id, userPrincipalName=TargetResources.userPrincipalName
| where TargetResources.modifiedProperties contains "Group.ObjectID"
| extend groupObjectId=trim('\"',tostring(TargetResources.modifiedProperties[0].oldValue))
| project TimeGenerated,ObjectId,userPrincipalName,groupObjectId
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH toUpper($ObjectId) AS ObjectId, toUpper($userPrincipalName) AS UserPrincipalName, toUpper($groupObjectId) AS GroupObjectId
MATCH (u:AZUser {objectid: ObjectId})-[r:AZMemberOf]->(g:AZGroup {objectid: GroupObjectId})
DELETE r
Parameters:
ObjectId: ObjectId
userPrincipalName: userPrincipalName
GroupObjectId: groupObjectId
31 changes: 31 additions & 0 deletions actions/01-Sentinel/sen_get_ad_group_removal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Name: All remove events from AD groups
ID: SEN_AD_Group_Removal
Description: Gets all group removals from the Security logs, including local groups
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: |
let timeframe = 15m;
let targetEvent = dynamic([4729,4733,4757]); // 4733 Domain Local, 4729 >> Global, 4757 >> Universal
SecurityEvent
| where ingestion_time() >= ago(timeframe)
| where EventID in (targetEvent)
| where MemberName != '-'
| project TargetAccount, TargetDomainName, GroupSid=TargetSid, MemberSid, Actor=SubjectUserName, TimeGenerated
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH $MemberSid AS MemberSid, $GroupSid AS GroupSid, $TimeGenerated AS Timestamp
MATCH (u:User) WHERE u.objectid = MemberSid
WITH u, GroupSid
MATCH (g:Group) WHERE g.objectid = GroupSid
MATCH (u)-[r:MemberOf]->(g)
DELETE r
Parameters:
MemberSid: MemberSid
GroupSid: GroupSid
TimeGenerated: TimeGenerated
8 changes: 4 additions & 4 deletions actions/01-Sentinel/sen_get_host_alerts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Query: |
| extend FQDN2=strcat(HostName,".",DnsDomain) // Sometimes the FQDN is not populated for some reason, so we can fix most this way.
| extend EntityName=iff(isnotempty(FQDN),FQDN,FQDN2)
| project EntityName,AlertId=VendorOriginalId,Entitytype,ProviderName, Status
| summarize make_set(AlertId) by Entitytype,EntityName,Status, ProviderName
// | where not((Entitytype == 'host' and EntityName !contains '.') or EntityName endswith ".") // Optional. Filters non-domain joined hosts and incomplete hostnames.
| mv-expand set_AlertId
| summarize make_set(AlertId) by Entitytype,DeviceName=EntityName,Status, ProviderName
//| where not((Entitytype == 'host' and EntityName !contains '.') or EntityName endswith ".") // Optional. Filters non-domain joined hosts and incomplete hostnames.
| mv-expand set_AlertId
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Expand All @@ -32,5 +32,5 @@ Targets: # Targets are the pla
c.owned = True
Parameters:
set_AlertId: set_AlertId
EntityName: EntityName
EntityName: DeviceName
Status: Status
8 changes: 8 additions & 0 deletions actions/01-Sentinel/sen_new_sessions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ Targets: # Targets are the pla
Query: |
WITH toUpper($Computer) as Computer, toUpper($TargetUserSid) as TargetUserSid, $Timestamp as Timestamp
MATCH (x:Computer {name:Computer}) MATCH (y:User {objectid:TargetUserSid}) MERGE (x)-[r:HasSession]->(y) SET r.since=Timestamp SET r.source='falconhound'
Parameters:
Computer: Computer
TargetUserSid: TargetUserSid
Timestamp: Timestamp
- Name: BloodHound
Enabled: false
Query: |
MATCH (x:Computer {name:$Computer}) MATCH (y:User {objectid:$TargetUserSid}) MERGE (x)-[r:HasSession]->(y) SET r.since=$Timestamp SET r.source='falconhound'
Parameters:
Computer: Computer
TargetUserSid: TargetUserSid
Expand Down
4 changes: 2 additions & 2 deletions actions/04-MSgraph/msgraph_dynamicgroups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: true # Enable to see query results in the console
Debug: false # Enable to see query results in the console
SourcePlatform: MSGraphApi # Sentinel, Watchlist, Neo4j, MDE, Graph, Splunk
Query: |
GetDynamicGroups
Expand All @@ -21,7 +21,7 @@ Targets: # Targets are the platforms that this action will push to (CSV, Neo4j
objectid: objectid,
displayname: displayname,
falconhound:True,
memebershiprule: membershiprule,
membershiprule: membershiprule,
membershiprulestate: membershiprulestate,
grouptype: grouptype
}
Expand Down
24 changes: 24 additions & 0 deletions actions/08-HTTP/http_entra_roles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Name: Get Entra admin tier roles
ID: HTTP_Entra_Roles
Description: Get all roles from Entra and create a relationship between the role and the user in Neo4j.
Author: FalconForce
Version: '1.0'
Info: |
Based on a tweet by martinsohndk https://twitter.com/martinsohndk/status/1768065960148136277
and references the project by Thomas Naunheim https://github.com/Cloud-Architekt/AzurePrivilegedIAM
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: HTTP # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk
Query: | # Splunk index can be hardcoded or a variable set in the config.yml file
EntraRoles
Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown)
- Name: Neo4j
Enabled: true
Query: |
WITH $AdminTierLevel AS AdminTierLevel, toUpper($RoleObjectId + '@' + $TenantId) AS TargetObjectId
MATCH (x:AZRole {objectid: TargetObjectId})
SET x.system_tags = AdminTierLevel
Parameters:
AdminTierLevel: AdminTierLevel
RoleObjectId: RoleId
TenantId: TenantId
36 changes: 36 additions & 0 deletions actions/10-Neo4j/n4j_ad_chokepoints.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Name: N4J AD Chokepoints
ID: N4J_AD_Chokepoints
Description: This action collects all paths to groups that can control many resources
Author: FalconForce
Version: '1.0'
Info: |
Based on a blog by sadprocess0r (https://falconforce.nl/bloodhound-calculating-ad-metrics-0x02/)
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Neo4j
Query: |
CALL {MATCH (allU:User) RETURN COUNT(allU) AS TotalU}
CALL {MATCH (allC:Computer) RETURN COUNT(allC) AS TotalC}
MATCH (y:Group)
CALL {WITH y
OPTIONAL MATCH pIN=shortestPath((x:User)-[*1..]->(y)) RETURN x
}
CALL {WITH y
OPTIONAL MATCH pOUT=shortestPath((y)-[*1..]->(z:Computer)) RETURN z
}
WITH DISTINCT y.name AS Target, TotalU, TotalC,
COUNT(DISTINCT(x)) AS CountIN,
COUNT(DISTINCT(z)) AS CountOUT
WHERE CountOUT > 0 AND CountIN > 0
RETURN {Target:Target,
CountIn:CountIN, TotalUsers:TotalU, PercentIn:round(CountIN/toFloat(TotalU)*100,1),
CountOut:CountOUT,TotalCount:TotalC, PercentOut:round(CountOUT/toFloat(TotalC)*100,1)} as info
Targets:
- Name: Sentinel
Enabled: true
- Name: Watchlist
Enabled: true
WatchlistName: FH_AD_Chokeopoints
DisplayName: AD Chokepoints
SearchKey: Target
Overwrite: true
32 changes: 32 additions & 0 deletions actions/10-Neo4j/n4j_azure_tier0_or_tier1_assigned_roles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Name: N4J Azure Tier0 or Tier1 Assigned Roles
ID: N4j_Azure_Tier0_or_Tier1_Assigned_Roles
Description: Collects all Azure roles assigned to Tier0 or Tier1 groups and creates a relationship between the role and the entity in Neo4j.
Author: FalconForce
Version: '1.0'
Info: |-
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Neo4j
Query: |
MATCH (a:AZRole)-[r:HasConsent|AZRunsAs|AZHasRole|AZGlobalAdmin|AZPrivilegedRoleAdmin|AZOwns]-(b)
WHERE (a.system_tags CONTAINS "admin_tier_0" or a.system_tags CONTAINS "admin_tier_1")
RETURN {Entity: b.name, Role: a.name, Relation: type(r)} as info
Targets:
- Name: CSV
Enabled: false
Path: output/azure_tier0_and_tier1_assigned.csv
- Name: Sentinel
BHQuery: |
MATCH b=(a:AZRole)-[r:HasConsent|AZRunsAs|AZHasRole|AZGlobalAdmin|AZPrivilegedRoleAdmin|AZOwns]-()
WHERE (a.system_tags CONTAINS "admin_tier_0" or a.system_tags CONTAINS "admin_tier_1")
RETURN b
Enabled: true
- Name: Watchlist
Enabled: true
WatchlistName: FH_Azure_Tier0_or_Tier1_Assigned_Roles
DisplayName: Azure Tier0 or Tier1 Assigned Roles
SearchKey: Entity
Overwrite: true
- Name: Markdown
Enabled: true
Path: report/{{date}}/Azure_Tier0_or_Tier1_Assigned_Roles.md
5 changes: 4 additions & 1 deletion actions/10-Neo4j/n4j_exploitable_device_to_high_value.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ Targets:
Overwrite: true
- Name: ADX
Enabled: false
Table: FalconHound
Table: FalconHound
- Name: Markdown
Enabled: true
Path: report/{{date}}/exploitable_to_highvaluecount.md
10 changes: 8 additions & 2 deletions actions/10-Neo4j/n4j_external_serviceprincipal_high_priv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Description: This action lists all externally owned Service Principals with high
Author: FalconForce
Version: '1.0'
Info: More information about the potential impact here > https://posts.specterops.io/microsoft-breach-how-can-i-see-this-in-bloodhound-33c92dca4c65
Active: false # Enable to run this action
Active: true # Enable to run this action
Debug: false # Enable to see query results in the console
SourcePlatform: Neo4j
Query: |
Expand All @@ -25,4 +25,10 @@ Targets:
WHERE (coalesce(s.system_tags,"") CONTAINS "admin_tier_0" or s.highvalue=true)
AND NOT toUpper(s.appownerorganizationid) = TENANTID
AND s.appownerorganizationid CONTAINS "-"
RETURN *
RETURN p
- Name: Watchlist
Enabled: true
WatchlistName: FH_Azure_EXT_SP_HIGH_PRIV
DisplayName: Azure External SP High Privileges
SearchKey: SPName
Overwrite: true
Loading

0 comments on commit ba44895

Please sign in to comment.