diff --git a/playbooks/alert_management/crowdstrike-to-cases.yml b/playbooks/alert_management/crowdstrike-to-cases.yml index 449381b97..e73cb0136 100644 --- a/playbooks/alert_management/crowdstrike-to-cases.yml +++ b/playbooks/alert_management/crowdstrike-to-cases.yml @@ -1,12 +1,19 @@ title: Fetch Crowdstrike alerts and open cases. description: Pulls Crowdstrike alerts and opens cases in Tracecat. +config: + enable_runtime_tests: true entrypoint: pull_crowdstrike_alerts triggers: - type: webhook ref: crowdstrike_alerts_webhook entrypoint: pull_crowdstrike_alerts + # shape: + # - start_time: ISO8601 string + # - end_time: ISO8601 string + # - update_crowdstrike_alert: Webhook URL actions: + # Mocked - ref: pull_crowdstrike_alerts action: integrations.crowdstrike.list_crowdstrike_alerts args: @@ -25,7 +32,7 @@ actions: payload: rule: ${{ var.alert.id }} # Identifier associated with the alert severity: ${{ var.alert.severity }} # Severity level associated with the detection - status: ${{ 'closed' if var.alert.status == 'resolved' else 'open' }} # Status of the alert + status: ${{ 'closed' if FN.equal(var.alert.status, 'resolved') else 'open' }} # Status of the alert malice: ${{ 'malicious' if FN.greater_than(var.alert.severity, 0) else 'benign' }} # Determines malice based on severity action: quarantine context: @@ -39,6 +46,7 @@ actions: context_timestamp: ${{ var.alert.context_timestamp -> str }} updated_at: ${{ var.alert.updated_timestamp -> str }} # Timestamp indicating when the alert was last updated created_at: ${{ var.alert.created_timestamp -> str }} # Timestamp indicating when the alert was created + user_name: ${{ var.alert.user_name }} - ref: send_slack_notification action: integrations.chat.slack.post_slack_message @@ -74,26 +82,20 @@ actions: text: "Select an option to update the alert:" accessory: type: static_select - action_id: update_crowdstrike_alert + action_id: ${{ TRIGGER.update_crowdstrike_alert }}?action_id=update_crowdstrike_alerts&alert_id=${{ var.smac.context.cid }}&old_status=${{ var.smac.status }}&username=${{ var.smac.context.user_name }} options: - text: type: plain_text text: Ignore - value: - old_status: ${{ var.smac.status }} - new_status: ignored" + value: new_status=ignored - text: type: plain_text text: True Positive - value: - old_status: ${{ var.smac.status }} - new_status: true_positive + value: new_status=true_positive - text: type: plain_text text: False Positive - value: - old_status: ${{ var.smac.status }} - new_status: false_positive + value: new_status=false_positive - ref: open_cases action: core.open_case @@ -107,4 +109,9 @@ actions: action: ${{ var.smac.action }} context: ${{ var.smac.context }} payload: ${{ var.smac.payload }} - priority: ${{ var.smac.severity }} + priority: ${{ 'high' if FN.greater_than(var.smac.payload.severity, 50) else 'low' }} + +tests: + - ref: pull_crowdstrike_alerts + success: + - https://raw.githubusercontent.com/TracecatHQ/tracecat/main/tests/data/log_samples/crowdstrike/alert.json diff --git a/playbooks/alert_management/slack-to-crowdstrike-update.yml b/playbooks/alert_management/slack-to-crowdstrike-update.yml index 14e3d3ed5..9a422ac4d 100644 --- a/playbooks/alert_management/slack-to-crowdstrike-update.yml +++ b/playbooks/alert_management/slack-to-crowdstrike-update.yml @@ -2,23 +2,29 @@ title: Update Crowdstrike alerts via Slack description: | Receives a Slack action and updates Crowdstrike alerts based on `alert_ids` and `status` provided in the Slack action payload. +config: + enable_runtime_tests: true entrypoint: extract_slack_payload triggers: - type: webhook ref: slack_actions_webhook entrypoint: receive_slack_action + # shape: + # - action_id: string + # - alert_id: string + # - old_status: string + # - new_status: string + # - username: string actions: - ref: extract_slack_payload action: core.transform.forward - # Check if action received is as expected - run_if: ${{ FN.equal(TRIGGER.action.action_id, 'update_crowdstrike_alert') }} args: value: - username: ${{ TRIGGER.user.username }} - alert_id: ${{ TRIGGER.action.value.alert_id }} - old_status: ${{ TRIGGER.action.value.old_status }} - new_status: ${{ TRIGGER.action.value.new_status }} + username: ${{ TRIGGER.username }} + alert_id: ${{ TRIGGER.alert_id }} + old_status: ${{ TRIGGER.old_status }} + new_status: ${{ TRIGGER.new_status }} - ref: update_crowdstrike_alerts action: integrations.crowdstrike.update_crowdstrike_alert_status @@ -51,3 +57,9 @@ actions: text: "*Old status:* ${{ ACTIONS.extract_slack_payload.result.old_status }}" - type: mrkdwn text: "*New status:* ${{ ACTIONS.extract_slack_payload.result.new_status }}" + +tests: + - ref: update_crowdstrike_alerts + success: + status: ok + code: 200 diff --git a/tests/data/log_samples/crowdstrike/alert.json b/tests/data/log_samples/crowdstrike/alert.json index a097b7005..fa2077fd2 100644 --- a/tests/data/log_samples/crowdstrike/alert.json +++ b/tests/data/log_samples/crowdstrike/alert.json @@ -1,209 +1,211 @@ -{ - "agent_id": "2ce412d17b334ad4adc8c1c54dbfec4b", - "aggregate_id": "aggind:2ce412d17b334ad4adc8c1c54dbfec4b:163208931778", - "alleged_filetype": "exe", - "cid": "92012896127c4a948236ba7601b886b0", - "cloud_indicator": false, - "cmdline": "\"C:\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe\"", - "composite_id": "92012896127c4a8236ba7601b886b0:ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", - "confidence": 10, - "context_timestamp": "2023-11-03T18:00:31.000Z", - "control_graph_id": "ctg:2ce4127b334ad4adc8c1c54dbfec4b:163208931778", - "crawl_edge_ids": { - "Sensor": [ - "KZcZ=__;K&cmqQ]Z=W,QK4W.9(rBfs\\gfmjTblqI^F-_oNnAWQ&-o0:dR/>>2JIVMD36[+=kiQDRm.bB?;d\"V0JaQlaltC59Iq6nM?6`>ZAs+LbOJ9p9A;9'WV9^H3XEMs8N", - "KZcZA__;?\"cmott@m_k)MSZ^+C?.cg92t[f!>*b9WLY@H!V0N,BJsNSTD:?/+fY';ea%iM\"__\"59K'R?_=`'`rK/'hA\"r+L5i-*Ut5PI!!*'!", - "N6CUF__;K!d$:[C93.?=/5(`5KnM]!L#UbnSY5HOHc#[6A&FE;(naXB4h/OG\"%MDAR=fo41Z]rXc\"J-\\&&V8UW.?I6V*G+,))Ztu_IuCMV#ZJ:QDJ_EjQmjiX#HENY'WD0rVAV$Gl6_+0e:2$8D)):.LUs+8-S$L!!!$!rr", - "N6CUF__;K!d$:\\N43JV0AO56@6D0$!na(s)d.dQ'iI1*uiKt#j?r\"X'\\AtNML2_C__7ic6,8Dc[F<0NTUGtl%HD#?/Y)t8!1X.;G!*FQ9GP-ukQn`6I##&$^81(P+hN*-#rf/cUs)Wb\"<_/?I'[##WMh'H[Rcl+!!<<'", - "N6L[G__;K!d\"qhT7k?[D\"Bk:5s%+=>#DM0j$_44ZjO9q*d!YLuHhkq!3>3tpi>OPYZp9]5f1#/AlRZL06`/I6cl\"d.&=To@9kS!prs8N" - ] - }, - "crawl_vertex_ids": { - "Sensor": [ - "aggind:2ce412d17b334ad4adc8c1c54dbfec4b:163208931778", - "ctg:2ce412d17b334ad4adc8c1c54dbfec4b:163208931778", - "ind:2ce412d17b34ad4adc8c1c54dbfec4b:399748687993-5761-42627600", - "mod:2ce412d17b4ad4adc8c1c54dbfec4b:0b25d56bd2b4d8a6df45beff7be165117fbf7ba6ba2c07744f039143866335e4", - "mod:2ce412d17b4ad4adc8c1c54dbfec4b:b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "mod:2ce412d17b334ad4adc8c1c54dbfec4b:caef4ae19056eeb122a0540508fa8984cea960173ada0dc648cb846d6ef5dd33", - "pid:2ce412d17b33d4adc8c1c54dbfec4b:392734873135", - "pid:2ce412d17b334ad4adc8c1c54dbfec4b:392736520876", - "pid:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993", - "quf:2ce412d17b334ad4adc8c1c54dbfec4b:b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "uid:2ce412d17b334ad4adc8c1c54dbfec4b:S-1-5-21-1909377054-3469629671-4104191496-4425" - ] - }, - "crawled_timestamp": "2023-11-03T19:00:23.985Z", - "created_timestamp": "2023-11-03T18:01:23.995Z", - "data_domains": [ - "Endpoint" - ], - "description": "ThisfilemeetstheAdware/PUPAnti-malwareMLalgorithm'slowest-confidencethreshold.", - "device": { - "agent_load_flags": 0, - "agent_local_time": "2023-10-12T03:45:57.753Z", - "agent_version": "7.04.17605.0", - "bios_manufacturer": "ABC", - "bios_version": "F8CN42WW(V2.05)", +[ + { + "agent_id": "2ce412d17b334ad4adc8c1c54dbfec4b", + "aggregate_id": "aggind:2ce412d17b334ad4adc8c1c54dbfec4b:163208931778", + "alleged_filetype": "exe", "cid": "92012896127c4a948236ba7601b886b0", - "config_id_base": "65994763", - "config_id_build": "17605", - "config_id_platform": 3, - "external_ip": "81.2.69.142", - "first_seen": "2023-04-07T09:36:36.000Z", - "groups": [ - "18704e21288243b58e4c76266d38caaf" + "cloud_indicator": false, + "cmdline": "\"C:\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe\"", + "composite_id": "92012896127c4a8236ba7601b886b0:ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", + "confidence": 10, + "context_timestamp": "2023-11-03T18:00:31.000Z", + "control_graph_id": "ctg:2ce4127b334ad4adc8c1c54dbfec4b:163208931778", + "crawl_edge_ids": { + "Sensor": [ + "KZcZ=__;K&cmqQ]Z=W,QK4W.9(rBfs\\gfmjTblqI^F-_oNnAWQ&-o0:dR/>>2JIVMD36[+=kiQDRm.bB?;d\"V0JaQlaltC59Iq6nM?6`>ZAs+LbOJ9p9A;9'WV9^H3XEMs8N", + "KZcZA__;?\"cmott@m_k)MSZ^+C?.cg92t[f!>*b9WLY@H!V0N,BJsNSTD:?/+fY';ea%iM\"__\"59K'R?_=`'`rK/'hA\"r+L5i-*Ut5PI!!*'!", + "N6CUF__;K!d$:[C93.?=/5(`5KnM]!L#UbnSY5HOHc#[6A&FE;(naXB4h/OG\"%MDAR=fo41Z]rXc\"J-\\&&V8UW.?I6V*G+,))Ztu_IuCMV#ZJ:QDJ_EjQmjiX#HENY'WD0rVAV$Gl6_+0e:2$8D)):.LUs+8-S$L!!!$!rr", + "N6CUF__;K!d$:\\N43JV0AO56@6D0$!na(s)d.dQ'iI1*uiKt#j?r\"X'\\AtNML2_C__7ic6,8Dc[F<0NTUGtl%HD#?/Y)t8!1X.;G!*FQ9GP-ukQn`6I##&$^81(P+hN*-#rf/cUs)Wb\"<_/?I'[##WMh'H[Rcl+!!<<'", + "N6L[G__;K!d\"qhT7k?[D\"Bk:5s%+=>#DM0j$_44ZjO9q*d!YLuHhkq!3>3tpi>OPYZp9]5f1#/AlRZL06`/I6cl\"d.&=To@9kS!prs8N" + ] + }, + "crawl_vertex_ids": { + "Sensor": [ + "aggind:2ce412d17b334ad4adc8c1c54dbfec4b:163208931778", + "ctg:2ce412d17b334ad4adc8c1c54dbfec4b:163208931778", + "ind:2ce412d17b34ad4adc8c1c54dbfec4b:399748687993-5761-42627600", + "mod:2ce412d17b4ad4adc8c1c54dbfec4b:0b25d56bd2b4d8a6df45beff7be165117fbf7ba6ba2c07744f039143866335e4", + "mod:2ce412d17b4ad4adc8c1c54dbfec4b:b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "mod:2ce412d17b334ad4adc8c1c54dbfec4b:caef4ae19056eeb122a0540508fa8984cea960173ada0dc648cb846d6ef5dd33", + "pid:2ce412d17b33d4adc8c1c54dbfec4b:392734873135", + "pid:2ce412d17b334ad4adc8c1c54dbfec4b:392736520876", + "pid:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993", + "quf:2ce412d17b334ad4adc8c1c54dbfec4b:b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "uid:2ce412d17b334ad4adc8c1c54dbfec4b:S-1-5-21-1909377054-3469629671-4104191496-4425" + ] + }, + "crawled_timestamp": "2023-11-03T19:00:23.985Z", + "created_timestamp": "2023-11-03T18:01:23.995Z", + "data_domains": [ + "Endpoint" ], - "hostinfo": { - "active_directory_dn_display": [ - "WinComputers", - "WinComputers\\ABC" + "description": "This file meets the Adware/PUP Anti-malware ML algorithm's lowest-confidence threshold.", + "device": { + "agent_load_flags": 0, + "agent_local_time": "2023-10-12T03:45:57.753Z", + "agent_version": "7.04.17605.0", + "bios_manufacturer": "ABC", + "bios_version": "F8CN42WW(V2.05)", + "cid": "92012896127c4a948236ba7601b886b0", + "config_id_base": "65994763", + "config_id_build": "17605", + "config_id_platform": 3, + "external_ip": "81.2.69.142", + "first_seen": "2023-04-07T09:36:36.000Z", + "groups": [ + "18704e21288243b58e4c76266d38caaf" + ], + "hostinfo": { + "active_directory_dn_display": [ + "WinComputers", + "WinComputers\\ABC" + ], + "domain": "ABC.LOCAL" + }, + "hostname": "ABC709-1175", + "id": "2ce412d17b334ad4adc8c1c54dbfec4b", + "last_seen": "2023-11-03T17:51:42.000Z", + "local_ip": "81.2.69.142", + "mac_address": "AB-21-48-61-05-B2", + "machine_domain": "ABC.LOCAL", + "major_version": "10", + "minor_version": "0", + "modified_timestamp": "2023-11-03T17:53:43.000Z", + "os_version": "Windows11", + "ou": [ + "ABC", + "WinComputers" ], - "domain": "ABC.LOCAL" + "platform_id": "0", + "platform_name": "Windows", + "product_type": "1", + "product_type_desc": "Workstation", + "site_name": "Default-First-Site-Name", + "status": "normal", + "system_manufacturer": "LENOVO", + "system_product_name": "20VE" }, - "hostname": "ABC709-1175", - "id": "2ce412d17b334ad4adc8c1c54dbfec4b", - "last_seen": "2023-11-03T17:51:42.000Z", - "local_ip": "81.2.69.142", - "mac_address": "AB-21-48-61-05-B2", - "machine_domain": "ABC.LOCAL", - "major_version": "10", - "minor_version": "0", - "modified_timestamp": "2023-11-03T17:53:43.000Z", - "os_version": "Windows11", - "ou": [ - "ABC", - "WinComputers" + "falcon_host_link": "https://falcon.us-2.crowdstrike.com/activity-v2/detections/dhjffg:ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", + "filename": "openvpn-abc-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", + "filepath": "\\Device\\HarddiskVolume3\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", + "grandparent_details": { + "cmdline": "C:\\Windows\\system32\\userinit.exe", + "filename": "userinit.exe", + "filepath": "\\Device\\HarddiskVolume3\\Windows\\System32\\userinit.exe", + "local_process_id": "4328", + "md5": "b07f77fd3f9828b2c9d61f8a36609741", + "process_graph_id": "pid:2ce412d17b334ad4adc8c1c54dbfec4b:392734873135", + "process_id": "392734873135", + "sha256": "caef4ae19056eeb122a0540508fa8984cea960173ada0dc648cb846d6ef5dd33", + "timestamp": "2023-10-30T16:49:19.000Z", + "user_graph_id": "uid:2ce412d17b334ad4adc8c1c54dbfec4b:S-1-5-21-1909377054-3469629671-4104191496-4425", + "user_id": "S-1-5-21-1909377054-3469629671-4104191496-4425", + "user_name": "yuvraj.mahajan" + }, + "has_script_or_module_ioc": true, + "id": "ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", + "indicator_id": "ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", + "ioc_context": [ + { + "ioc_description": "\\Device\\HarddiskVolume3\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", + "ioc_source": "library_load", + "ioc_type": "hash_sha256", + "ioc_value": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "md5": "cdf9cfebb400ce89d5b6032bfcdc693b", + "sha256": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "type": "module" + } ], - "platform_id": "0", - "platform_name": "Windows", - "product_type": "1", - "product_type_desc": "Workstation", - "site_name": "Default-First-Site-Name", - "status": "normal", - "system_manufacturer": "LENOVO", - "system_product_name": "20VE" - }, - "falcon_host_link": "https://falcon.us-2.crowdstrike.com/activity-v2/detections/dhjffg:ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", - "filename": "openvpn-abc-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", - "filepath": "\\Device\\HarddiskVolume3\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", - "grandparent_details": { - "cmdline": "C:\\Windows\\system32\\userinit.exe", - "filename": "userinit.exe", - "filepath": "\\Device\\HarddiskVolume3\\Windows\\System32\\userinit.exe", - "local_process_id": "4328", - "md5": "b07f77fd3f9828b2c9d61f8a36609741", - "process_graph_id": "pid:2ce412d17b334ad4adc8c1c54dbfec4b:392734873135", - "process_id": "392734873135", - "sha256": "caef4ae19056eeb122a0540508fa8984cea960173ada0dc648cb846d6ef5dd33", - "timestamp": "2023-10-30T16:49:19.000Z", - "user_graph_id": "uid:2ce412d17b334ad4adc8c1c54dbfec4b:S-1-5-21-1909377054-3469629671-4104191496-4425", - "user_id": "S-1-5-21-1909377054-3469629671-4104191496-4425", - "user_name": "yuvraj.mahajan" - }, - "has_script_or_module_ioc": true, - "id": "ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", - "indicator_id": "ind:2ce412d17b334ad4adc8c1c54dbfec4b:399748687993-5761-42627600", - "ioc_context": [ - { - "ioc_description": "\\Device\\HarddiskVolume3\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", - "ioc_source": "library_load", - "ioc_type": "hash_sha256", - "ioc_value": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "md5": "cdf9cfebb400ce89d5b6032bfcdc693b", - "sha256": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "type": "module" - } - ], - "is_synthetic_quarantine_disposition": true, - "local_process_id": "17076", - "logon_domain": "ABSYS", - "md5": "cdf9cfebb400ce89d5b6032bfcdc693b", - "name": "PrewittPupAdwareSensorDetect-Lowest", - "objective": "FalconDetectionMethod", - "parent_details": { - "cmdline": "C:\\WINDOWS\\Explorer.EXE", - "filename": "explorer.exe", - "filepath": "\\Device\\HarddiskVolume3\\Windows\\explorer.exe", - "local_process_id": "1040", - "md5": "8cc3fcdd7d52d2d5221303c213e044ae", - "process_graph_id": "pid:2ce412d17b334ad4adc8c1c54dbfec4b:392736520876", - "process_id": "392736520876", - "sha256": "0b25d56bd2b4d8a6df45beff7be165117fbf7ba6ba2c07744f039143866335e4", - "timestamp": "2023-11-03T18:00:32.000Z", - "user_graph_id": "uid:2ce412d17b334ad4adc8c1c54dbfec4b:S-1-5-21-1909377054-3469629671-4104191496-4425", + "is_synthetic_quarantine_disposition": true, + "local_process_id": "17076", + "logon_domain": "ABSYS", + "md5": "cdf9cfebb400ce89d5b6032bfcdc693b", + "name": "PrewittPupAdwareSensorDetect-Lowest", + "objective": "FalconDetectionMethod", + "parent_details": { + "cmdline": "C:\\WINDOWS\\Explorer.EXE", + "filename": "explorer.exe", + "filepath": "\\Device\\HarddiskVolume3\\Windows\\explorer.exe", + "local_process_id": "1040", + "md5": "8cc3fcdd7d52d2d5221303c213e044ae", + "process_graph_id": "pid:2ce412d17b334ad4adc8c1c54dbfec4b:392736520876", + "process_id": "392736520876", + "sha256": "0b25d56bd2b4d8a6df45beff7be165117fbf7ba6ba2c07744f039143866335e4", + "timestamp": "2023-11-03T18:00:32.000Z", + "user_graph_id": "uid:2ce412d17b334ad4adc8c1c54dbfec4b:S-1-5-21-1909377054-3469629671-4104191496-4425", + "user_id": "S-1-5-21-1909377054-3469629671-4104191496-4425", + "user_name": "mohit.jha" + }, + "parent_process_id": "392736520876", + "pattern_disposition": 2176, + "pattern_disposition_description": "Prevention/Quarantine,processwasblockedfromexecutionandquarantinewasattempted.", + "pattern_disposition_details": { + "blocking_unsupported_or_disabled": false, + "bootup_safeguard_enabled": false, + "critical_process_disabled": false, + "detect": false, + "fs_operation_blocked": false, + "handle_operation_downgraded": false, + "inddet_mask": false, + "indicator": false, + "kill_action_failed": false, + "kill_parent": false, + "kill_process": false, + "kill_subprocess": false, + "operation_blocked": false, + "policy_disabled": false, + "process_blocked": true, + "quarantine_file": true, + "quarantine_machine": false, + "registry_operation_blocked": false, + "rooting": false, + "sensor_only": false, + "suspend_parent": false, + "suspend_process": false + }, + "pattern_id": "5761", + "platform": "Windows", + "poly_id": "AACSASiWEnxKlIIaw8LWC-8XINBatE2uYZaWqRAAATiEEfPFwhoY4opnh1CQjm0tvUQp4Lu5eOAx29ZVj-qrGrA==", + "process_end_time": "2023-11-03T18:00:21.000Z", + "process_id": "399748687993", + "process_start_time": "2023-11-03T18:00:13.000Z", + "product": "epp", + "quarantined_files": [ + { + "filename": "\\Device\\Volume3\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", + "id": "2ce412d17b334ad4adc8c1c54dbfec4b_b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "sha256": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "state": "quarantined" + } + ], + "scenario": "NGAV", + "severity": 30, + "sha1": "0000000000000000000000000000000000000000", + "sha256": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", + "show_in_ui": true, + "source_products": [ + "FalconInsight" + ], + "source_vendors": [ + "CrowdStrike" + ], + "status": "new", + "tactic": "MachineLearning", + "tactic_id": "CSTA0004", + "technique": "Adware/PUP", + "technique_id": "CST0000", + "timestamp": "2023-11-03T18:00:22.328Z", + "tree_id": "1931778", + "tree_root": "38687993", + "triggering_process_graph_id": "pid:2ce4124ad4adc8c1c54dbfec4b:399748687993", + "type": "ldt", + "updated_timestamp": "2023-11-03T19:00:23.985Z", "user_id": "S-1-5-21-1909377054-3469629671-4104191496-4425", "user_name": "mohit.jha" - }, - "parent_process_id": "392736520876", - "pattern_disposition": 2176, - "pattern_disposition_description": "Prevention/Quarantine,processwasblockedfromexecutionandquarantinewasattempted.", - "pattern_disposition_details": { - "blocking_unsupported_or_disabled": false, - "bootup_safeguard_enabled": false, - "critical_process_disabled": false, - "detect": false, - "fs_operation_blocked": false, - "handle_operation_downgraded": false, - "inddet_mask": false, - "indicator": false, - "kill_action_failed": false, - "kill_parent": false, - "kill_process": false, - "kill_subprocess": false, - "operation_blocked": false, - "policy_disabled": false, - "process_blocked": true, - "quarantine_file": true, - "quarantine_machine": false, - "registry_operation_blocked": false, - "rooting": false, - "sensor_only": false, - "suspend_parent": false, - "suspend_process": false - }, - "pattern_id": "5761", - "platform": "Windows", - "poly_id": "AACSASiWEnxKlIIaw8LWC-8XINBatE2uYZaWqRAAATiEEfPFwhoY4opnh1CQjm0tvUQp4Lu5eOAx29ZVj-qrGrA==", - "process_end_time": "2023-11-03T18:00:21.000Z", - "process_id": "399748687993", - "process_start_time": "2023-11-03T18:00:13.000Z", - "product": "epp", - "quarantined_files": [ - { - "filename": "\\Device\\Volume3\\Users\\yuvraj.mahajan\\AppData\\Local\\Temp\\Temp3cc4c329-2896-461f-9dea-88009eb2e8fb_pfSenseFirewallOpenVPNClients-20230823T120504Z-001.zip\\pfSenseFirewallOpenVPNClients\\Windows\\openvpn-cds-pfSense-UDP4-1194-pfsense-install-2.6.5-I001-amd64.exe", - "id": "2ce412d17b334ad4adc8c1c54dbfec4b_b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "sha256": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "state": "quarantined" - } - ], - "scenario": "NGAV", - "severity": 30, - "sha1": "0000000000000000000000000000000000000000", - "sha256": "b26a6791b72753d2317efd5e1363d93fdd33e611c8b9e08a3b24ea4d755b81fd", - "show_in_ui": true, - "source_products": [ - "FalconInsight" - ], - "source_vendors": [ - "CrowdStrike" - ], - "status": "new", - "tactic": "MachineLearning", - "tactic_id": "CSTA0004", - "technique": "Adware/PUP", - "technique_id": "CST0000", - "timestamp": "2023-11-03T18:00:22.328Z", - "tree_id": "1931778", - "tree_root": "38687993", - "triggering_process_graph_id": "pid:2ce4124ad4adc8c1c54dbfec4b:399748687993", - "type": "ldt", - "updated_timestamp": "2023-11-03T19:00:23.985Z", - "user_id": "S-1-5-21-1909377054-3469629671-4104191496-4425", - "user_name": "mohit.jha" -} + } +] diff --git a/tracecat/api/app.py b/tracecat/api/app.py index 9c7618208..654c0f443 100644 --- a/tracecat/api/app.py +++ b/tracecat/api/app.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Annotated, Any +import orjson import polars as pl from fastapi import ( Depends, @@ -60,6 +61,7 @@ from tracecat.dsl.graph import RFGraph from tracecat.logging import logger from tracecat.middleware import RequestLoggingMiddleware +from tracecat.parse import parse_child_webhook from tracecat.registry import RegistryValidationError, ValidationError, registry from tracecat.types.api import ( ActionMetadataResponse, @@ -80,6 +82,7 @@ EventSearchParams, SearchSecretsParams, SecretResponse, + ServiceCallbackAction, StartWorkflowParams, StartWorkflowResponse, TriggerWorkflowRunParams, @@ -221,7 +224,11 @@ def check_health() -> dict[str, str]: def validate_incoming_webhook( - webhook_id: str, secret: str, request: Request + webhook_id: str, + secret: str, + request: Request, + *, + validate_method: bool = True, ) -> WorkflowDefinition: """Validate incoming webhook request. @@ -254,7 +261,7 @@ def validate_incoming_webhook( detail="Webhook is offline", ) - if webhook.method.lower() != request.method.lower(): + if validate_method and webhook.method.lower() != request.method.lower(): logger.error("Method does not match") raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -295,13 +302,17 @@ def validate_incoming_webhook( async def handle_incoming_webhook( - request: Request, path: str, secret: str + request: Request, path: str, secret: str, validate_method: bool = True ) -> WorkflowDefinition: - """Handle incoming webhook requests.""" + """Handle an incoming webhook request and set the Role context.""" # TODO(perf): Replace this when we get async sessions with logger.contextualize(webhook_id=path): defn = await asyncio.to_thread( - validate_incoming_webhook, webhook_id=path, secret=secret, request=request + validate_incoming_webhook, + webhook_id=path, + secret=secret, + request=request, + validate_method=validate_method, ) ctx_role.set( Role(type="service", user_id=defn.owner_id, service_id="tracecat-runner") @@ -346,6 +357,142 @@ async def incoming_webhook( return {"status": "ok"} +async def handle_service_callback( + request: Request, service: str +) -> ServiceCallbackAction | None: + if service == "slack": + # if ( + # request.headers["user-agent"] + # != "Slackbot 1.0 (+https://api.slack.com/robots)" + # ): + # raise HTTPException( + # status_code=status.HTTP_400_BAD_REQUEST, + # detail="Invalid User-Agent", + # ) + # NOTE: This coroutine can only be consumed once! + form_data = await request.form() + json_payload = form_data.get("payload") + payload = orjson.loads(json_payload) + # Extract out the webhook + if (actions := payload.get("actions")) is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Slack callback: Invalid payload", + ) + logger.info("Received slack action", actions=actions) + if len(actions) < 1: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Slack callback: No actions", + ) + action = actions[0] + match action: + case { + "type": "static_select", + "action_id": url, + "selected_option": {"value": kv_params}, + }: + # e.g. + # { + # "type": "static_select", + # "action_id": "...", + # "block_id": "nMMIK", + # "selected_option": { + # "text": { + # "type": "plain_text", + # "text": "True Positive", + # "emoji": True, + # }, + # "value": '["closed", "true_positive"]', + # }, + # "action_ts": "1719281272.742854", + # } + + logger.info("Matched static select action", action=action) + child_wh = parse_child_webhook(url, [kv_params]) + + if not child_wh: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid child webhook URL", + ) + return ServiceCallbackAction( + action="webhook", + payload=child_wh["payload"], + metadata={"path": child_wh["path"], "secret": child_wh["secret"]}, + ) + + case {"type": action_type}: + logger.error( + "Invalid action type", action_type=action_type, action=action + ) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid action type {action_type}", + ) + case _: + logger.error("Invalid action", action=action) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid action", + ) + return None + + +@app.post("/callback/{service}", tags=["public"]) +async def webhook_callback( + request: Request, + service: str, + next_action: Annotated[ + ServiceCallbackAction | None, Depends(handle_service_callback) + ], +) -> dict[str, str]: + """Receive a callback from an external service. + + This can be used to trigger a workflow from an external service, or perform some other actions. + """ + + match next_action: + case ServiceCallbackAction( + action="webhook", + payload=payload, + metadata={"path": path, "secret": secret}, + ): + # Don't validate method because callback is always POST + defn = await handle_incoming_webhook( + request, path, secret, validate_method=False + ) + logger.info( + "Received Webhook in callback", + service=service, + path=path, + payload=payload, + role=ctx_role.get(), + ) + + # Fetch the DSL from the workflow object + dsl_input = defn.content + + # Set runtime configuration + if payload: + dsl_input.trigger_inputs = payload + + logger.info(dsl_input.dump_yaml()) + + asyncio.create_task(dispatch_workflow(dsl_input, wf_id=path)) + return {"status": "ok", "message": "Webhook dispatched"} + + case None: + logger.info("No next action", service=service) + return {"status": "ok", "message": "No action taken"} + case _: + logger.error("Unsupported next action", next_action=next_action) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Unsupported next action", + ) + + # ----- Workflows ----- # diff --git a/tracecat/dsl/common.py b/tracecat/dsl/common.py index e699b3219..fdf145374 100644 --- a/tracecat/dsl/common.py +++ b/tracecat/dsl/common.py @@ -4,6 +4,7 @@ import asyncio import json +from collections.abc import Coroutine from pathlib import Path from tempfile import SpooledTemporaryFile from typing import Annotated, Any, Literal, Self @@ -111,10 +112,22 @@ class ActionTest(BaseModel): failure: Any = Field(default=None, description="Patched failure output") async def resolve_success_output(self) -> Any: - output = self.success - if isinstance(output, str): - output = await asyncio.to_thread(resolve_string_or_uri, output) - return output + def resolver_coro(_obj: Any) -> Coroutine: + return asyncio.to_thread(resolve_string_or_uri, _obj) + + obj = self.success + match obj: + case str(): + return await resolver_coro(obj) + case list(): + tasks = [] + async with asyncio.TaskGroup() as tg: + for item in obj: + task = tg.create_task(resolver_coro(item)) + tasks.append(task) + return [task.result() for task in tasks] + case _: + return obj def resolve_string_or_uri(string_or_uri: str) -> Any: diff --git a/tracecat/expressions/functions.py b/tracecat/expressions/functions.py index a03ea5c54..d68703135 100644 --- a/tracecat/expressions/functions.py +++ b/tracecat/expressions/functions.py @@ -1,3 +1,4 @@ +import base64 import itertools import json import operator @@ -32,6 +33,14 @@ def _format_string(template: str, *values: Any) -> str: return template.format(*values) +def _str_to_b64(x: str) -> str: + return base64.b64encode(x.encode()).decode() + + +def _b64_to_str(x: str) -> str: + return base64.b64decode(x).decode() + + BUILTIN_TYPE_NAPPING = { "int": int, "float": float, @@ -80,8 +89,12 @@ def _format_string(template: str, *values: Any) -> str: # Type conversion # Convert JSON to string "serialize_json": json.dumps, + "deserialize_json": json.loads, # Convert timestamp to datetime "from_timestamp": lambda x, unit,: _from_timestamp(x, unit), + # Base64 + "to_base64": _str_to_b64, + "from_base64": _b64_to_str, } diff --git a/tracecat/parse.py b/tracecat/parse.py new file mode 100644 index 000000000..dc48ba534 --- /dev/null +++ b/tracecat/parse.py @@ -0,0 +1,46 @@ +from typing import Any +from urllib.parse import parse_qs, urlparse + + +def insert_obj_by_path( + obj: dict[str, Any], *, path: str, value: Any, sep: str = "." +) -> None: + *stem, leaf = path.split(sep=sep) + for key in stem: + obj = obj.setdefault(key, {}) + obj[leaf] = value + + +def reconstruct_obj(flat_kv: dict[str, Any], *, sep: str = ".") -> dict[str, Any]: + """Parse a flat key-value dictionary into a nested dictionary. + + Keys are expected to be delimiter-separated paths. + """ + obj = {} + for path, value in flat_kv.items(): + if isinstance(value, list) and len(value) == 1: + value = value[0] + insert_obj_by_path(obj, path=path, value=value, sep=sep) + return obj + + +def parse_child_webhook( + url: str, additional_qs: list[str] | None = None +) -> dict[str, Any] | None: + """Return the reconstructed payload and metadata from a child webhook URL.""" + parsed_url = urlparse(url) + # Dot delimited keys + query = parsed_url.query + if additional_qs: + query += "&" + "&".join(additional_qs) + query_kv = parse_qs(query) + payload = reconstruct_obj(query_kv) + endpoint, path, secret = parsed_url.path.strip("/").split("/") + if endpoint != "webhooks": + return None + + return { + "payload": payload, + "path": path, + "secret": secret, + } diff --git a/tracecat/types/api.py b/tracecat/types/api.py index 6868cec67..5b4289d3e 100644 --- a/tracecat/types/api.py +++ b/tracecat/types/api.py @@ -346,3 +346,9 @@ class CommitWorkflowResponse(BaseModel): message: str errors: list[UDFArgsValidationResponse] | None = None metadata: dict[str, Any] | None = None + + +class ServiceCallbackAction(BaseModel): + action: Literal["webhook"] + payload: dict[str, Any] + metadata: dict[str, Any]