CrowdstrikeFalcon
The CrowdStrike Falcon OAuth 2 API (formerly the Falcon Firehose API), enables fetching and resolving detections, searching devices, getting behaviors by ID, containing hosts, and lifting host containment.
Endpoint · CrowdStrike Falcon
Details
| ID | CrowdstrikeFalcon |
|---|---|
| Provider | CrowdStrike |
| Category | Endpoint |
| From Version | 5.0.0 |
| Docker Image | demisto/py3-tools:1.0.0.10120494 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security Exposure Management |
README
The CrowdStrike Falcon OAuth 2 API (formerly the Falcon Firehose API), enables fetching and resolving detections, searching devices, getting behaviors by ID, containing hosts, and lifting host containment.
Configure CrowdStrike Falcon in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g., https://api.crowdstrike.com) | True | |
| Client ID | False | |
| Secret | False | |
| Source Reliability | Reliability of the source providing the intelligence data. Currently used for “CVE” reputation command. | False |
| Fetch incidents | False | |
| Incident Fetch types | Choose what incident types to fetch - You can choose any combination. Note: Records from the detection endpoint of the CrowdStrike Falcon UI could be of types: ‘Endpoint Detection’ and ‘OFP Detection’. |
False |
| Incident type | False | |
| First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) | Supported in Cortex XSOAR and Cortex Platform. | False |
| Max incidents per fetch | Supported in Cortex XSOAR and Cortex Platform. Input a value between 1-500. Default is 15. | False |
| Incidents Fetch Interval | Supported in Cortex XSOAR and Cortex Platform. | False |
| Advanced: Time in minutes to look back when fetching incidents and detections | Use this parameter to determine the look-back period for searching for incidents that were created before the last run time and did not match the query when they were created. | False |
| Endpoint Detections filter query | Use the Falcon Query Language to refine the data collected. For more information, refer to the FQL syntax documentation: https://www.falconpy.io/Usage/Falcon-Query-Language.html | False |
| IDP Detections filter query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| Mobile Detections filter query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| IOM filter query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| IOA filter query | The Indicator of Attack (IOA) fetch query. This is an FQL filter that is combined with the time-range filter using AND. For example: cloud_provider:’aws’. For more information, see the Falcon Query Language (FQL) documentation at: https://www.falconpy.io/Usage/Falcon-Query-Language.html. | False |
| Detections from On-Demand Scans filter query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| OFP Detections filter query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| Third Party Detection fetch query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| NGSIEM Detection fetch query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| NGSIEM automated leads fetch query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| NGSIEM cases fetch query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| NGSIEM incidents fetch query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| Recon filter query | Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). | False |
| Mirroring Direction | Choose the direction to mirror the detection: Incoming (from CrowdStrike Falcon to Cortex XSOAR), Outgoing (from Cortex XSOAR to CrowdStrike Falcon), or Incoming and Outgoing (to/from CrowdStrike Falcon and Cortex XSOAR). | False |
| Close Mirrored XSOAR Incident | When selected, closing the CrowdStrike Falcon incident is mirrored in Cortex XSOAR. | False |
| Close Mirrored CrowdStrike Falcon Incident or Detection | When selected, closing the Cortex XSOAR incident is mirrored in CrowdStrike Falcon, according to the types that were chosen to be fetched and mirrored. | False |
| Reopen Statuses | CrowdStrike Falcon statuses that will reopen an incident in Cortex XSOAR if closed. You can choose any combination. | False |
| Fetch events | False | |
| Event Fetch types | Choose what event types to fetch - You can choose any combination. Note: Records from the detection endpoint of the CrowdStrike Falcon UI could be of types: ‘Endpoint Detection’ and ‘OFP Detection’. | False |
| Events Fetch Interval | False | |
| Advanced: Time in minutes to look back when fetching events and detections | Use this parameter to determine the look-back period for searching for events that were created before the last run time and did not match the query when they were created. | False |
| Fetch assets and vulnerabilities | False | |
| Fetch Asset types | The asset sources to ingest into the Cortex Unified Asset Inventory. | False |
| Assets Fetch Interval | The fetch interval for assets and vulnerabilities. It is recommended to set it to 1 hour. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False |
Required API client scope
In order to use the CrowdStrike Falcon integration, the API client must have the following scope and permissions:
-
Real Time Response - Read and Write - Real Time Response Admin - Write
- Alerts - Read and Write
- IOC Manager - Read and Write
- IOCs - Read and Write
- IOA Exclusions - Read and Write
- Machine Learning Exclusions - Read and Write
- Detections - Read and Write
- Hosts - Read and Write
- Host Groups - Read and Write
- Incidents - Read and Write
- Spotlight Vulnerabilities - Read
- User Management - Read
- On-Demand Scans (ODS) - Read and Write
- Identity Protection Entities - Read and Write
- Identity Protection Detections - Read and Write
- Identity Protection Timeline - Read
- Identity Protection Assessment - Read
- Falcon Container Image - Read
- Recon - Read and Write
- Workflow - Read and Write
Incident Mirroring (Cortex XSOAR Only)
You can enable incident mirroring between Cortex XSOAR incidents and CrowdStrike Falcon corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:
- Enable Fetching incidents in your instance configuration.
- In the Fetch types integration parameter, select what types to mirror.
- Optional: You can go to one of the fetch query parameters and select the query to fetch the events from CrowdStrike Falcon.
-
In the Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:
Option Description None Turns off incident mirroring. Incoming Any changes in CrowdStrike Falcon events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents. Outgoing Any changes in Cortex XSOAR incidents will be reflected in CrowdStrike Falcon events (outgoing mirrored fields). Incoming And Outgoing Changes in Cortex XSOAR incidents and CrowdStrike Falcon events will be reflected in both directions. - Optional: Check the Close Mirrored XSOAR Incident integration parameter to close the Cortex XSOAR incident when the corresponding event is closed in CrowdStrike Falcon.
- Optional: Check the Close Mirrored CrowdStrike Falcon Incident or Detection integration parameter to close the CrowdStrike Falcon incident or detection when the corresponding Cortex XSOAR incident is closed.
Newly fetched Cortex XSOAR incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.
Important Notes
- To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and CrowdStrike Falcon.
- When mirroring in incidents from CrowdStrike Falcon to Cortex XSOAR:
- For the
tagsfield, tags can only be added from the remote system. - When enabling the Close Mirrored XSOAR Incident integration parameter, the field in CrowdStrike Falcon that determines whether the incident was closed is the
statusfield. - In case the look-back parameter is initialized with a certain value and during a time that incidents were fetched, if changing
the lookback to a number that is greater than the previous value, then in the initial incident fetching there will be incidents duplications.
If the integration was already set with lookback > 0, and the lookback is not being increased at any point of time, then those incident duplications would not occur.
- For the
Fetch Incidents (Cortex XSOAR Only)
CrowdStrike Falcon incidents or detections can be fetched as incidents in Cortex XSOAR.
Users can specify a fetch query per CrowdStrike Falcon fetch type when configuring the integration instance to control which records are fetched.
Indicator of Misconfiguration (IOM) Fetch Query
The IOM Fetch query relies on an FQL filter expression.
Available filters:
use_current_scan_ids(use this to get records for latest scans)account_nameaccount_idagent_idattack_typesazure_subscription_idcloud_providercloud_service_keywordcustom_policy_idis_managedpolicy_idpolicy_typeresource_idregionstatusseverityseverity_string
For example: cloud_provider: 'aws'+account_id: 'my_id'
Indicator of Attack (IOA) Fetch Query
Indicator of Attack (IOA) fetch query. An FQL filter that will be combined with the time-range filter using AND. For example: cloud_provider:'aws'. For more information, see the FQL Syntax Documentation.
Fetch Assets
CrowdStrike Falcon assets and vulnerabilities can be fetched and ingested into the Cortex XSIAM Unified Asset Inventory (UAI).
Select the desired method in the Fetch Assets Type parameter:
- Spotlight: Fetches vulnerabilities from the Spotlight Vulnerabilities Endpoint and enriches them with the associated host details. Both the vulnerabilities and the corresponding assets are ingested into the Unified Asset Inventory. Only vulnerabilities updated within the last 100 days are retrieved, keeping each collection focused on recent data.
- CNAPP Alerts: Fetches Cloud Native Application Protection Platform (CNAPP) alerts as assets.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
cs-falcon-search-device
Searches for a device that matches the query.
Base Command
cs-falcon-search-device
Input
| Argument Name | Description | Required |
|---|---|---|
| extended_data | Whether or not to get additional data about the device. Possible values are: Yes, No. | Optional |
| filter | The query by which to filter the device. The query format should be filter_parameter_name:'filter_value'. |
Optional |
| limit | The maximum records to return [1-5000]. Default is 50. | Optional |
| offset | The offset to start retrieving records from. | Optional |
| ids | A comma-separated list of device IDs to limit the results. | Optional |
| status | The status of the device. Possible values are: normal, containment_pending, contained, lift_containment_pending. | Optional |
| hostname | The hostname of the device. | Optional |
| platform_name | The platform name of the device. Possible values are: Windows, Mac, Linux. | Optional |
| site_name | The site name of the device. | Optional |
| sort | The property to sort by (e.g., status.desc or hostname.asc). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Device.ID | String | The ID of the device. |
| CrowdStrike.Device.LocalIP | String | The local IP address of the device. |
| CrowdStrike.Device.ExternalIP | String | The external IP address of the device. |
| CrowdStrike.Device.Hostname | String | The hostname of the device. |
| CrowdStrike.Device.OS | String | The operating system of the device. |
| CrowdStrike.Device.MacAddress | String | The MAC address of the device. |
| CrowdStrike.Device.FirstSeen | String | The first time the device was seen. |
| CrowdStrike.Device.LastSeen | String | The last time the device was seen. |
| CrowdStrike.Device.PolicyType | String | The policy type of the device. |
| CrowdStrike.Device.Status | String | The device status. |
| Endpoint.Hostname | String | The endpoint hostname. |
| Endpoint.OS | String | The endpoint operation system. |
| Endpoint.IPAddress | String | The endpoint IP address. |
| Endpoint.ID | String | The endpoint ID. |
| Endpoint.Status | String | The endpoint status. |
| Endpoint.IsIsolated | String | The endpoint isolation status. |
| Endpoint.MACAddress | String | The endpoint MAC address. |
| Endpoint.Vendor | String | The integration name of the endpoint vendor. |
| Endpoint.OSVersion | String | The endpoint operation system version. |
Command Example
!cs-falcon-search-device ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1,a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Context Example
{
"CrowdStrike": {
"Device": [
{
"ExternalIP": "94.188.164.68",
"MacAddress": "8c-85-90-3d-ed-3e",
"Hostname": "154.132.82-test-co.in-addr.arpa",
"LocalIP": "192.168.1.76",
"LastSeen": "2019-03-28T02:36:41Z",
"OS": "Mojave (10.14)",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"FirstSeen": "2017-12-28T22:38:11Z",
"Status": "contained"
},
{
"ExternalIP": "94.188.164.68",
"MacAddress": "f0-18-98-74-8c-31",
"Hostname": "154.132.82-test-co.in-addr.arpa",
"LocalIP": "172.22.14.237",
"LastSeen": "2019-03-17T10:03:17Z",
"OS": "Mojave (10.14)",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"FirstSeen": "2017-12-10T11:01:20Z",
"Status": "contained"
}
]
},
"Endpoint": [
{
"Hostname": "154.132.82-test-co.in-addr.arpa",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"IPAddress": "192.168.1.76",
"OS": "Mojave (10.14)",
"Status": "Online",
"Vendor": "CrowdStrike Falcon",
"MACAddress": "1-1-1-1"
},
{
"Hostname": "154.132.82-test-co.in-addr.arpa",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"IPAddress": "172.22.14.237",
"OS": "Mojave (10.14)",
"Status": "Online",
"Vendor": "CrowdStrike Falcon",
"MACAddress": "1-1-1-1"
}
]
}
Human Readable Output
Devices
ID Hostname OS Mac Address Local IP External IP First Seen Last Seen Status a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 154.132.82-test-co.in-addr.arpa Mojave (10.14) 8c-85-90-3d-ed-3e 192.168.1.76 94.188.164.68 2017-12-28T22:38:11Z 2019-03-28T02:36:41Z contained a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 154.132.82-test-co.in-addr.arpa Mojave (10.14) f0-18-98-74-8c-31 172.22.14.237 94.188.164.68 2017-12-10T11:01:20Z 2019-03-17T10:03:17Z contained
cs-falcon-search-detection
Search for details of specific detections, either using a filter query, or by providing the IDs of the detections.
Base Command
cs-falcon-search-detection
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of IDs of the detections to search. If provided, will override other arguments. | Optional |
| filter | Filter detections using a query in Falcon Query Language (FQL). For example, filter=”device.hostname:’CS-SE-TG-W7-01’“ For a full list of valid filter options, see: https://falcon.crowdstrike.com/support/documentation/2/query-api-reference#detectionsearch. Default is product:’epp’+type:’ldt’. |
Optional |
| extended_data | Whether to get additional data such as device and behaviors processed. Possible values are: Yes, No. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Detection.Behavior.FileName | String | The filename of the behavior. |
| CrowdStrike.Detection.Behavior.Scenario | String | The scenario name of the behavior. |
| CrowdStrike.Detection.Behavior.MD5 | String | The MD5 hash of the IOC of the behavior. |
| CrowdStrike.Detection.Behavior.SHA256 | String | The SHA256 hash of the IOC of the behavior. |
| CrowdStrike.Detection.Behavior.IOCType | String | The type of the IOC. |
| CrowdStrike.Detection.Behavior.IOCValue | String | The value of the IOC. |
| CrowdStrike.Detection.Behavior.CommandLine | String | The command line executed in the behavior. |
| CrowdStrike.Detection.Behavior.UserName | String | The username related to the behavior. |
| CrowdStrike.Detection.Behavior.SensorID | String | The sensor ID related to the behavior. |
| CrowdStrike.Detection.Behavior.ParentProcessID | String | The ID of the parent process. |
| CrowdStrike.Detection.Behavior.ProcessID | String | The process ID of the behavior. |
| CrowdStrike.Detection.Behavior.ID | String | The ID of the behavior. |
| CrowdStrike.Detection.System | String | The system name of the detection. |
| CrowdStrike.Detection.CustomerID | String | The ID of the customer (CID). |
| CrowdStrike.Detection.MachineDomain | String | The name of the domain of the detection machine. |
| CrowdStrike.Detection.ID | String | The detection ID. |
| CrowdStrike.Detection.ProcessStartTime | Date | The start time of the process that generated the detection. |
Command Example
!cs-falcon-search-detection filter="product:'idp'"
!cs-falcon-search-detection filter="product:'mobile'"
!cs-falcon-search-detection filter="product:'ngsiem'"
!cs-falcon-search-detection filter="product:'thirdparty'"
!cs-falcon-search-detection ids=ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1898376850347,ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1092318056279064902
Context Example
{
"CrowdStrike": {
"Detection": [
{
"Status": "false_positive",
"ProcessStartTime": "2019-03-21T20:32:55.654489974Z",
"Behavior": [
{
"IOCType": "domain",
"ProcessID": "2279170016592",
"Scenario": "intel_detection",
"ParentProcessID": "2257232915544",
"CommandLine": "C:\\Python27\\pythonw.exe -c __import__('idlelib.run').run.main(True) 1250",
"UserName": "josh",
"FileName": "pythonw.exe",
"SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ID": "4900",
"IOCValue": "systemlowcheck.com",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
},
{
"IOCType": "domain",
"ProcessID": "2283087267593",
"Scenario": "intel_detection",
"ParentProcessID": "2279170016592",
"CommandLine": "ping.exe systemlowcheck.com",
"UserName": "josh",
"FileName": "PING.EXE",
"SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ID": "4900",
"IOCValue": "systemlowcheck.com",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
],
"MaxSeverity": 70,
"System": "DESKTOP-S49VMIL",
"ID": "ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1898376850347",
"MachineDomain": "",
"ShowInUi": true,
"CustomerID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
},
{
"Status": "new",
"ProcessStartTime": "2019-02-04T07:05:57.083205971Z",
"Behavior": [
{
"IOCType": "sha256",
"ProcessID": "201917905370426448",
"Scenario": "known_malware",
"ParentProcessID": "201917902773103685",
"CommandLine": "./xSf",
"UserName": "user@u-MacBook-Pro-2.local",
"FileName": "xSf",
"SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ID": "3206",
"IOCValue": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
},
{
"IOCType": "sha256",
"ProcessID": "201917905370426448",
"Scenario": "known_malware",
"ParentProcessID": "201917902773103685",
"CommandLine": "./xSf",
"UserName": "user@u-MacBook-Pro-2.local",
"FileName": "xSf",
"SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ID": "3206",
"IOCValue": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
],
"MaxSeverity": 30,
"System": "u-MacBook-Pro-2.local",
"ID": "ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1092318056279064902",
"MachineDomain": "",
"ShowInUi": true,
"CustomerID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
]
}
}
Human Readable Output
Detections Found
ID Status System Process Start Time Customer ID Max Severity ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1898376850347 false_positive DESKTOP-S49VMIL 2019-03-21T20:32:55.654489974Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 70 ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1092318056279064902 new u-MacBook-Pro-2.local 2019-02-04T07:05:57.083205971Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 30
cs-falcon-resolve-detection
Resolves and updates a detection using the provided arguments. At least one optional argument must be passed, otherwise no change will take place. Note that IDP detections are not supported.
Base Command
cs-falcon-resolve-detection
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of one or more IDs to resolve. | Required |
| status | The status to transition a detection to. Possible values are: new, in_progress, closed, reopened. | Optional |
| assigned_to_uuid | A user ID, for example: 1234567855512345678. username and assigned_to_uuid are mutually exclusive. | Optional |
| comment | Optional comment to add to the detection. Comments are displayed with the detection in CrowdStrike Falcon and provide context or notes for other Falcon users. | Optional |
| show_in_ui | If true, displays the detection in the UI. Possible values are: true, false. | Optional |
| username | Username to assign the detections to. (This is usually the user’s email address, but may vary based on your configuration). username and assigned_to_uuid are mutually exclusive. | Optional |
| tag | The tag to add to the detection, supported only for API V3. | Optional |
Context Output
There is no context output for this command.
cs-falcon-contain-host
Contains containment for a specified host. When contained, a host can only communicate with the CrowdStrike cloud and any IPs specified in your containment policy.
Base Command
cs-falcon-contain-host
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of host agent IDs (AID) of the host to contain. Get an agent ID from a detection. | Required |
Context Output
There is no context output for this command.
cs-falcon-lift-host-containment
Lifts containment on the host, which returns its network communications to normal. When lift_filesystem_containment_all is set to true, lifts filesystem containment instead.
Base Command
cs-falcon-lift-host-containment
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of host agent IDs (AIDs) of the hosts to contain. Get an agent ID from a detection. | Required |
| lift_filesystem_containment_all | Whether to lift filesystem containment instead of network containment. When set to true, uses the lift_filesystem_containment_all action to remove filesystem containment from the specified hosts. Possible values are: true, false. Default is false. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-lift-host-containment ids="a]1234567890abcdef12345678"
Human Readable Output
Containment has been lifted off host ‘a]1234567890abcdef12345678’
cs-falcon-run-command
Sends commands to hosts.
Base Command
cs-falcon-run-command
Input
| Argument Name | Description | Required |
|---|---|---|
| queue_offline | Any commands run against an offline-queued session will be queued up and executed when the host comes online. | Optional |
| host_ids | A comma-separated list of host agent IDs to run commands for. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. | Required |
| command_type | The type of command to run. | Required |
| full_command | The full command to run. | Required |
| scope | The scope to run the command for. (NOTE: In order to run the CrowdStrike RTR put command, it is necessary to pass scope=admin). Possible values are: read, write, admin. Default is read. |
Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Default is 180. | Optional |
| target | The target to run the command for. Possible values are: batch, single. Default is batch. | Optional |
| batch_id | A batch ID to execute the command on. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.HostID | String | The ID of the host the command was running for. |
| CrowdStrike.Command.SessionID | string | The session ID of the host. |
| CrowdStrike.Command.Stdout | String | The standard output of the command. |
| CrowdStrike.Command.Stderr | String | The standard error of the command. |
| CrowdStrike.Command.BaseCommand | String | The base command. |
| CrowdStrike.Command.FullCommand | String | The full command. |
| CrowdStrike.Command.TaskID | string | (For single host) The ID of the command request which has been accepted. |
| CrowdStrike.Command.Complete | boolean | (For single host) True if the command completed. |
| CrowdStrike.Command.NextSequenceID | number | (For single host) The next sequence ID. |
| CrowdStrike.Command.BatchID | String | The Batch ID that the command was executed on. |
Command Example
cs-falcon-run-command host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 command_type=ls full_command="ls C:\\"
Context Example
{
'CrowdStrike': {
'Command': [{
'HostID': 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
'Stdout': 'Directory listing for C:\\ -\n\n'
'BatchID': 'batch_id'
'Name Type Size (bytes) Size (MB) '
'Last Modified (UTC-5) Created (UTC-5) \n---- '
' ---- ------------ --------- --------------------- -------'
'-------- \n$Recycle.Bin <Directory> -- '
' -- 11/27/2018 10:54:44 AM 9/15/2017 3:33:40 AM \nITAYDI '
' <Directory> -- -- 11/19/2018 1:'
'31:42 PM 11/19/2018 1:31:42 PM ',
'Stderr': '',
'BaseCommand': 'ls',
'Command': 'ls C:\\'
}]
}
Human Readable Output
Command ls C:\ results
BaseCommand Command HostID Stderr Stdout BatchID ls ls C:\ a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 Directory listing for C:\ -
Name Type Size (bytes) Size (MB) Last Modified (UTC-5) Created (UTC-5)
—- —- ———— ——— ——————— —————
$Recycle.Bin <Directory> – – 11/27/2018 10:54:44 AM 9/15/2017 3:33:40 AM
ITAYDI <Directory> – – 11/19/2018 1:31:42 PM 11/19/2018 1:31:42 PMbatch_id
cs-falcon-upload-script
Uploads a script to Falcon CrowdStrike.
Base Command
cs-falcon-upload-script
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The script name to upload. | Required |
| permission_type | The permission type for the custom script. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins. Possible values are: private, group, public. Default is private. | Optional |
| content | The content of the PowerShell script. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-upload-script name=greatscript content="Write-Output 'Hello, World!'"
Human Readable Output
The script was uploaded successfully.
Context Output
There is no context output for this command.
cs-falcon-upload-file
Uploads a file to the CrowdStrike cloud. (Can be used for the RTR ‘put’ command).
Base Command
cs-falcon-upload-file
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | The file entry ID to upload. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-upload-file entry_id=4@4
Human Readable Output
The file was uploaded successfully.
Context Output
There is no context output for this command.
cs-falcon-delete-file
Deletes a file based on the provided ID or name. Can delete only one file at a time.
Base Command
cs-falcon-delete-file
Input
| Argument Name | Description | Required |
|---|---|---|
| file_id | The ID of the file to delete. Either this argument or file_name is required. When both are specified, file_id takes precedence. | Optional |
| file_name | The name of the file to delete. Either this argument or file_id is required. When both are specified, file_id takes precedence. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-delete-file file_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Human Readable Output
File le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 was deleted successfully.
Context Output
There is no context output for this command.
cs-falcon-get-file
Returns files based on the provided IDs. These files are used for the RTR ‘put’ command.
Base Command
cs-falcon-get-file
Input
| Argument Name | Description | Required |
|---|---|---|
| file_id | A comma-separated list of file IDs to get. The list of file IDs can be retrieved by running the ‘cs-falcon-list-files’ command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.File.ID | String | The ID of the file. |
| CrowdStrike.File.CreatedBy | String | The email address of the user who created the file. |
| CrowdStrike.File.CreatedTime | Date | The datetime the file was created. |
| CrowdStrike.File.Description | String | The description of the file. |
| CrowdStrike.File.Type | String | The type of the file. For example, script. |
| CrowdStrike.File.ModifiedBy | String | The email address of the user who modified the file. |
| CrowdStrike.File.ModifiedTime | Date | The datetime the file was modified. |
| CrowdStrike.File.Name | String | The full name of the file. |
| CrowdStrike.File.Permission | String | The permission type of the file. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins. |
| CrowdStrike.File.SHA256 | String | The SHA-256 hash of the file. |
| File.Type | String | The file type. |
| File.Name | String | The full name of the file. |
| File.SHA256 | String | The SHA-256 hash of the file. |
| File.Size | Number | The size of the file in bytes. |
Command Example
!cs-falcon-get-file file_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Context Example
{
"CrowdStrike": {
"File": {
"CreatedBy": "spongobob@demisto.com",
"CreatedTime": "2019-10-17T13:41:48.487520845Z",
"Description": "Demisto",
"ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "spongobob@demisto.com",
"ModifiedTime": "2019-10-17T13:41:48.487521161Z",
"Name": "Demisto",
"Permission": "private",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Type": "script"
}
}
}
Human Readable Output
CrowdStrike Falcon file le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256 Type spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 script
cs-falcon-list-files
Returns a list of put-file IDs that are available for the user in the ‘put’ command. Due to an API limitation, the maximum number of files returned is 100.
Base Command
cs-falcon-list-files
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.File.ID | String | The ID of the file. |
| CrowdStrike.File.CreatedBy | String | The email address of the user who created the file. |
| CrowdStrike.File.CreatedTime | Date | The datetime the file was created. |
| CrowdStrike.File.Description | String | The description of the file. |
| CrowdStrike.File.Type | String | The type of the file. For example, script. |
| CrowdStrike.File.ModifiedBy | String | The email address of the user who modified the file. |
| CrowdStrike.File.ModifiedTime | Date | The datetime the file was modified. |
| CrowdStrike.File.Name | String | The full name of the file. |
| CrowdStrike.File.Permission | String | The permission type of the file. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins. |
| CrowdStrike.File.SHA256 | String | The SHA-256 hash of the file. |
| File.Type | String | The file type. |
| File.Name | String | The full name of the file. |
| File.SHA256 | String | The SHA-256 hash of the file. |
| File.Size | Number | The size of the file in bytes. |
Command Example
!cs-falcon-list-files
Context Example
{
"CrowdStrike": {
"File": [
{
"CreatedBy": "spongobob@demisto.com",
"CreatedTime": "2019-10-17T13:41:48.487520845Z",
"Description": "Demisto",
"ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "spongobob@demisto.com",
"ModifiedTime": "2019-10-17T13:41:48.487521161Z",
"Name": "Demisto",
"Permission": "private",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Type": "script"
}
]
}
}
Human Readable Output
CrowdStrike Falcon files
CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256 Type spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 script
cs-falcon-get-script
Returns custom scripts based on the provided ID. Used for the RTR ‘runscript’ command.
Base Command
cs-falcon-get-script
Input
| Argument Name | Description | Required |
|---|---|---|
| script_id | A comma-separated list of script IDs to return. The script IDs can be retrieved by running the ‘cs-falcon-list-scripts’ command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Script.ID | String | The ID of the script. |
| CrowdStrike.Script.CreatedBy | String | The email address of the user who created the script. |
| CrowdStrike.Script.CreatedTime | Date | The datetime the script was created. |
| CrowdStrike.Script.Description | String | The description of the script. |
| CrowdStrike.Script.ModifiedBy | String | The email address of the user who modified the script. |
| CrowdStrike.Script.ModifiedTime | Date | The datetime the script was modified. |
| CrowdStrike.Script.Name | String | The script name. |
| CrowdStrike.Script.Permission | String | Permission type of the script. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins. |
| CrowdStrike.Script.SHA256 | String | The SHA-256 hash of the script file. |
| CrowdStrike.Script.RunAttemptCount | Number | The number of times the script attempted to run. |
| CrowdStrike.Script.RunSuccessCount | Number | The number of times the script ran successfully. |
| CrowdStrike.Script.Platform | String | The list of operating system platforms on which the script can run. For example, Windows. |
| CrowdStrike.Script.WriteAccess | Boolean | Whether the user has write access to the script. |
Command Example
!cs-falcon-get-script file_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Context Example
{
"CrowdStrike": {
"Script": [
{
"CreatedBy": "spongobob@demisto.com",
"CreatedTime": "2019-10-17T13:41:48.487520845Z",
"Description": "Demisto",
"ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "spongobob@demisto.com",
"ModifiedTime": "2019-10-17T13:41:48.487521161Z",
"Name": "Demisto",
"Permission": "private",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"RunAttemptCount": 0,
"RunSuccessCount": 0,
"WriteAccess": true
}
]
}
}
Human Readable Output
CrowdStrike Falcon script le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256 spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
cs-falcon-delete-script
Deletes a custom-script based on the provided ID. Can delete only one script at a time.
Base Command
cs-falcon-delete-script
Input
| Argument Name | Description | Required |
|---|---|---|
| script_id | The script ID to delete. The script IDs can be retrieved by running the ‘cs-falcon-list-scripts’ command. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-delete-script script_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Human Readable Output
Script le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 was deleted successfully.
Context Output
There is no context output for this command.
cs-falcon-list-scripts
Returns a list of custom script IDs that are available for the user in the ‘runscript’ command.
Base Command
cs-falcon-list-scripts
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Script.ID | String | The ID of the script. |
| CrowdStrike.Script.CreatedBy | String | The email address of the user who created the script. |
| CrowdStrike.Script.CreatedTime | Date | The datetime the script was created. |
| CrowdStrike.Script.Description | String | The description of the script. |
| CrowdStrike.Script.ModifiedBy | String | The email address of the user who modified the script. |
| CrowdStrike.Script.ModifiedTime | Date | The datetime the script was modified. |
| CrowdStrike.Script.Name | String | The script name. |
| CrowdStrike.Script.Permission | String | Permission type of the script. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins. |
| CrowdStrike.Script.SHA256 | String | The SHA-256 hash of the script file. |
| CrowdStrike.Script.RunAttemptCount | Number | The number of times the script attempted to run. |
| CrowdStrike.Script.RunSuccessCount | Number | The number of times the script ran successfully. |
| CrowdStrike.Script.Platform | String | The list of operating system platforms on which the script can run. For example, Windows. |
| CrowdStrike.Script.WriteAccess | Boolean | Whether the user has write access to the script. |
Command Example
!cs-falcon-list-scripts
Context Example
{
"CrowdStrike": {
"Script": [
{
"CreatedBy": "spongobob@demisto.com",
"CreatedTime": "2019-10-17T13:41:48.487520845Z",
"Description": "Demisto",
"ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "spongobob@demisto.com",
"ModifiedTime": "2019-10-17T13:41:48.487521161Z",
"Name": "Demisto",
"Permission": "private",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"RunAttemptCount": 0,
"RunSuccessCount": 0,
"WriteAccess": true
}
]
}
}
Human Readable Output
CrowdStrike Falcon scripts
CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256 spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
cs-falcon-run-script
Runs a script on the agent host.
Base Command
cs-falcon-run-script
Input
| Argument Name | Description | Required |
|---|---|---|
| script_name | The name of the script to run. | Optional |
| host_ids | A comma-separated list of host agent IDs to run commands. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. | Required |
| raw | The PowerShell script code to run. | Optional |
| timeout | Timeout for how long to wait for the request in seconds. Maximum is 600 (10 minutes). Default is 30. | Optional |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.HostID | String | The ID of the host for which the command was running. |
| CrowdStrike.Command.SessionID | String | The ID of the session of the host. |
| CrowdStrike.Command.Stdout | String | The standard output of the command. |
| CrowdStrike.Command.Stderr | String | The standard error of the command. |
| CrowdStrike.Command.BaseCommand | String | The base command. |
| CrowdStrike.Command.FullCommand | String | The full command. |
Command Example
cs-falcon-run-script host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 raw="Write-Output 'Hello, World!'"
Context Example
{
"CrowdStrike": {
"Command": [
{
"HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Stdout": "Hello, World!",
"Stderr": "",
"BaseCommand": "runscript",
"Command": "runscript -Raw=Write-Output 'Hello, World!'"
}
]
}
}
Human Readable Output
Command runscript -Raw=Write-Output ‘Hello, World! results
BaseCommand Command HostID Stderr Stdout runscript runscript -Raw=Write-Output ‘Hello, World! a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 Hello, World! Type Size (bytes) Size (MB) Last Modified (UTC-5) Created (UTC-5)
—- —- ———— ——— ——————— —————
$Recycle.Bin <Directory> – – 11/27/2018 10:54:44 AM 9/15/2017 3:33:40 AM
ITAYDI <Directory> – – 11/19/2018 1:31:42 PM 11/19/2018 1:31:42 PM
cs-falcon-run-get-command
Batch executes ‘get’ command across hosts to retrieve files.
Base Command
cs-falcon-run-get-command
Input
| Argument Name | Description | Required |
|---|---|---|
| host_ids | A comma-separated list of host agent IDs on which to run the RTR command. | Required |
| file_path | Full path to the file that will be retrieved from each host in the batch. | Required |
| optional_hosts | A comma-separated list of a subset of hosts on which to run the command. | Optional |
| timeout | The number of seconds to wait for the request before it times out. In ISO time format. For example: 2019-10-17T13:41:48.487520845Z. | Optional |
| timeout_duration | The amount of time to wait for the request before it times out. In duration syntax. For example, 10s. Valid units are: ns, us, ms, s, m, h. Maximum value is 10 minutes. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.HostID | string | The ID of the host on which the command was running. |
| CrowdStrike.Command.Stdout | string | The standard output of the command. |
| CrowdStrike.Command.Stderr | string | The standard error of the command. |
| CrowdStrike.Command.BaseCommand | string | The base command. |
| CrowdStrike.Command.TaskID | string | The ID of the command that was running on the host. |
| CrowdStrike.Command.GetRequestID | string | The ID of the command request that was accepted. |
| CrowdStrike.Command.Complete | boolean | True if the command completed. |
| CrowdStrike.Command.FilePath | string | The file path. |
Command Example
cs-falcon-run-get-command host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 file_path="""c:\Windows\notepad.exe"""
Context Example
{
"CrowdStrike": {
"Command": [
{
"BaseCommand": "get",
"Complete": true,
"FilePath": "c:\\Windows\\notepad.exe",
"GetRequestID": "84ee4d50-f499-482e-bac6-b0e296149bbf",
"HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Stderr": "",
"Stdout": "C:\\Windows\\notepad.exe",
"TaskID": "b5c8f140-280b-43fd-8501-9900f837510b"
}
]
}
}
Human Readable Output
Get command has requested for a file c:\Windows\notepad.exe
BaseCommand Complete FilePath GetRequestID HostID Stderr Stdout TaskID get true c:\Windows\notepad.exe 107199bc-544c-4b0c-8f20-3094c062a115 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 C:\Windows\notepad.exe 9c820b97-6a60-4238-bc23-f63513970ec8
cs-falcon-status-get-command
Retrieves the status of the specified batch ‘get’ command.
Base Command
cs-falcon-status-get-command
Input
| Argument Name | Description | Required |
|---|---|---|
| request_ids | A comma-separated list of IDs of the command requested. | Required |
| timeout | The number of seconds to wait for the request before it times out. In ISO time format. For example: 2019-10-17T13:41:48.487520845Z. | Optional |
| timeout_duration | The amount of time to wait for the request before it times out. In duration syntax. For example, 10s. Valid units are: ns, us, ms, s, m, h. Maximum value is 10 minutes. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.File.ID | string | The ID of the file. |
| CrowdStrike.File.TaskID | string | The ID of the command that is running. |
| CrowdStrike.File.CreatedAt | date | The date the file was created. |
| CrowdStrike.File.DeletedAt | date | The date the file was deleted. |
| CrowdStrike.File.UpdatedAt | date | The date the file was last updated. |
| CrowdStrike.File.Name | string | The full name of the file. |
| CrowdStrike.File.SHA256 | string | The SHA256 hash of the file. |
| CrowdStrike.File.Size | number | The size of the file in bytes. |
| File.Name | string | The full name of the file. |
| File.Size | number | The size of the file in bytes. |
| File.SHA256 | string | The SHA256 hash of the file. |
Command Example
!cs-falcon-status-get-command request_ids="84ee4d50-f499-482e-bac6-b0e296149bbf"
Context Example
{
"CrowdStrike": {
"File": {
"CreatedAt": "2020-05-01T16:09:00Z",
"DeletedAt": null,
"ID": 185596,
"Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Size": 0,
"TaskID": "b5c8f140-280b-43fd-8501-9900f837510b",
"UpdatedAt": "2020-05-01T16:09:00Z"
}
},
"File": {
"Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Size": 0
}
}
Human Readable Output
CrowdStrike Falcon files
CreatedAt DeletedAt ID Name SHA256 Size TaskID UpdatedAt 2020-05-01T16:09:00Z 185596 \Device\HarddiskVolume2\Windows\notepad.exe a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 0 b5c8f140-280b-43fd-8501-9900f837510b 2020-05-01T16:09:00Z
cs-falcon-status-command
Gets the status of a command executed on a host.
Base Command
cs-falcon-status-command
Input
| Argument Name | Description | Required |
|---|---|---|
| request_id | The ID of the command requested. | Required |
| sequence_id | The sequence ID in chunk requests. | Optional |
| scope | The scope to run the command for. Possible values are: read, write, admin. Default is read. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.TaskID | string | The ID of the command request that was accepted. |
| CrowdStrike.Command.Stdout | string | The standard output of the command. |
| CrowdStrike.Command.Stderr | string | The standard error of the command. |
| CrowdStrike.Command.BaseCommand | string | The base command. |
| CrowdStrike.Command.Complete | boolean | True if the command completed. |
| CrowdStrike.Command.SequenceID | number | The sequence ID in the current request. |
| CrowdStrike.Command.NextSequenceID | number | The sequence ID for the next request in the chunk request. |
Command Example
!cs-falcon-status-command request_id="ae323961-5aa8-442e-8461-8d05c4541d7d"
Context Example
{
"CrowdStrike": {
"Command": [
{
"BaseCommand": "ls",
"Complete": true,
"NextSequenceID": null,
"SequenceID": null,
"Stderr": "",
"Stdout": "Directory listing for C:\\ -\n\nName Type Size (bytes) Size (MB) Last Modified (UTC+9) Created (UTC+9) \n---- ---- ------------ --------- --------------------- --------------- \n$Recycle.Bin \u003cDirectory\u003e -- -- 2020/01/10 16:05:59 2019/03/19 13:52:43 \nConfig.Msi \u003cDirectory\u003e -- -- 2020/05/01 23:12:50 2020/01/10 16:52:09 \nDocuments and Settings \u003cDirectory\u003e -- -- 2019/09/12 15:03:21 2019/09/12 15:03:21 \nPerfLogs \u003cDirectory\u003e -- -- 2019/03/19 13:52:43 2019/03/19 13:52:43 \nProgram Files \u003cDirectory\u003e -- -- 2020/01/10 17:11:47 2019/03/19 13:52:43 \nProgram Files (x86) \u003cDirectory\u003e -- -- 2020/05/01 23:12:53 2019/03/19 13:52:44 \nProgramData \u003cDirectory\u003e -- -- 2020/01/10 17:16:51 2019/03/19 13:52:44 \nRecovery \u003cDirectory\u003e -- -- 2019/09/11 20:13:59 2019/09/11 20:13:59 \nSystem Volume Information \u003cDirectory\u003e -- -- 2019/09/12 15:08:21 2019/09/11 20:08:43 \nUsers \u003cDirectory\u003e -- -- 2019/09/22 22:26:11 2019/03/19 13:37:22 \nWindows \u003cDirectory\u003e -- -- 2020/05/01 23:09:08 2019/03/19 13:37:22 \npagefile.sys .sys 2334928896 2226.762 2020/05/02 2:10:05 2019/09/11 20:08:44 \nswapfile.sys .sys 268435456 256 2020/05/01 23:09:13 2019/09/11 20:08:44 \n",
"TaskID": "ae323961-5aa8-442e-8461-8d05c4541d7d"
}
]
}
}
Human Readable Output
Command status results
BaseCommand Complete Stdout TaskID ls true Directory listing for C:\ …… ae323961-5aa8-442e-8461-8d05c4541d7d
cs-falcon-get-extracted-file
Gets the RTR extracted file contents for the specified session and SHA256 hash.
Base Command
cs-falcon-get-extracted-file
Input
| Argument Name | Description | Required |
|---|---|---|
| host_id | The host agent ID. | Required |
| sha256 | The SHA256 hash of the file. | Required |
| filename | The filename to use for the archive name and the file within the archive. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-get-extracted-file host_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" sha256="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
Context Output
There is no context output for this command.
cs-falcon-list-host-files
Gets a list of files for the specified RTR session on a host.
Base Command
cs-falcon-list-host-files
Input
| Argument Name | Description | Required |
|---|---|---|
| host_id | The ID of the host agent that lists files in the session. | Required |
| session_id | The ID of the existing session with the agent. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.HostID | string | The ID of the host the command was running for. |
| CrowdStrike.Command.TaskID | string | The ID of the command request that was accepted. |
| CrowdStrike.Command.SessionID | string | The ID of the session of the host. |
| CrowdStrike.File.ID | string | The ID of the file. |
| CrowdStrike.File.CreatedAt | date | The date the file was created. |
| CrowdStrike.File.DeletedAt | date | The date the file was deleted. |
| CrowdStrike.File.UpdatedAt | date | The date the file was last updated. |
| CrowdStrike.File.Name | string | The full name of the file. |
| CrowdStrike.File.SHA256 | string | The SHA256 hash of the file. |
| CrowdStrike.File.Size | number | The size of the file in bytes. |
| File.Name | string | The full name of the file. |
| File.Size | number | The size of the file in bytes. |
| File.SHA256 | string | The SHA256 hash of the file. |
Command Example
!cs-falcon-list-host-files host_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
Context Example
{
"CrowdStrike": {
"Command": {
"HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SessionID": "fdd6408f-6688-441b-8659-41bcad25441c",
"TaskID": "1269ad9e-c11f-4e38-8aba-1a0275304f9c"
},
"File": {
"CreatedAt": "2020-05-01T17:57:42Z",
"DeletedAt": null,
"ID": 186811,
"Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Size": 0,
"Stderr": null,
"Stdout": null,
"UpdatedAt": "2020-05-01T17:57:42Z"
}
},
"File": {
"Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Size": 0
}
}
Human Readable Output
CrowdStrike Falcon files
CreatedAt DeletedAt ID Name SHA256 Size Stderr Stdout UpdatedAt 2020-05-01T17:57:42Z 186811 \Device\HarddiskVolume2\Windows\notepad.exe a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 0 2020-05-01T17:57:42Z
cs-falcon-refresh-session
Refresh a session timeout on a single host.
Base Command
cs-falcon-refresh-session
Input
| Argument Name | Description | Required |
|---|---|---|
| host_id | The ID of the host to extend the session for. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-refresh-session host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Human Readable Output
CrowdStrike Session Refreshed: fdd6408f-6688-441b-8659-41bcad25441c
Context Output
There is no context output for this command.
cs-falcon-search-custom-iocs
Returns a list of your uploaded IOCs that match the search criteria.
Base Command
cs-falcon-search-custom-iocs
Input
| Argument Name | Description | Required |
|---|---|---|
| types | A comma-separated list of indicator types. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. | Optional |
| values | A comma-separated list of indicator values. | Optional |
| sources | A comma-separated list of IOC sources. | Optional |
| expiration | The datetime the indicator will become inactive (ISO 8601 format, i.e., YYYY-MM-DDThh:mm:ssZ). | Optional |
| limit | The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 50. | Optional |
| sort | The order the results are returned in. Possible values are: type.asc, type.desc, value.asc, value.desc, policy.asc, policy.desc, share_level.asc, share_level.desc, expiration_timestamp.asc, expiration_timestamp.desc. | Optional |
| offset | The offset to begin the list from. For example, start from the 10th record and return the list. | Optional |
| next_page_token | A pagination token used with the limit parameter to manage pagination of results. Matching the ‘after’ parameter in the API. Use instead of offset. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator. |
| CrowdStrike.IOC.Severity | string | The severity level to apply to this indicator. |
| CrowdStrike.IOC.Source | string | The source of the IOC. |
| CrowdStrike.IOC.Action | string | Action to take when a host observes the custom IOC. |
| CrowdStrike.IOC.Expiration | string | The datetime the indicator will expire. |
| CrowdStrike.IOC.Description | string | The description of the IOC. |
| CrowdStrike.IOC.CreatedTime | date | The datetime the IOC was created. |
| CrowdStrike.IOC.CreatedBy | string | The identity of the user/process who created the IOC. |
| CrowdStrike.IOC.ModifiedTime | date | The datetime the indicator was last modified. |
| CrowdStrike.IOC.ModifiedBy | string | The identity of the user/process who last updated the IOC. |
| CrowdStrike.IOC.MobileAction | string | The action to take on mobile devices when a host observes the custom IOC. |
| CrowdStrike.NextPageToken | unknown | A pagination token used with the limit parameter to manage pagination of results. |
Command Example
!cs-falcon-search-custom-iocs limit=2
Context Example
{
"CrowdStrike": {
"IOC": [
{
"Action": "no_action",
"CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"CreatedTime": "2022-02-16T17:17:25.992164453Z",
"Description": "test",
"Expiration": "2022-02-17T13:47:57Z",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedTime": "2022-02-16T17:17:25.992164453Z",
"Platforms": [
"mac"
],
"Severity": "informational",
"Source": "Cortex",
"Type": "ipv4",
"Value": "1.1.8.9"
},
{
"Action": "no_action",
"CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"CreatedTime": "2022-02-16T17:16:44.514398876Z",
"Description": "test",
"Expiration": "2022-02-17T13:47:57Z",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedTime": "2022-02-16T17:16:44.514398876Z",
"Platforms": [
"mac"
],
"Severity": "informational",
"Source": "Cortex",
"Type": "ipv4",
"Value": "4.1.8.9"
}
]
}
}
Human Readable Output
Indicators of Compromise
ID Action Severity Type Value Expiration CreatedBy CreatedTime Description ModifiedBy ModifiedTime Platforms Policy ShareLevel Source Tags a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 no_action informational ipv4 1.1.8.9 2022-02-17T13:47:57Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z test a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z mac Cortex a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 no_action informational ipv4 4.1.8.9 2022-02-17T13:47:57Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:16:44.514398876Z test a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:16:44.514398876Z mac Cortex
cs-falcon-get-custom-ioc
Gets the full definition of one or more indicators that you are watching.
Base Command
cs-falcon-get-custom-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The IOC type to retrieve. Either ioc_id or ioc_type and value must be provided. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. | Optional |
| value | The string representation of the indicator. Either ioc_id or ioc_type and value must be provided. | Optional |
| ioc_id | The ID of the IOC to get. The ID of the IOC can be retrieved by running the ‘cs-falcon-search-custom-iocs’ command. Either ioc_id or ioc_type and value must be provided. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator. |
| CrowdStrike.IOC.Severity | string | The severity level to apply to this indicator. |
| CrowdStrike.IOC.Source | string | The source of the IOC. |
| CrowdStrike.IOC.Action | string | Action to take when a host observes the custom IOC. |
| CrowdStrike.IOC.Expiration | string | The datetime when the indicator will expire. |
| CrowdStrike.IOC.Description | string | The description of the IOC. |
| CrowdStrike.IOC.CreatedTime | date | The datetime the IOC was created. |
| CrowdStrike.IOC.CreatedBy | string | The identity of the user/process who created the IOC. |
| CrowdStrike.IOC.ModifiedTime | date | The datetime the indicator was last modified. |
| CrowdStrike.IOC.ModifiedBy | string | The identity of the user/process who last updated the IOC. |
| CrowdStrike.IOC.MobileAction | string | The action to take on mobile devices when a host observes the custom IOC. |
Command Example
!cs-falcon-get-custom-ioc type=ipv4 value=7.5.9.8
Context Example
{
"CrowdStrike": {
"IOC": {
"Action": "no_action",
"MobileAction": "no_action",
"CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"CreatedTime": "2022-02-16T14:25:22.968603813Z",
"Expiration": "2022-02-17T17:55:09Z",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedTime": "2022-02-16T14:25:22.968603813Z",
"Platforms": [
"linux"
],
"Severity": "informational",
"Source": "Cortex",
"Tags": [
"test",
"test1"
],
"Type": "ipv4",
"Value": "7.5.9.8"
}
}
}
Human Readable Output
Indicator of Compromise
ID Action Severity Type Value Expiration CreatedBy CreatedTime Description ModifiedBy ModifiedTime Platforms Policy ShareLevel Source Tags a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 no_action informational ipv4 7.5.9.8 2022-02-17T17:55:09Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T14:25:22.968603813Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T14:25:22.968603813Z linux Cortex test,
test1
cs-falcon-upload-custom-ioc
Uploads an indicator for CrowdStrike to monitor.
Base Command
cs-falcon-upload-custom-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| ioc_type | The type of the indicator. Possible values are: sha256, md5, domain, ipv4, ipv6. | Required |
| value | A comma-separated list of indicators. More than one value can be supplied to upload multiple IOCs of the same type but with different values. Note that the uploaded IOCs will have the same properties (as supplied in other arguments). |
Required |
| action | Action to take when a host observes the custom IOC. Possible values are: no_action - Save the indicator for future use, but take no action. No severity required. allow - Applies to hashes only. Allow the indicator and do not detect it. Severity does not apply and should not be provided. prevent_no_ui - Applies to hashes only. Block and detect the indicator, but hide it from Activity > Detections. Has a default severity value. prevent - Applies to hashes only. Block the indicator and show it as a detection at the selected severity. detect - Enable detections for the indicator at the selected severity. Possible values are: no_action, allow, prevent_no_ui, prevent, detect. | Required |
| platforms | A comma-separated list of the platforms that the indicator applies to. Possible values are: mac, windows, linux, android, ios. | Required |
| severity | The severity level to apply to this indicator. Required for the prevent and detect actions. Optional for no_action. Possible values are: informational, low, medium, high, critical. | Optional |
| expiration | The datetime the indicator will become inactive (ISO 8601 format, i.e., YYYY-MM-DDThh:mm:ssZ). | Optional |
| source | The source where this indicator originated. This can be used for tracking where this indicator was defined. Limited to 200 characters. | Optional |
| description | A meaningful description of the indicator. Limited to 200 characters. | Optional |
| applied_globally | Whether the indicator is applied globally. Either applied_globally or host_groups must be provided. Possible values are: true, false. | Optional |
| host_groups | A comma-separated list of host group IDs that the indicator applies to. The list of host group IDs can be retrieved by running the ‘cs-falcon-list-host-groups’ command. Either applied_globally or host_groups must be provided. | Optional |
| tags | A comma-separated list of tags to apply to the indicator. | Optional |
| file_name | Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs. | Optional |
| mobile_action | The action to take on mobile devices when a host observes the custom IOC. Note: To use this argument, a mobile platform (android or ios) must be included in the platforms argument. - no_action: The indicator is saved for future use, but no action is taken (no severity required). - allow: The indicator is allowed and not detected (severity does not apply and should not be provided). - detect: The connection is allowed and a detection is generated. - prevent: The indicator is blocked and shown as a detection. Possible values are: no_action, allow, detect, prevent. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator. |
| CrowdStrike.IOC.Severity | string | The severity level to apply to this indicator. |
| CrowdStrike.IOC.Source | string | The source of the IOC. |
| CrowdStrike.IOC.Action | string | Action to take when a host observes the custom IOC. |
| CrowdStrike.IOC.Expiration | string | The datetime when the indicator will expire. |
| CrowdStrike.IOC.Description | string | The description of the IOC. |
| CrowdStrike.IOC.CreatedTime | date | The datetime the IOC was created. |
| CrowdStrike.IOC.CreatedBy | string | The identity of the user/process who created the IOC. |
| CrowdStrike.IOC.ModifiedTime | date | The datetime the indicator was last modified. |
| CrowdStrike.IOC.ModifiedBy | string | The identity of the user/process who last updated the IOC. |
| CrowdStrike.IOC.Tags | Unknown | The tags of the IOC. |
| CrowdStrike.IOC.Platforms | Unknown | The platforms of the IOC. |
| CrowdStrike.IOC.Filename | string | Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs. |
| CrowdStrike.IOC.MobileAction | string | The action to take on mobile devices when a host observes the custom IOC. |
Command Example
!cs-falcon-upload-custom-ioc ioc_type="domain" value="test.domain.com" action="prevent" severity="high" source="Demisto playbook" description="Test ioc" platforms="mac" mobile_action="no_action"
Context Example
{
"CrowdStrike": {
"IOC": {
"CreatedTime": "2020-10-02T13:55:26Z",
"Description": "Test ioc",
"Expiration": "2020-11-01T00:00:00Z",
"ID": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
"ModifiedTime": "2020-10-02T13:55:26Z",
"Action": "prevent",
"MobileAction": "no_action",
"Severity": "high",
"Source": "Demisto playbook",
"Type": "domain",
"Value": "test.domain.com",
"Platforms": ["mac"]
}
}
}
Human Readable Output
Custom IOC was created successfully
CreatedTime Description Expiration ID ModifiedTime Action Severity Source Type Value 2020-10-02T13:55:26Z Test ioc 2020-11-01T00:00:00Z 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r 2020-10-02T13:55:26Z prevent high Demisto playbook domain test.domain.com
cs-falcon-update-custom-ioc
Updates an indicator for CrowdStrike to monitor.
Base Command
cs-falcon-update-custom-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| ioc_id | The ID of the IOC to update. The ID of the IOC can be retrieved by running the ‘cs-falcon-search-custom-iocs’ command. | Required |
| action | Action to take when a host observes the custom IOC. Possible values are: no_action - Save the indicator for future use, but take no action. No severity required. allow - Applies to hashes only. Allow the indicator and do not detect it. Severity does not apply and should not be provided. prevent_no_ui - Applies to hashes only. Block and detect the indicator, but hide it from Activity > Detections. Has a default severity value. prevent - Applies to hashes only. Block the indicator and show it as a detection at the selected severity. detect - Enable detections for the indicator at the selected severity. Possible values are: no_action, allow, prevent_no_ui, prevent, detect. | Optional |
| platforms | A comma-separated list of the platforms that the indicator applies to. Possible values are: mac, windows, linux. | Optional |
| severity | The severity level to apply to this indicator. Required for the prevent and detect actions. Optional for no_action. Possible values are: informational, low, medium, high, critical. | Optional |
| expiration | The datetime the indicator will become inactive (ISO 8601 format, i.e., YYYY-MM-DDThh:mm:ssZ). | Optional |
| source | The source where this indicator originated. This can be used for tracking where this indicator was defined. Limited to 200 characters. | Optional |
| description | A meaningful description of the indicator. Limited to 200 characters. | Optional |
| file_name | Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs. | Optional |
| mobile_action | The action to take on mobile devices when a host observes the custom IOC. Note: To use this argument, a mobile platform (android or ios) must be included in the platforms argument. - no_action: The indicator is saved for future use, but no action is taken (no severity required). - allow: The indicator is allowed and not detected (severity does not apply and should not be provided). - detect: The connection is allowed and a detection is generated. - prevent: The indicator is blocked and shown as a detection. Possible values are: no_action, allow, detect, prevent. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator. |
| CrowdStrike.IOC.Severity | string | The severity level to apply to this indicator. |
| CrowdStrike.IOC.Source | string | The source of the IOC. |
| CrowdStrike.IOC.Action | string | Action to take when a host observes the custom IOC. |
| CrowdStrike.IOC.Expiration | string | The datetime when the indicator will expire. |
| CrowdStrike.IOC.Description | string | The description of the IOC. |
| CrowdStrike.IOC.CreatedTime | date | The datetime the IOC was created. |
| CrowdStrike.IOC.CreatedBy | string | The identity of the user/process who created the IOC. |
| CrowdStrike.IOC.ModifiedTime | date | The datetime the indicator was last modified. |
| CrowdStrike.IOC.ModifiedBy | string | The identity of the user/process who last updated the IOC. |
| CrowdStrike.IOC.Filename | string | Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs. |
| CrowdStrike.IOC.MobileAction | string | The action to take on mobile devices when a host observes the custom IOC. |
Command Example
!cs-falcon-update-custom-ioc ioc_id="4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r" severity="high"
Context Example
{
"CrowdStrike": {
"IOC": {
"CreatedTime": "2020-10-02T13:55:26Z",
"Description": "Test ioc",
"Expiration": "2020-11-01T00:00:00Z",
"ID": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
"ModifiedTime": "2020-10-02T13:55:26Z",
"Action": "prevent",
"MobileAction": "no_action",
"Severity": "high",
"Source": "Demisto playbook",
"Type": "domain",
"Value": "test.domain.com"
}
}
}
Human Readable Output
Custom IOC was updated successfully
CreatedTime Description Expiration ID ModifiedTime Action Severity Source Type Value 2020-10-02T13:55:26Z Test ioc 2020-11-01T00:00:00Z 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r 2020-10-02T13:55:26Z prevent high Demisto playbook domain test.domain.com
cs-falcon-delete-custom-ioc
Deletes a monitored indicator.
Base Command
cs-falcon-delete-custom-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| ioc_id | The ID of the IOC to delete. The ID of the IOC can be retrieved by running the ‘cs-falcon-search-custom-iocs’ command. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-delete-custom-ioc ioc_id="4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r"
Human Readable Output
Custom IOC 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r was successfully deleted.
cs-falcon-device-count-ioc
The number of hosts that observed the provided IOC.
Base Command
cs-falcon-device-count-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The IOC type. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. | Required |
| value | The string representation of the indicator. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator (type:value). |
| CrowdStrike.IOC.DeviceCount | number | The number of devices the IOC ran on. |
Command Example
!cs-falcon-device-count-ioc type="domain" value="value"
Context Example
{
"CrowdStrike": {
"IOC": {
"DeviceCount": 1,
"ID": "domain:value",
"Type": "domain",
"Value": "value"
}
}
}
Human Readable Output
Indicator of Compromise domain:value device count: 1
cs-falcon-processes-ran-on
Get processes associated with a given IOC.
Base Command
cs-falcon-processes-ran-on
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The IOC type. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. | Required |
| value | The string representation of the indicator. | Required |
| device_id | The device ID to check against. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator (type:value). |
| CrowdStrike.IOC.Process.ID | number | The processes IDs associated with the given IOC. |
| CrowdStrike.IOC.Process.DeviceID | number | The device the process ran on. |
Command Example
!cs-falcon-processes-ran-on device_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 type=domain value=value
Context Example
{
"CrowdStrike": {
"IOC": {
"ID": "domain:value",
"Process": {
"DeviceID": "pid",
"ID": [
"pid:pid:650164094720"
]
},
"Type": "domain",
"Value": "value"
}
}
}
Human Readable Output
Processes with custom IOC domain:value on device device_id
Process ID pid:pid:650164094720
cs-falcon-process-details
Retrieves the details of a process, according to the process ID that is running or that previously ran.
Base Command
cs-falcon-process-details
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of process IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Process.process_id | String | The process ID. |
| CrowdStrike.Process.process_id_local | String | Local ID of the process. |
| CrowdStrike.Process.device_id | String | The device the process ran on. |
| CrowdStrike.Process.file_name | String | The path of the file that ran the process. |
| CrowdStrike.Process.command_line | String | The command line command execution. |
| CrowdStrike.Process.start_timestamp_raw | String | The start datetime of the process in Unix time format. For example: 132460167512852140. |
| CrowdStrike.Process.start_timestamp | String | The start datetime of the process in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.Process.stop_timestamp_raw | Date | The stop datetime of the process in Unix time format. For example: 132460167512852140. |
| CrowdStrike.Process.stop_timestamp | Date | The stop datetime of the process in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
Command Example
!cs-falcon-process-details ids="pid:pid:pid"
Context Example
{
"CrowdStrike": {
"Process": {
"command_line": "\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\"",
"device_id": "deviceId",
"file_name": "\\Device\\HarddiskVolume1\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
"process_id": "deviceId:pid",
"process_id_local": "pid",
"start_timestamp": "2020-10-01T09:05:51Z",
"start_timestamp_raw": "132460167512852140",
"stop_timestamp": "2020-10-02T06:43:45Z",
"stop_timestamp_raw": "132460946259334768"
}
}
}
Human Readable Output
Details for process: pid:pid:pid
command_line device_id file_name process_id process_id_local start_timestamp start_timestamp_raw stop_timestamp stop_timestamp_raw “C:\Program Files (x86)\Google\Chrome\Application\chrome.exe” deviceId \Device\HarddiskVolume1\Program Files (x86)\Google\Chrome\Application\chrome.exe device_id:pid pid 2020-10-01T09:05:51Z 132460167512852140 2020-10-02T06:43:45Z 132460946259334768
cs-falcon-device-ran-on
Returns a list of device IDs an indicator ran on.
Base Command
cs-falcon-device-ran-on
Input
| Argument Name | Description | Required |
|---|---|---|
| type | The type of indicator. Possible values are: domain, ipv4, ipv6, md5, sha1, sha256. | Required |
| value | The string representation of the indicator. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.DeviceID | string | Device IDs an indicator ran on. |
Command Example
!cs-falcon-device-ran-on type=domain value=value
Context Example
{
"CrowdStrike": {
"DeviceID": [
"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
]
}
}
Human Readable Output
Devices that encountered the IOC domain:value
Device ID a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
cs-falcon-list-detection-summaries
Lists detection summaries.
Base Command
cs-falcon-list-detection-summaries
Input
| Argument Name | Description | Required |
|---|---|---|
| fetch_query | The query used to filter the results. | Optional |
| ids | A comma-separated list of detection IDs. For example, ldt:1234:1234,ldt:5678:5678. If you use this argument, the fetch_query argument will be ignored. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Detections.cid | String | The organization’s customer ID (CID). |
| CrowdStrike.Detections.created_timestamp | Date | The datetime the detection occurred in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.Detections.detection_id | String | The ID of the detection. |
| CrowdStrike.Detections.device.device_id | String | The device ID as seen by CrowdStrike Falcon. |
| CrowdStrike.Detections.device.cid | String | The CrowdStrike Customer ID (CID) to which the device belongs. |
| CrowdStrike.Detections.device.agent_load_flags | String | The CrowdStrike Falcon agent load flags. |
| CrowdStrike.Detections.device.agent_local_time | Date | The local time of the sensor. |
| CrowdStrike.Detections.device.agent_version | String | The version of the agent that the device is running. For example: 5.32.11406.0. |
| CrowdStrike.Detections.device.bios_manufacturer | String | The BIOS manufacturer. |
| CrowdStrike.Detections.device.bios_version | String | The device’s BIOS version. |
| CrowdStrike.Detections.device.config_id_base | String | The base of the sensor that the device is running. |
| CrowdStrike.Detections.device.config_id_build | String | The version of the sensor that the device is running. For example: 11406. |
| CrowdStrike.Detections.device.config_id_platform | String | The platform ID of the sensor that the device is running. |
| CrowdStrike.Detections.device.external_ip | String | The external IP address of the device. |
| CrowdStrike.Detections.device.hostname | String | The hostname of the device. |
| CrowdStrike.Detections.device.first_seen | Date | The datetime the host was first seen by CrowdStrike Falcon. |
| CrowdStrike.Detections.device.last_seen | Date | The datetime the host was last seen by CrowdStrike Falcon. |
| CrowdStrike.Detections.device.local_ip | String | The local IP address of the device. |
| CrowdStrike.Detections.device.mac_address | String | The MAC address of the device. |
| CrowdStrike.Detections.device.major_version | String | The major version of the operating system. |
| CrowdStrike.Detections.device.minor_version | String | The minor version of the operating system. |
| CrowdStrike.Detections.device.os_version | String | The operating system of the device. |
| CrowdStrike.Detections.device.platform_id | String | The platform ID of the device that runs the sensor. |
| CrowdStrike.Detections.device.platform_name | String | The platform name of the device. |
| CrowdStrike.Detections.device.product_type_desc | String | The value indicating the product type. For example, 1 = Workstation, 2 = Domain Controller, 3 = Server. |
| CrowdStrike.Detections.device.status | String | The containment status of the machine. Possible values are: “normal”, “containment_pending”, “contained”, and “lift_containment_pending”. |
| CrowdStrike.Detections.device.system_manufacturer | String | The system manufacturer of the device. |
| CrowdStrike.Detections.device.system_product_name | String | The product name of the system. |
| CrowdStrike.Detections.device.modified_timestamp | Date | The datetime the device was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.Detections.behaviors.device_id | String | The ID of the device associated with the behavior. |
| CrowdStrike.Detections.behaviors.timestamp | Date | The datetime the behavior detection occurred in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.Detections.behaviors.filename | String | The filename of the triggering process. |
| CrowdStrike.Detections.behaviors.alleged_filetype | String | The file extension of the behavior’s filename. |
| CrowdStrike.Detections.behaviors.cmdline | String | The command line of the triggering process. |
| CrowdStrike.Detections.behaviors.scenario | String | The name of the scenario the behavior belongs to. |
| CrowdStrike.Detections.behaviors.objective | String | The name of the objective associated with the behavior. |
| CrowdStrike.Detections.behaviors.tactic | String | The name of the tactic associated with the behavior. |
| CrowdStrike.Detections.behaviors.technique | String | The name of the technique associated with the behavior. |
| CrowdStrike.Detections.behaviors.severity | Number | The severity rating for the behavior. The value can be any integer between 1-100. |
| CrowdStrike.Detections.behaviors.confidence | Number | The true positive confidence rating for the behavior. The value can be any integer between 1-100. |
| CrowdStrike.Detections.behaviors.ioc_type | String | The type of the triggering IOC. Possible values are: “hash_sha256”, “hash_md5”, “domain”, “filename”, “registry_key”, “command_line”, and “behavior”. |
| CrowdStrike.Detections.behaviors.ioc_value | String | The IOC value. |
| CrowdStrike.Detections.behaviors.user_name | String | The user name. |
| CrowdStrike.Detections.behaviors.user_id | String | The Security Identifier (SID) of the user in Windows. |
| CrowdStrike.Detections.behaviors.control_graph_id | String | The behavior hit key for the Threat Graph API. |
| CrowdStrike.Detections.behaviors.triggering_process_graph_id | String | The ID of the process that triggered the behavior detection. |
| CrowdStrike.Detections.behaviors.sha256 | String | The SHA256 of the triggering process. |
| CrowdStrike.Detections.behaviors.md5 | String | The MD5 hash of the triggering process. |
| CrowdStrike.Detections.behaviors.parent_details.parent_sha256 | String | The SHA256 hash of the parent process. |
| CrowdStrike.Detections.behaviors.parent_details.parent_md5 | String | The MD5 hash of the parent process. |
| CrowdStrike.Detections.behaviors.parent_details.parent_cmdline | String | The command line of the parent process. |
| CrowdStrike.Detections.behaviors.parent_details.parent_process_graph_id | String | The process graph ID of the parent process. |
| CrowdStrike.Detections.behaviors.pattern_disposition | Number | The pattern associated with the action performed on the behavior. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.indicator | Boolean | Whether the detection behavior is similar to an indicator. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.detect | Boolean | Whether this behavior is detected. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.inddet_mask | Boolean | Whether this behavior is an inddet mask. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.sensor_only | Boolean | Whether this detection is sensor only. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.rooting | Boolean | Whether this behavior is rooting. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.kill_process | Boolean | Whether this detection kills the process. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.kill_subprocess | Boolean | Whether this detection kills the subprocess. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.quarantine_machine | Boolean | Whether this detection was on a quarantined machine. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.quarantine_file | Boolean | Whether this detection was on a quarantined file. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.policy_disabled | Boolean | Whether this policy is disabled. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.kill_parent | Boolean | Whether this detection kills the parent process. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.operation_blocked | Boolean | Whether the operation is blocked. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.process_blocked | Boolean | Whether the process is blocked. |
| CrowdStrike.Detections.behaviors.pattern_disposition_details.registry_operation_blocked | Boolean | Whether the registry operation is blocked. |
| CrowdStrike.Detections.email_sent | Boolean | Whether an email is sent about this detection. |
| CrowdStrike.Detections.show_in_ui | Boolean | Whether the detection displays in the UI. |
| CrowdStrike.Detections.status | String | The status of the detection. |
| CrowdStrike.Detections.hostinfo.domain | String | The domain of the Active Directory. |
| CrowdStrike.Detections.seconds_to_triaged | Number | The amount of time it took to move a detection from “new” to “in_progress”. |
| CrowdStrike.Detections.seconds_to_resolved | Number | The amount of time it took to move a detection from new to a resolved state (“true_positive”, “false_positive”, and “ignored”). |
Command Example
#### Context Example
```json
{
"CrowdStrike": {
"Detections": [
{
"behaviors": [
{
"alleged_filetype": "exe",
"behavior_id": "10197",
"cmdline": "choice /m crowdstrike_sample_detection",
"confidence": 80,
"control_graph_id": "ctg:ctg:ctg",
"device_id": "deviceid",
"display_name": "",
"filename": "choice.exe",
"filepath": "",
"ioc_description": "",
"ioc_source": "",
"ioc_type": "",
"ioc_value": "",
"md5": "md5",
"objective": "Falcon Detection Method",
"parent_details": {
"parent_cmdline": "\"C:\\Windows\\system32\\cmd.exe\" ",
"parent_md5": "md5",
"parent_process_graph_id": "pid:pid:pid",
"parent_sha256": "sha256"
},
"pattern_disposition": 0,
"pattern_disposition_details": {
"bootup_safeguard_enabled": false,
"critical_process_disabled": false,
"detect": false,
"fs_operation_blocked": false,
"inddet_mask": false,
"indicator": false,
"kill_parent": false,
"kill_process": false,
"kill_subprocess": false,
"operation_blocked": false,
"policy_disabled": false,
"process_blocked": false,
"quarantine_file": false,
"quarantine_machine": false,
"registry_operation_blocked": false,
"rooting": false,
"sensor_only": false
},
"scenario": "suspicious_activity",
"severity": 30,
"sha256": "sha256",
"tactic": "Malware",
"tactic_id": "",
"technique": "Malicious File",
"technique_id": "",
"template_instance_id": "382",
"timestamp": "2020-07-06T08:10:44Z",
"triggering_process_graph_id": "pid:pid:pid",
"user_id": "user_id",
"user_name": "user_name"
}
],
"behaviors_processed": [
"pid:pid:pid:10197"
],
"cid": "cid",
"created_timestamp": "2020-07-06T08:10:55.538668036Z",
"detection_id": "ldt:ldt:ldt",
"device": {
"agent_load_flags": "0",
"agent_local_time": "2020-07-02T01:42:07.640Z",
"agent_version": "5.32.11406.0",
"bios_manufacturer": "Google",
"bios_version": "Google",
"cid": "cid",
"config_id_base": "id",
"config_id_build": "id",
"config_id_platform": "3",
"device_id": "device_id",
"external_ip": "external_ip",
"first_seen": "2020-02-10T12:40:18Z",
"hostname": "FALCON-CROWDSTR",
"last_seen": "2020-07-06T07:59:12Z",
"local_ip": "local_ip",
"mac_address": "mac_address",
"major_version": "major_version",
"minor_version": "minor_version",
"modified_timestamp": "modified_timestamp",
"os_version": "os_version",
"platform_id": "platform_id",
"platform_name": "platform_name",
"product_type": "product_type",
"product_type_desc": "product_type_desc",
"status": "status",
"system_manufacturer": "system_manufacturer",
"system_product_name": "system_product_name"
},
"email_sent": false,
"first_behavior": "2020-07-06T08:10:44Z",
"hostinfo": {
"domain": ""
},
"last_behavior": "2020-07-06T08:10:44Z",
"max_confidence": 80,
"max_severity": 30,
"max_severity_displayname": "Low",
"seconds_to_resolved": 0,
"seconds_to_triaged": 0,
"show_in_ui": true,
"status": "new"
}
]
}
}
Human Readable Output
CrowdStrike Detections
detection_id created_time status max_severity ldt:ldt:ldt 2020-07-06T08:10:55.538668036Z new Low
endpoint
Returns information about an endpoint. Does not support regex.
Base Command
endpoint
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The endpoint ID. | Optional |
| ip | The endpoint IP address. | Optional |
| hostname | The endpoint hostname. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Endpoint.Hostname | String | The endpoint’s hostname. |
| Endpoint.OS | String | The endpoint’s operation system. |
| Endpoint.IPAddress | String | The endpoint’s IP address. |
| Endpoint.ID | String | The endpoint’s ID. |
| Endpoint.Status | String | The endpoint’s status. |
| Endpoint.IsIsolated | String | The endpoint’s isolation status. |
| Endpoint.MACAddress | String | The endpoint’s MAC address. |
| Endpoint.Vendor | String | The integration name of the endpoint vendor. |
| Endpoint.OSVersion | String | The endpoint’s operation system version. |
Command Example
!endpoint id=15dbb9d5fe9f61eb46e829d986
Context Example
{
"Endpoint":
{
"Hostname": "Hostname",
"ID": "15dbb9d5fe9f61eb46e829d986",
"IPAddress": "1.1.1.1",
"OS": "Windows",
"OSVersion": "Windows Server 2019",
"Status": "Online",
"Vendor": "CrowdStrike Falcon",
"MACAddress": "1-1-1-1"
}
}
Human Readable Output
Endpoints
ID IPAddress OS OSVersion Hostname Status MACAddress Vendor a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 1.1.1.1 Windows Windows Server 2019 Hostname Online 1-1-1-1 CrowdStrike Falcon \n”
cs-falcon-create-host-group
Create a host group.
Base Command
cs-falcon-create-host-group
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of the host. | Required |
| group_type | The group type of the group. Possible values are: static, dynamic. | Required |
| description | The description of the host. | Optional |
| assignment_rule | The assignment rule. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.HostGroup.id | String | The ID of the host group. |
| CrowdStrike.HostGroup.group_type | String | The group type of the host group. |
| CrowdStrike.HostGroup.name | String | The name of the host group. |
| CrowdStrike.HostGroup.description | String | The description of the host group. |
| CrowdStrike.HostGroup.created_by | String | The client that created the host group. |
| CrowdStrike.HostGroup.created_timestamp | Date | The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.HostGroup.modified_by | String | The client that modified the host group. |
| CrowdStrike.HostGroup.modified_timestamp | Date | The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
Command Example
!cs-falcon-create-host-group name="test_name_1" description="test_description" group_type=static
Context Example
{
"CrowdStrike": {
"HostGroup": {
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-25T08:02:02.060242909Z",
"description": "test_description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-25T08:02:02.060242909Z",
"name": "test_name_1"
}
}
}
Human Readable Output
Results
created_by created_timestamp description group_type id modified_by modified_timestamp name api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:02.060242909Z test_description static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:02.060242909Z test_name_1
cs-falcon-list-host-groups
List the available host groups.
Base Command
cs-falcon-list-host-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | The query by which to filter the devices that belong to the host group. | Optional |
| offset | Page offset. | Optional |
| limit | Maximum number of results on a page. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.HostGroup.id | String | The ID of the host group. |
| CrowdStrike.HostGroup.group_type | String | The group type of the host group. |
| CrowdStrike.HostGroup.name | String | The name of the host group. |
| CrowdStrike.HostGroup.description | String | The description of the host group. |
| CrowdStrike.HostGroup.created_by | String | The client that created the host group. |
| CrowdStrike.HostGroup.created_timestamp | Date | The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.HostGroup.modified_by | String | The client that modified the host group. |
| CrowdStrike.HostGroup.modified_timestamp | Date | The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
Command Example
#### Context Example
```json
{
"CrowdStrike": {
"HostGroup": [
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:23.765624811Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:23.765624811Z",
"name": "InnerServicesModuleMon Aug 23 2021"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:25.506030441Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:25.506030441Z",
"name": "Rasterize_default_instanceMon Aug 23 2021"
},
{
"assignment_rule": "device_id:[''],hostname:['','FALCON-CROWDSTR']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-07-27T12:34:59.13917402Z",
"description": "",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-07-27T12:34:59.13917402Z",
"name": "Static by id group test"
},
{
"assignment_rule": "device_id:[],hostname:[]",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-07-27T12:24:18.364057533Z",
"description": "Group test",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-07-27T12:24:18.364057533Z",
"name": "Static group test"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:26.069515348Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:26.069515348Z",
"name": "ad-loginMon Aug 23 2021"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:25.556897468Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:25.556897468Z",
"name": "ad-queryMon Aug 23 2021"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:23.737307612Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:23.737307612Z",
"name": "d2Mon Aug 23 2021"
},
{
"created_by": "someone@email.com",
"created_timestamp": "2021-07-27T12:27:43.503021999Z",
"description": "dhfh",
"group_type": "staticByID",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "someone@email.com",
"modified_timestamp": "2021-07-27T12:27:43.503021999Z",
"name": "ddfxgh"
},
{
"assignment_rule": "device.hostname:'FALCON-CROWDSTR'",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-07-27T12:46:39.058352326Z",
"description": "",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-07-27T12:46:39.058352326Z",
"name": "dynamic 1 group test"
},
{
"assignment_rule": "lkjlk:'FalconGroupingTags/example_tag'",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T13:12:56.338590022Z",
"description": "",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T13:12:56.338590022Z",
"name": "dynamic 13523 group test"
},
{
"assignment_rule": "lkjlk:'FalconGroupingTags/example_tag'",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-07-27T14:02:05.538065349Z",
"description": "",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-07-27T14:02:05.538065349Z",
"name": "dynamic 1353 group test"
},
{
"assignment_rule": "tags:'FalconGroupingTags/example_tag'",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-07-27T12:41:33.127997409Z",
"description": "",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-07-27T12:41:33.127997409Z",
"name": "dynamic 2 group test"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:23.7402217Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:23.7402217Z",
"name": "fcm_default_instanceMon Aug 23 2021"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-11T09:55:23.801049103Z",
"description": "ilan test",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-11T09:55:23.801049103Z",
"name": "ilan"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-12T11:24:51.434863056Z",
"description": "ilan test",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-12T11:24:51.434863056Z",
"name": "ilan 2"
},
{
"assignment_rule": "device_id:[''],hostname:['FALCON-CROWDSTR']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-12T11:55:57.943490809Z",
"description": "ilan test",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-12T11:55:57.943490809Z",
"name": "ilan 23"
},
{
"assignment_rule": "",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-17T11:28:39.855075106Z",
"description": "after change",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T09:26:15.351650252Z",
"name": "ilan 2345"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-17T11:58:42.453661998Z",
"description": "ilan test",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-17T11:58:42.453661998Z",
"name": "ilan 23e"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-11T13:54:59.695821727Z",
"description": "",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-11T13:54:59.695821727Z",
"name": "ilan test 2"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-12T10:56:49.2127345Z",
"description": "ilan test",
"group_type": "dynamic",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-12T11:35:35.76509212Z",
"name": "ilan2"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T14:35:23.766284685Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T14:35:23.766284685Z",
"name": "splunkMon Aug 23 2021"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:09:15.36414377Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:09:15.36414377Z",
"name": "test_1629731353498"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:12:20.69203954Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:12:20.69203954Z",
"name": "test_1629731538458"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:14:20.650781714Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:14:23.026511269Z",
"name": "test_16297316587261629731658726"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:18:53.896505566Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:18:56.2598933Z",
"name": "test_16297319320381629731932038"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:19:51.91067257Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:19:54.269898808Z",
"name": "test_16297319902371629731990237"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:25:42.99601887Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:25:42.99601887Z",
"name": "test_1629732339973"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:26:12.280379354Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:26:14.973676462Z",
"name": "test_16297323698941629732369894"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:26:58.717706381Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:27:01.648623079Z",
"name": "test_16297324168771629732416877"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:28:18.674512647Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:28:21.781563212Z",
"name": "test_16297324965761629732496576"
},
{
"assignment_rule": "device_id:[''],hostname:['FALCON-CROWDSTR','INSTANCE-1','falcon-crowdstrike-sensor-centos7']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:31:41.142748214Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:31:43.800147323Z",
"name": "test_16297326990981629732699098"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:34:20.195778795Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:34:23.212828317Z",
"name": "test_16297328579781629732857978"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:34:55.837119719Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:34:58.490114093Z",
"name": "test_16297328938791629732893879"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-23T15:37:42.911344704Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-23T15:37:45.620464598Z",
"name": "test_16297330605301629733060530"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-24T07:05:55.813475476Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-24T07:05:58.805702883Z",
"name": "test_16297887501421629788750142"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-24T07:07:30.422517324Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-24T07:07:34.291988227Z",
"name": "test_16297888481381629788848138"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-24T08:03:15.522772079Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-24T08:03:18.622015517Z",
"name": "test_16297921932741629792193274"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:09:52.379925975Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:09:52.379925975Z",
"name": "test_1629967211800"
},
{
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T12:34:36.934507422Z",
"description": "description",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T12:34:36.934507422Z",
"name": "test_162996721180000"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T08:46:09.996065663Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T08:46:11.572092204Z",
"name": "test_16299675695531629967569553"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T08:53:15.35181954Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T08:53:17.041535905Z",
"name": "test_16299679949831629967994983"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T08:59:52.639696743Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T08:59:54.538170036Z",
"name": "test_16299683923121629968392312"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:06:21.891707157Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:06:23.846219163Z",
"name": "test_16299687814871629968781487"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:12:53.982989Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:12:55.571265187Z",
"name": "test_16299691732871629969173287"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:17:58.206157753Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:17:59.659515838Z",
"name": "test_16299694779051629969477905"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:19:23.276267291Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:19:25.318976241Z",
"name": "test_16299695623981629969562398"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:26:22.538367707Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:26:25.085214782Z",
"name": "test_16299699813871629969981387"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:33:46.303790983Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:33:48.288311235Z",
"name": "test_16299704254441629970425444"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T09:55:09.157561612Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T09:55:10.741852436Z",
"name": "test_16299717065381629971706538"
},
{
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-26T10:02:50.175530821Z",
"description": "description2",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-26T10:02:52.026307768Z",
"name": "test_16299721694081629972169408"
}
]
}
}
Human Readable Output
Results
assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name device_id:[’’],hostname:[’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-26T10:02:50.175530821Z description2 static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-26T10:02:52.026307768Z test_16299721694081629972169408
cs-falcon-delete-host-groups
Deletes the requested host groups.
Base Command
cs-falcon-delete-host-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| host_group_id | A comma-separated list of the IDs of the host groups to be deleted. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-delete-host-groups host_group_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1,a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Human Readable Output
host group id a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 deleted successfully
host group id a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 deleted successfully
cs-falcon-update-host-group
Updates a host group.
Base Command
cs-falcon-update-host-group
Input
| Argument Name | Description | Required |
|---|---|---|
| host_group_id | The ID of the host group. | Required |
| name | The name of the host group. | Optional |
| description | The description of the host group. | Optional |
| assignment_rule | The assignment rule. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.HostGroup.id | String | The ID of the host group. |
| CrowdStrike.HostGroup.group_type | String | The group type of the host group. |
| CrowdStrike.HostGroup.name | String | The name of the host group. |
| CrowdStrike.HostGroup.description | String | The description of the host group. |
| CrowdStrike.HostGroup.created_by | String | The client that created the host group. |
| CrowdStrike.HostGroup.created_timestamp | Date | The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.HostGroup.modified_by | String | The client that modified the host group. |
| CrowdStrike.HostGroup.modified_timestamp | Date | The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
Command Example
!cs-falcon-update-host-group host_group_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 name="test_name_update_1" description="test_description_update"
Context Example
{
"CrowdStrike": {
"HostGroup": {
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-22T07:48:35.111070562Z",
"description": "test_description_update",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-25T08:02:05.295663156Z",
"name": "test_name_update_1"
}
}
}
Human Readable Output
Results
assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name device_id:[’’],hostname:[’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-22T07:48:35.111070562Z test_description_update static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:05.295663156Z test_name_update_1
cs-falcon-list-host-group-members
Gets the list of host group members.
Base Command
cs-falcon-list-host-group-members
Input
| Argument Name | Description | Required |
|---|---|---|
| host_group_id | The ID of the host group. | Optional |
| filter | The query to filter the devices that belong to the host group. | Optional |
| offset | Page offset. | Optional |
| limit | The maximum number of results on a page. Default is 50. | Optional |
| sort | The property to sort by (e.g., status.desc or hostname.asc). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Device.ID | String | The ID of the device. |
| CrowdStrike.Device.LocalIP | String | The local IP address of the device. |
| CrowdStrike.Device.ExternalIP | String | The external IP address of the device. |
| CrowdStrike.Device.Hostname | String | The hostname of the device. |
| CrowdStrike.Device.OS | String | The operating system of the device. |
| CrowdStrike.Device.MacAddress | String | The MAC address of the device. |
| CrowdStrike.Device.FirstSeen | String | The first time the device was seen. |
| CrowdStrike.Device.LastSeen | String | The last time the device was seen. |
| CrowdStrike.Device.Status | String | The device status. |
Command Example
#### Context Example
```json
{
"CrowdStrike": {
"Device": [
{
"ExternalIP": "35.224.136.145",
"FirstSeen": "2021-08-12T16:13:26Z",
"Hostname": "FALCON-CROWDSTR",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"LastSeen": "2021-08-23T04:59:48Z",
"LocalIP": "10.128.0.21",
"MacAddress": "42-01-0a-80-00-15",
"OS": "Windows Server 2019",
"Status": "normal"
},
{
"ExternalIP": "35.224.136.145",
"FirstSeen": "2020-02-10T12:40:18Z",
"Hostname": "FALCON-CROWDSTR",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"LastSeen": "2021-08-25T07:42:47Z",
"LocalIP": "10.128.0.7",
"MacAddress": "42-01-0a-80-00-07",
"OS": "Windows Server 2019",
"Status": "contained"
},
{
"ExternalIP": "35.224.136.145",
"FirstSeen": "2021-08-23T05:04:41Z",
"Hostname": "INSTANCE-1",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"LastSeen": "2021-08-25T07:49:06Z",
"LocalIP": "10.128.0.20",
"MacAddress": "42-01-0a-80-00-14",
"OS": "Windows Server 2019",
"Status": "normal"
},
{
"ExternalIP": "35.224.136.145",
"FirstSeen": "2021-08-11T13:57:29Z",
"Hostname": "INSTANCE-1",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"LastSeen": "2021-08-23T04:45:37Z",
"LocalIP": "10.128.0.20",
"MacAddress": "42-01-0a-80-00-14",
"OS": "Windows Server 2019",
"Status": "normal"
},
{
"ExternalIP": "35.224.136.145",
"FirstSeen": "2021-08-08T11:33:21Z",
"Hostname": "falcon-crowdstrike-sensor-centos7",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"LastSeen": "2021-08-25T07:50:47Z",
"LocalIP": "10.128.0.19",
"MacAddress": "42-01-0a-80-00-13",
"OS": "CentOS 7.9",
"Status": "normal"
}
]
}
}
Human Readable Output
Devices
ID External IP Local IP Hostname OS Mac Address First Seen Last Seen Status a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 35.224.136.145 10.128.0.19 falcon-crowdstrike-sensor-centos7 CentOS 7.9 42-01-0a-80-00-13 2021-08-08T11:33:21Z 2021-08-25T07:50:47Z normal
cs-falcon-add-host-group-members
Add host group members.
Base Command
cs-falcon-add-host-group-members
Input
| Argument Name | Description | Required |
|---|---|---|
| host_group_id | The ID of the host group. | Required |
| host_ids | A comma-separated list of host agent IDs to run commands. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.HostGroup.id | String | The ID of the host group. |
| CrowdStrike.HostGroup.group_type | String | The group type of the host group. |
| CrowdStrike.HostGroup.name | String | The name of the host group. |
| CrowdStrike.HostGroup.description | String | The description of the host group. |
| CrowdStrike.HostGroup.created_by | String | The client that created the host group. |
| CrowdStrike.HostGroup.created_timestamp | Date | The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.HostGroup.modified_by | String | The client that modified the host group. |
| CrowdStrike.HostGroup.modified_timestamp | Date | The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
Command Example
!cs-falcon-add-host-group-members host_group_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" host_ids="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
Context Example
{
"CrowdStrike": {
"HostGroup": {
"assignment_rule": "device_id:[''],hostname:['falcon-crowdstrike-sensor-centos7','']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-22T07:48:35.111070562Z",
"description": "test_description_update",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-25T08:02:05.295663156Z",
"name": "test_name_update_1"
}
}
}
Human Readable Output
Results
assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name device_id:[’’],hostname:[‘falcon-crowdstrike-sensor-centos7’,’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-22T07:48:35.111070562Z test_description_update static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:05.295663156Z test_name_update_1
cs-falcon-remove-host-group-members
Remove host group members.
Base Command
cs-falcon-remove-host-group-members
Input
| Argument Name | Description | Required |
|---|---|---|
| host_group_id | The ID of the host group. | Required |
| host_ids | A comma-separated list of host agent IDs to run commands. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.HostGroup.id | String | The ID of the host group. |
| CrowdStrike.HostGroup.group_type | String | The group type of the host group. |
| CrowdStrike.HostGroup.name | String | The name of the host group. |
| CrowdStrike.HostGroup.description | String | The description of the host group. |
| CrowdStrike.HostGroup.created_by | String | The client that created the host group. |
| CrowdStrike.HostGroup.created_timestamp | Date | The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
| CrowdStrike.HostGroup.modified_by | String | The client that modified the host group. |
| CrowdStrike.HostGroup.modified_timestamp | Date | The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z. |
Command Example
!cs-falcon-remove-host-group-members host_group_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" host_ids="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
Context Example
{
"CrowdStrike": {
"HostGroup": {
"assignment_rule": "device_id:[''],hostname:['']",
"created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_timestamp": "2021-08-22T07:48:35.111070562Z",
"description": "test_description_update",
"group_type": "static",
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"modified_timestamp": "2021-08-25T08:02:05.295663156Z",
"name": "test_name_update_1"
}
}
}
Human Readable Output
Results
assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name device_id:[’’],hostname:[’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-22T07:48:35.111070562Z test_description_update static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:05.295663156Z test_name_update_1
cs-falcon-batch-upload-custom-ioc
Uploads a batch of indicators.
Base Command
cs-falcon-batch-upload-custom-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| multiple_indicators_json | A JSON object with a list of CrowdStrike Falcon indicators to upload. | Required |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Default is 180. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOC.Type | string | The type of the IOC. |
| CrowdStrike.IOC.Value | string | The string representation of the indicator. |
| CrowdStrike.IOC.ID | string | The full ID of the indicator. |
| CrowdStrike.IOC.Severity | string | The severity level to apply to this indicator. |
| CrowdStrike.IOC.Source | string | The source of the IOC. |
| CrowdStrike.IOC.Action | string | The action to take when a host observes the custom IOC. |
| CrowdStrike.IOC.Expiration | string | The datetime the indicator will expire. |
| CrowdStrike.IOC.Description | string | The description of the IOC. |
| CrowdStrike.IOC.CreatedTime | date | The datetime the IOC was created. |
| CrowdStrike.IOC.CreatedBy | string | The identity of the user/process who created the IOC. |
| CrowdStrike.IOC.ModifiedTime | date | The datetime the indicator was last modified. |
| CrowdStrike.IOC.ModifiedBy | string | The identity of the user/process who last updated the IOC. |
| CrowdStrike.IOC.Tags | Unknown | The tags of the IOC. |
| CrowdStrike.IOC.Platforms | Unknown | The platforms of the IOC. |
| CrowdStrike.IOC.MobileAction | string | The action to take on mobile devices when a host observes the custom IOC. |
Command Example
!cs-falcon-batch-upload-custom-ioc multiple_indicators_json=`[{"description": "test", "expiration": "2022-02-17T13:47:57Z", "type": "ipv4", "severity": "Informational", "value": "1.1.8.9", "action": "no_action", "platforms": ["mac"], "source": "Cortex", "applied_globally": true}]`
Context Example
{
"CrowdStrike": {
"IOC": {
"Action": "no_action",
"CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"CreatedTime": "2022-02-16T17:17:25.992164453Z",
"Description": "test",
"Expiration": "2022-02-17T13:47:57Z",
"ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ModifiedTime": "2022-02-16T17:17:25.992164453Z",
"Platforms": [
"mac"
],
"Severity": "informational",
"Source": "Cortex",
"Type": "ipv4",
"Value": "1.1.8.9"
}
}
}
Human Readable Output
Custom IOC 1.1.8.9 was created successfully
Action CreatedBy CreatedTime Description Expiration ID ModifiedBy ModifiedTime Platforms Severity Source Type Value no_action a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z test 2022-02-17T13:47:57Z “a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z mac informational Cortex ipv4 1.1.8.9
cs-falcon-rtr-kill-process
Execute an active responder kill command on a single host.
Base Command
cs-falcon-rtr-kill-process
Input
| Argument Name | Description | Required |
|---|---|---|
| host_id | The host ID to kill the given process for. | Required |
| process_ids | A comma-separated list of process IDs to kill. | Required |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.kill.ProcessID | String | The process ID that was killed. |
| CrowdStrike.Command.kill.Error | String | The error message raised if the command failed. |
| CrowdStrike.Command.kill.HostID | String | The host ID. |
Command Example
!cs-falcon-rtr-kill-process host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 process_ids=5260,123
Context Example
{
"CrowdStrike": {
"Command": {
"kill": [
{
"Error": "Cannot find a process with the process identifier 123.",
"HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ProcessID": "123"
},
{
"Error": "Success",
"HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"ProcessID": "5260"
}
]
}
}
}
Human Readable Output
CrowdStrike Falcon kill command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
|ProcessID|Error|
|—|—|
| 123 | Cannot find a process with the process identifier 123. |
| 5260 | Success |
Note: you don’t see the following IDs in the results as the request was failed for them.
ID 123 failed as it was not found.
cs-falcon-rtr-remove-file
Batch executes an RTR active-responder remove file across the hosts mapped to the given batch ID.
Base Command
cs-falcon-rtr-remove-file
Input
| Argument Name | Description | Required |
|---|---|---|
| host_ids | A comma-separated list of the hosts IDs to remove the file for. | Required |
| file_path | The path to a file or a directory to remove. | Required |
| os | The operating system of the hosts given. Since the remove command is different in each operating system, you can choose only one operating system. Possible values are: Windows, Linux, Mac. | Required |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.rm.HostID | String | The host ID. |
| CrowdStrike.Command.rm.Error | String | The error message raised if the command failed. |
Command Example
!cs-falcon-rtr-remove-file file_path="c:\\testfolder" host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 os=Windows
Context Example
{
"CrowdStrike": {
"Command": {
"rm": {
"Error": "Success",
"HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
}
}
}
Human Readable Output
CrowdStrike Falcon rm over the file: c:\testfolder
HostID Error a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 Success
cs-falcon-rtr-list-processes
Executes an RTR active-responder ps command to get a list of active processes across the given host.
Base Command
cs-falcon-rtr-list-processes
Input
| Argument Name | Description | Required |
|---|---|---|
| host_id | The host ID to get the processes list from. | Required |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.ps.Filename | String | The name of the result file to be returned. |
Command Example
!cs-falcon-rtr-list-processes host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Context Example
{
"CrowdStrike": {
"Command": {
"ps": {
"Filename": "ps-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
}
},
"File": {
"EntryID": "1792@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
"Info": "text/plain",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Name": "ps-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a115919af3",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SSDeep": "768:4jcAkTBaZ61QUEcDBdMoFwIxVvroYrohrbY2akHLnsa5fbqFEJtPNObzVj0ff+3K:4IraZ61QUEcDBdMoFwIxRJEbY2akHLnr",
"Size": 30798,
"Type": "ASCII text"
}
}
Human Readable Output
CrowdStrike Falcon ps command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Stdout TOO MUCH INFO TO DISPLAY
cs-falcon-rtr-list-network-stats
Executes an RTR active-responder netstat command to get a list of network status and protocol statistics across the given host.
Base Command
cs-falcon-rtr-list-network-stats
Input
| Argument Name | Description | Required |
|---|---|---|
| host_id | The host ID to get the network status and protocol statistics list from. | Required |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Command.netstat.Filename | String | The name of the result file to be returned. |
Command Example
!cs-falcon-rtr-list-network-stats host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Context Example
{
"CrowdStrike": {
"Command": {
"netstat": {
"Filename": "netstat-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
}
},
"File": {
"EntryID": "1797@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
"Info": "text/plain",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Name": "netstat-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1864ce595",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SSDeep": "48:XSvprPoeCfd8saowYL8zjt6yjjRchg24OI58RtTLvWptl6TtCla5n1lEtClMw/u:CRQeCxRmxVpIHUchCIvsCo",
"Size": 4987,
"Type": "ASCII text, with CRLF line terminators"
}
}
Human Readable Output
CrowdStrike Falcon netstat command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Stdout TOO MUCH INFO TO DISPLAY
cs-falcon-rtr-read-registry
Executes an RTR active-responder read registry keys command across the given hosts. This command is valid only for Windows hosts.
Base Command
cs-falcon-rtr-read-registry
Input
| Argument Name | Description | Required |
|---|---|---|
| host_ids | A comma-separated list of the host IDs to get the registry keys from. | Required |
| registry_keys | A comma-separated list of the registry keys, sub-keys, or value to get. | Required |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-rtr-read-registry host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 registry_keys=`
HKEY_LOCAL_MACHINE,HKEY_USERS`
Context Example
{
"File": [
{
"EntryID": "1806@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
"Info": "text/plain",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Name": "reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_USERS",
"SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a139dd0333",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SSDeep": "12:uSn3PtdoI1pZI2WUNI2e6NI2vboI2vbP3I2zd:uSQIpZIII1aIUMIUjIcd",
"Size": 656,
"Type": "ASCII text, with CRLF, LF line terminators"
},
{
"EntryID": "1807@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
"Info": "text/plain",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Name": "reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_LOCAL_MACHINE",
"SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a18e3b4919",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SSDeep": "6:zYuSugMQEYPtdWCMwdiwf2Jai2FU42DGE25/:zYuSnMQXPtd9/eJqy7yfh",
"Size": 320,
"Type": "ASCII text, with CRLF, LF line terminators"
}
]
}
Human Readable Output
CrowdStrike Falcon reg command on hosts [‘a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1’]
FileName Stdout reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_USERS TOO MUCH INFO TO DISPLAY reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_LOCAL_MACHINE TOO MUCH INFO TO DISPLAY
cs-falcon-rtr-list-scheduled-tasks
Executes an RTR active-responder netstat command to get a list of scheduled tasks across the given host. This command is valid only for Windows hosts.
Base Command
cs-falcon-rtr-list-scheduled-tasks
Input
| Argument Name | Description | Required |
|---|---|---|
| host_ids | A comma-separated list of the hosts IDs to get the list of scheduled tasks from. | Required |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-rtr-list-scheduled-tasks host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Context Example
{
"CrowdStrike": {
"Command": {
"runscript": {
"Filename": "runscript-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
}
},
"File": {
"EntryID": "1812@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
"Info": "text/plain",
"MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"Name": "runscript-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1c589bf80",
"SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"SSDeep": "3072:zjQ3/3YHGa8dbXbpbItbo4W444ibNb9MTf2Wat4cuuEqk4W4ybmF54c4eEEEjX6f:EXN8Nbw",
"Size": 299252,
"Type": "ASCII text"
}
}
Human Readable Output
CrowdStrike Falcon runscript command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Stdout TOO MUCH INFO TO DISPLAY
cs-falcon-rtr-retrieve-file
Gets the RTR extracted file contents for the specified file path.
Base Command
cs-falcon-rtr-retrieve-file
Input
| Argument Name | Description | Required |
|---|---|---|
| host_ids | A comma-separated list of the hosts IDs to get the file from. | Required |
| file_path | The file path of the required file to extract. | Required |
| filename | The filename to use for the archive name and the file within the archive. | Optional |
| interval_in_seconds | Interval between polling. Default is 60 seconds. Must be higher than 10. | Optional |
| hosts_and_requests_ids | This is an internal argument used for the polling process, not to be used by the user. | Optional |
| SHA256 | This is an internal argument used for the polling process, not to be used by the user. | Optional |
| queue_offline | Whether the command will run against an offline-queued session and be queued for execution when the host comes online. | Optional |
| timeout | The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. | Optional |
| polling_timeout | Timeout for polling. Default is 600 seconds. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.File.FileName | String | The filename. |
| CrowdStrike.File.HostID | String | The host ID. |
| File.Size | Number | The size of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Name | String | The name of the file. |
| File.SSDeep | String | The SSDeep hash of the file. |
| File.EntryID | String | The entry ID of the file. |
| File.Info | String | Information about the file. |
| File.Type | String | The file type. |
| File.MD5 | String | The MD5 hash of the file. |
| File.Extension | String | The extension of the file. |
Command Example
!cs-falcon-rtr-retrieve-file file_path=`C:\Windows\System32\Windows.Media.FaceAnalysis.dll` host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1,a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
Human Readable Output
Waiting for the polling execution
get-mapping-fields
Returns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes. Note that this command is supported in Cortex XSOAR only.
Base Command
get-mapping-fields
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
get-remote-data
Gets remote data from a remote incident or detection. This method does not update the current incident or detection, and should be used for debugging purposes only. Note that this command is supported in Cortex XSOAR only.
Base Command
get-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The remote incident or detection ID. | Required |
| lastUpdate | The UTC timestamp in seconds of the last update. The incident or detection is only updated if it was modified after the last update time. Default is 0. | Optional |
Context Output
There is no context output for this command.
get-modified-remote-data
Gets the list of incidents and detections that were modified since the last update time. This method is used for debugging purposes. The get-modified-remote-data command is used as part of the Mirroring feature that was introduced in Cortex XSOAR version 6.1. Note that this command is supported in Cortex XSOAR only.
Base Command
get-modified-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| lastUpdate | Date string representing the local time in UTC timestamp in seconds. The incident or detection is only returned if it was modified after the last update time. | Optional |
Context Output
There is no context output for this command.
update-remote-system
Updates the remote incident or detection with local incident or detection changes. This method is only used for debugging purposes and will not update the current incident or detection. Note that this command is supported in Cortex XSOAR only.
Base Command
update-remote-system
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
cve
Retrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.
Base Command
cve
Input
| Argument Name | Description | Required |
|---|---|---|
| cve_id | Deprecated. Use cve instead. | Optional |
| cve | Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD). This filter supports multiple values and negation. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| DBotScore.Score | Number | The actual score. |
Command Example
cve cve=CVE-2021-2222
Human Readable Output
| ID | Severity | Published Date | Base Score |
|---|---|---|---|
| CVE-2021-2222 | HIGH | 2021-09-16T15:12:42Z | 1 |
cs-falcon-create-ml-exclusion
Create an ML exclusion.
Base Command
cs-falcon-create-ml-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| value | Value to match for exclusion. | Required |
| excluded_from | A comma-separated list from where to exclude the exclusion. Possible values are: blocking, extraction. | Required |
| comment | Comment describing why the exclusions were created. | Optional |
| groups | A comma-separated list of group ID(s) impacted by the exclusion OR all if empty. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.MLExclusion.id | String | The ML exclusion ID. |
| CrowdStrike.MLExclusion.value | String | The ML exclusion value. |
| CrowdStrike.MLExclusion.regexp_value | String | A regular expression for matching the excluded value. |
| CrowdStrike.MLExclusion.value_hash | String | An hash of the value field. |
| CrowdStrike.MLExclusion.excluded_from | String | What the exclusion applies to (e.g., a specific ML model). |
| CrowdStrike.MLExclusion.groups.id | String | Group ID that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.group_type | String | Group type that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.name | String | Group name that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.description | String | Group description that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.assignment_rule | String | Group assignment rule that the exclusion is associated with. |
| CrowdStrike.MLExclusion.groups.created_by | String | Indicate who created the group. |
| CrowdStrike.MLExclusion.groups.created_timestamp | Date | The date when the group was created. |
| CrowdStrike.MLExclusion.groups.modified_by | String | Indicate who last modified the group. |
| CrowdStrike.MLExclusion.groups.modified_timestamp | Date | The date when the group was last modified. |
| CrowdStrike.MLExclusion.applied_globally | Boolean | Whether the exclusion rule applies globally or only to specific entities. |
| CrowdStrike.MLExclusion.last_modified | Date | The date when the exclusion rule was last modified. |
| CrowdStrike.MLExclusion.modified_by | String | Indicate who last modified the rule. |
| CrowdStrike.MLExclusion.created_on | Date | The date when the exclusion rule was created. |
| CrowdStrike.MLExclusion.created_by | String | Indicate who created the rule. |
Command Example
!cs-falcon-create-ml-exclusion value=/demo-test excluded_from=blocking groups=999999
Context Example
{
"CrowdStrike": {
"MLExclusion": {
"applied_globally": false,
"created_by": "api-client-id:123456",
"created_on": "2023-03-06T13:57:14.853546312Z",
"excluded_from": [
"blocking"
],
"groups": [
{
"assignment_rule": "device_id",
"created_by": "admin@test.com",
"created_timestamp": "2023-01-23T15:01:11.846726918Z",
"description": "",
"group_type": "static",
"id": "999999",
"modified_by": "admin@test.com",
"modified_timestamp": "2023-01-23T15:18:52.316882546Z",
"name": "Lab env"
}
],
"id": "123456",
"last_modified": "2023-03-06T13:57:14.853546312Z",
"modified_by": "api-client-id:123456",
"regexp_value": "\\/demo-test",
"value": "/demo-test",
"value_hash": "abcdef123456"
}
}
}
Human Readable Output
CrowdStrike Falcon machine learning exclusion
Id Value RegexpValue ValueHash ExcludedFrom Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy 123456 /demo-test \/demo-test abcdef123456 values: blocking - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id:
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z2023-03-06T13:57:14.853546312Z api-client-id:123456 2023-03-06T13:57:14.853546312Z api-client-id:123456
cs-falcon-update-ml-exclusion
Updates an ML exclusion. At least one argument is required in addition to the id argument.
Base Command
cs-falcon-update-ml-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the exclusion to update. | Required |
| value | Value to match for the exclusion (the exclusion pattern). | Optional |
| comment | Comment describing why the exclusions were created. | Optional |
| groups | A comma-separated list of group ID(s) impacted by the exclusion. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.MLExclusion.id | String | The ML exclusion ID. |
| CrowdStrike.MLExclusion.value | String | The ML exclusion value. |
| CrowdStrike.MLExclusion.regexp_value | String | A regular expression for matching the excluded value. |
| CrowdStrike.MLExclusion.value_hash | String | A hash of the value field. |
| CrowdStrike.MLExclusion.excluded_from | String | What the exclusion applies to (e.g., a specific ML model). |
| CrowdStrike.MLExclusion.groups.id | String | Group ID that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.group_type | String | Group type that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.name | String | Group name that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.description | String | Group description that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.assignment_rule | String | Group assignment rule that the exclusion is associated with. |
| CrowdStrike.MLExclusion.groups.created_by | String | Indicate who created the group. |
| CrowdStrike.MLExclusion.groups.created_timestamp | Date | The date when the group was created. |
| CrowdStrike.MLExclusion.groups.modified_by | String | Indicate who last modified the group. |
| CrowdStrike.MLExclusion.groups.modified_timestamp | Date | The date when the group was last modified. |
| CrowdStrike.MLExclusion.applied_globally | Boolean | Whether the exclusion rule applies globally or only to specific entities. |
| CrowdStrike.MLExclusion.last_modified | Date | The date when the exclusion rule was last modified. |
| CrowdStrike.MLExclusion.modified_by | String | Indicate who last modified the rule. |
| CrowdStrike.MLExclusion.created_on | Date | The date when the exclusion rule was created. |
| CrowdStrike.MLExclusion.created_by | String | Indicate who created the rule. |
Command Example
!cs-falcon-update-ml-exclusion id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 comment=demo-comment
Context Example
{
"CrowdStrike": {
"MLExclusion": {
"applied_globally": false,
"created_by": "api-client-id:123456",
"created_on": "2023-03-06T13:56:25.940685483Z",
"excluded_from": [
"extraction",
"blocking"
],
"groups": [
{
"assignment_rule": "device_id:",
"created_by": "admin@test.com",
"created_timestamp": "2023-01-23T15:01:11.846726918Z",
"description": "",
"group_type": "static",
"id": "999999",
"modified_by": "admin@test.com",
"modified_timestamp": "2023-01-23T15:18:52.316882546Z",
"name": "Lab env"
}
],
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"last_modified": "2023-03-06T13:57:21.57829431Z",
"modified_by": "api-client-id:123456",
"regexp_value": "\\/demo",
"value": "/demo",
"value_hash": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
}
}
}
Human Readable Output
CrowdStrike Falcon machine learning exclusion
Id Value RegexpValue ValueHash ExcludedFrom Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 /demo \/demo a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 values: extraction, blocking - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id:
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z2023-03-06T13:57:21.57829431Z api-client-id:123456 2023-03-06T13:56:25.940685483Z api-client-id:123456
cs-falcon-delete-ml-exclusion
Delete the ML exclusions by ID.
Base Command
cs-falcon-delete-ml-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of exclusion IDs to delete. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-delete-ml-exclusion ids=123456
Human Readable Output
‘The machine learning exclusions with IDs ‘123456’ was successfully deleted.’
cs-falcon-search-ml-exclusion
Get a list of ML exclusions by specifying their IDs, value, or a specific filter.
Base Command
cs-falcon-search-ml-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | A custom filter by which the exclusions should be filtered. The syntax follows the pattern <property>:[operator]'<value>'. For example: value:’test’.Available filters: applied_globally, created_by, created_on, last_modified, modified_by, value. For more information, see: https://falcon.crowdstrike.com/documentation/page/d3c84a1b/falcon-query-language-fql. |
Optional |
| value | The value by which the exclusions should be filtered. | Optional |
| ids | A comma-separated list of exclusion IDs to retrieve. The IDs overwrite the filter and value. | Optional |
| limit | The maximum number of records to return. [1-500]. Applies only if the ids argument is not supplied. | Optional |
| offset | The offset to start retrieving records from. Applies only if the ids argument is not supplied. | Optional |
| sort | How to sort the retrieved exclusions. Possible values are: applied_globally.asc, applied_globally.desc, created_by.asc, created_by.desc, created_on.asc, created_on.desc, last_modified.asc, last_modified.desc, modified_by.asc, modified_by.desc, value.asc, value.desc. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.MLExclusion.id | String | The ML exclusion ID. |
| CrowdStrike.MLExclusion.value | String | The ML exclusion value. |
| CrowdStrike.MLExclusion.regexp_value | String | A regular expression for matching the excluded value. |
| CrowdStrike.MLExclusion.value_hash | String | A hash of the value field. |
| CrowdStrike.MLExclusion.excluded_from | String | What the exclusion applies to (e.g., a specific ML model). |
| CrowdStrike.MLExclusion.groups.id | String | Group ID that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.group_type | String | Group type that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.name | String | Group name that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.description | String | Group description that the exclusion rule is associated with. |
| CrowdStrike.MLExclusion.groups.assignment_rule | String | Group assignment rule that the exclusion is associated with. |
| CrowdStrike.MLExclusion.groups.created_by | String | Indicate who created the group. |
| CrowdStrike.MLExclusion.groups.created_timestamp | Date | The date when the group was created. |
| CrowdStrike.MLExclusion.groups.modified_by | String | Indicate who last modified the group. |
| CrowdStrike.MLExclusion.groups.modified_timestamp | Date | The date when the group was last modified. |
| CrowdStrike.MLExclusion.applied_globally | Boolean | Whether the exclusion rule applies globally or only to specific entities. |
| CrowdStrike.MLExclusion.last_modified | Date | The date when the exclusion rule was last modified. |
| CrowdStrike.MLExclusion.modified_by | String | Indicate who last modified the rule. |
| CrowdStrike.MLExclusion.created_on | Date | The date when the exclusion rule was created. |
| CrowdStrike.MLExclusion.created_by | String | Indicate who created the rule. |
Command Example
!cs-falcon-search-ml-exclusion limit=1
Context Example
{
"CrowdStrike": {
"MLExclusion": {
"applied_globally": false,
"created_by": "api-client-id:123456",
"created_on": "2023-03-01T18:51:07.196018144Z",
"excluded_from": [
"blocking"
],
"groups": [
{
"assignment_rule": "device_id",
"created_by": "admin@test.com",
"created_timestamp": "2023-01-23T15:01:11.846726918Z",
"description": "",
"group_type": "static",
"id": "999999",
"modified_by": "admin@test.com",
"modified_timestamp": "2023-01-23T15:18:52.316882546Z",
"name": "Lab env"
}
],
"id": "123456",
"last_modified": "2023-03-01T18:51:07.196018144Z",
"modified_by": "api-client-id:123456",
"regexp_value": "\\/MosheTest2-432",
"value": "/MosheTest2-432",
"value_hash": "abcdef123456"
}
}
}
Human Readable Output
CrowdStrike Falcon machine learning exclusions
Id Value RegexpValue ValueHash ExcludedFrom Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy 123456 /MosheTest2-432 \/MosheTest2-432 abcdef123456 values: blocking - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z2023-03-01T18:51:07.196018144Z api-client-id:123456 2023-03-01T18:51:07.196018144Z api-client-id:123456
cs-falcon-create-ioa-exclusion
Create an IOA exclusion.
Base Command
cs-falcon-create-ioa-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| exclusion_name | Name of the exclusion. | Required |
| pattern_name | Name of the exclusion pattern. | Optional |
| pattern_id | ID of the exclusion pattern. | Required |
| cl_regex | Command line regular expression. | Required |
| ifn_regex | Image filename regular expression. | Required |
| comment | Comment describing why the exclusions were created. | Optional |
| description | Exclusion description. | Optional |
| detection_json | JSON formatted detection template. | Optional |
| groups | A comma-separated list of group ID(s) impacted by the exclusion OR all if empty. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOAExclusion.id | String | A unique identifier for the IOA exclusion. |
| CrowdStrike.IOAExclusion.name | String | The name of the IOA exclusion. |
| CrowdStrike.IOAExclusion.description | String | A description of the IOA exclusion. |
| CrowdStrike.IOAExclusion.pattern_id | String | The identifier of the pattern associated with the IOA exclusion. |
| CrowdStrike.IOAExclusion.pattern_name | String | The name of the pattern associated with the IOA exclusion. |
| CrowdStrike.IOAExclusion.ifn_regex | String | A regular expression used for filename matching. |
| CrowdStrike.IOAExclusion.cl_regex | String | A regular expression used for command line matching. |
| CrowdStrike.IOAExclusion.detection_json | String | A JSON string that describes the detection logic for the IOA exclusion. |
| CrowdStrike.IOAExclusion.groups.id | String | Group ID that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.group_type | String | Group type that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.name | String | Group name that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.description | String | Group description that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.assignment_rule | String | Group assignment rule that the exclusion is associated with. |
| CrowdStrike.IOAExclusion.groups.created_by | String | Indicate who created the group. |
| CrowdStrike.IOAExclusion.groups.created_timestamp | Date | The date when the group was created. |
| CrowdStrike.IOAExclusion.groups.modified_by | String | Indicate who last modified the group. |
| CrowdStrike.IOAExclusion.groups.modified_timestamp | Date | The date when the group was last modified. |
| CrowdStrike.IOAExclusion.applied_globally | Boolean | Whether the exclusion rule applies globally or only to specific entities. |
| CrowdStrike.IOAExclusion.last_modified | Date | The date when the exclusion rule was last modified. |
| CrowdStrike.IOAExclusion.modified_by | String | Indicate who last modified the rule. |
| CrowdStrike.IOAExclusion.created_on | Date | The date when the exclusion rule was created. |
| CrowdStrike.IOAExclusion.created_by | String | Indicate who created the rule. |
Command Example
!cs-falcon-create-ioa-exclusion exclusion_name=demo-test pattern_id=101010 cl_regex=.* ifn_regex="c:\\\\windows\\\\system32\\\\test.exe" groups=999999
Context Example
{
"CrowdStrike": {
"IOAExclusion": {
"applied_globally": false,
"cl_regex": ".*",
"created_by": "api-client-id:123456",
"created_on": "2023-03-06T13:57:41.746172897Z",
"description": "",
"detection_json": "",
"groups": [
{
"assignment_rule": "device_id",
"created_by": "admin@test.com",
"created_timestamp": "2023-01-23T15:01:11.846726918Z",
"description": "",
"group_type": "static",
"id": "999999",
"modified_by": "admin@test.com",
"modified_timestamp": "2023-01-23T15:18:52.316882546Z",
"name": "Lab env"
}
],
"id": "123456",
"ifn_regex": "c:\\\\windows\\\\system32\\\\test.exe",
"last_modified": "2023-03-06T13:57:41.746172897Z",
"modified_by": "api-client-id:123456",
"name": "demo-test",
"pattern_id": "101010",
"pattern_name": ""
}
}
}
Human Readable Output
CrowdStrike Falcon IOA exclusion
Id Name PatternId IfnRegex ClRegex Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy 123456 demo-test 101010 c:\windows\system32\poqexec.exe .* - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z2023-03-06T13:57:41.746172897Z api-client-id:123456 2023-03-06T13:57:41.746172897Z api-client-id:123456
cs-falcon-update-ioa-exclusion
Updates an IOA exclusion. At least one argument is required in addition to the id argument.
Base Command
cs-falcon-update-ioa-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| id | ID of the exclusion to update. | Required |
| exclusion_name | Name of the exclusion. | Optional |
| pattern_id | ID of the exclusion pattern to update. | Optional |
| pattern_name | Name of the exclusion pattern. | Optional |
| cl_regex | Command line regular expression. | Optional |
| ifn_regex | Image filename regular expression. | Optional |
| comment | Comment describing why the exclusions was created. | Optional |
| description | Exclusion description. | Optional |
| detection_json | JSON formatted detection template. | Optional |
| groups | A comma-separated list of group ID(s) impacted by the exclusion. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOAExclusion.id | String | A unique identifier for the IOA exclusion. |
| CrowdStrike.IOAExclusion.name | String | The name of the IOA exclusion. |
| CrowdStrike.IOAExclusion.description | String | A description of the IOA exclusion. |
| CrowdStrike.IOAExclusion.pattern_id | String | The identifier of the pattern associated with the IOA exclusion. |
| CrowdStrike.IOAExclusion.pattern_name | String | The name of the pattern associated with the IOA exclusion. |
| CrowdStrike.IOAExclusion.ifn_regex | String | A regular expression used for filename matching. |
| CrowdStrike.IOAExclusion.cl_regex | String | A regular expression used for command line matching. |
| CrowdStrike.IOAExclusion.detection_json | String | A JSON string that describes the detection logic for the IOA exclusion. |
| CrowdStrike.IOAExclusion.groups.id | String | Group ID that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.group_type | String | Group type that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.name | String | Group name that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.description | String | Group description that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.assignment_rule | String | Group assignment rule that the exclusion is associated with. |
| CrowdStrike.IOAExclusion.groups.created_by | String | Indicate who created the group. |
| CrowdStrike.IOAExclusion.groups.created_timestamp | Date | The date when the group was created. |
| CrowdStrike.IOAExclusion.groups.modified_by | String | Indicate who last modified the group. |
| CrowdStrike.IOAExclusion.groups.modified_timestamp | Date | The date when the group was last modified. |
| CrowdStrike.IOAExclusion.applied_globally | Boolean | Whether the exclusion rule applies globally or only to specific entities. |
| CrowdStrike.IOAExclusion.last_modified | Date | The date when the exclusion rule was last modified. |
| CrowdStrike.IOAExclusion.modified_by | String | Indicate who last modified the rule. |
| CrowdStrike.IOAExclusion.created_on | Date | The date when the exclusion rule was created. |
| CrowdStrike.IOAExclusion.created_by | String | Indicate who created the rule. |
Command Example
!cs-falcon-update-ioa-exclusion id=123456 description=demo-description
Context Example
{
"CrowdStrike": {
"IOAExclusion": {
"applied_globally": false,
"cl_regex": ".*",
"created_by": "api-client-id:123456",
"created_on": "2023-03-06T13:46:58.137122925Z",
"description": "demo-description",
"detection_json": "",
"groups": [
{
"assignment_rule": "device_id",
"created_by": "admin@test.com",
"created_timestamp": "2023-01-23T15:01:11.846726918Z",
"description": "",
"group_type": "static",
"id": "999999",
"modified_by": "admin@test.com",
"modified_timestamp": "2023-01-23T15:18:52.316882546Z",
"name": "Lab env"
}
],
"id": "123456",
"ifn_regex": "c:\\\\windows\\\\system32\\\\poqexec\\.exe",
"last_modified": "2023-03-06T13:57:49.086458198Z",
"modified_by": "api-client-id:123456",
"name": "demo",
"pattern_id": "101010",
"pattern_name": ""
}
}
}
Human Readable Output
CrowdStrike Falcon IOA exclusion
Id Name Description PatternId IfnRegex ClRegex Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy 123456 demo demo-description 101010 c:\windows\system32\poqexec.exe .* - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z2023-03-06T13:57:49.086458198Z api-client-id:123456 2023-03-06T13:46:58.137122925Z api-client-id:123456
cs-falcon-delete-ioa-exclusion
Delete the IOA exclusions by ID.
Base Command
cs-falcon-delete-ioa-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of exclusion IDs to delete. | Required |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-delete-ioa-exclusion ids=123456
Human Readable Output
‘The IOA exclusions with IDs ‘123456’ was successfully deleted.’
cs-falcon-search-ioa-exclusion
Get a list of IOA exclusions by specifying their IDs or a filter.
Base Command
cs-falcon-search-ioa-exclusion
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | A custom filter by which the exclusions should be filtered. The syntax follows the pattern <property>:[operator]'<value>'. For example: name:’test’.Available filters: applied_globally, created_by, created_on, name, last_modified, modified_by, value, pattern. For more information, see: https://www.falconpy.io/Service-Collections/Falcon-Query-Language. |
Optional |
| name | The name by which the exclusions should be filtered. | Optional |
| ids | A comma-separated list of exclusion IDs to retrieve. The IDs overwrite the filter and name. | Optional |
| limit | The limit of how many exclusions to retrieve. Default is 50. Applies only if the ids argument is not supplied. | Optional |
| offset | The offset of how many exclusions to skip. Default is 0. Applies only if the ids argument is not supplied. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOAExclusion.id | String | A unique identifier for the IOA exclusion. |
| CrowdStrike.IOAExclusion.name | String | The name of the IOA exclusion. |
| CrowdStrike.IOAExclusion.description | String | A description of the IOA exclusion. |
| CrowdStrike.IOAExclusion.pattern_id | String | The identifier of the pattern associated with the IOA exclusion. |
| CrowdStrike.IOAExclusion.pattern_name | String | The name of the pattern associated with the IOA exclusion. |
| CrowdStrike.IOAExclusion.ifn_regex | String | A regular expression used for filename matching. |
| CrowdStrike.IOAExclusion.cl_regex | String | A regular expression used for command line matching. |
| CrowdStrike.IOAExclusion.detection_json | String | A JSON string that describes the detection logic for the IOA exclusion. |
| CrowdStrike.IOAExclusion.groups.id | String | Group ID that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.group_type | String | Group type that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.name | String | Group name that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.description | String | Group description that the exclusion rule is associated with. |
| CrowdStrike.IOAExclusion.groups.assignment_rule | String | Group assignment rule that the exclusion is associated with. |
| CrowdStrike.IOAExclusion.groups.created_by | String | Indicate who created the group. |
| CrowdStrike.IOAExclusion.groups.created_timestamp | Date | The date when the group was created. |
| CrowdStrike.IOAExclusion.groups.modified_by | String | Indicate who last modified the group. |
| CrowdStrike.IOAExclusion.groups.modified_timestamp | Date | The date when the group was last modified. |
| CrowdStrike.IOAExclusion.applied_globally | Boolean | Whether the exclusion rule applies globally or only to specific entities. |
| CrowdStrike.IOAExclusion.last_modified | Date | The date when the exclusion rule was last modified. |
| CrowdStrike.IOAExclusion.modified_by | String | Indicate who last modified the rule. |
| CrowdStrike.IOAExclusion.created_on | Date | The date when the exclusion rule was created. |
| CrowdStrike.IOAExclusion.created_by | String | Indicate who created the rule. |
Command Example
!cs-falcon-search-ioa-exclusion limit=1
Context Example
{
"CrowdStrike": {
"IOAExclusion": {
"applied_globally": true,
"cl_regex": "regex",
"created_by": "user@test.com",
"created_on": "2023-02-06T16:42:19.29906839Z",
"description": "demo description",
"detection_json": "",
"groups": [],
"id": "123456",
"ifn_regex": ".*\\\\Windows\\\\System32\\\\choice\\.exe",
"last_modified": "2023-02-26T15:30:04.554767735Z",
"modified_by": "api-client-id:123456",
"name": "My IOA Exclusion",
"pattern_id": "101010",
"pattern_name": "P_name"
}
}
}
Human Readable Output
CrowdStrike Falcon IOA exclusions
Id Name Description PatternId PatternName IfnRegex ClRegex AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy 123456 My IOA Exclusion demo description 101010 P_name .*\Windows\System32\choice.exe choice\s+/m\s+crowdstrike_sample_detection 2023-02-26T15:30:04.554767735Z api-client-id:123456 2023-02-06T16:42:19.29906839Z user@test.com
cs-falcon-list-quarantined-file
Get quarantine file metadata by specified IDs or filter.
Base Command
cs-falcon-list-quarantined-file
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of quarantined file IDs to retrieve. | Optional |
| filter | A custom filter by which the retrieved quarantined file should be filtered. | Optional |
| sha256 | A comma-separated list of SHA256 hash of the files to retrieve. | Optional |
| filename | A comma-separated list of the name of the files to retrieve. | Optional |
| state | Filter the retrieved files by state. | Optional |
| hostname | A comma-separated list of the hostnames of the files to retrieve. | Optional |
| username | A comma-separated list of the usernames of the files to retrieve. | Optional |
| limit | Maximum number of IDs to return. Max 5000. Default 50. | Optional |
| offset | Starting index of the overall result set from which to return IDs. Default 0. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.QuarantinedFile.id | String | A unique identifier for the quarantined file. |
| CrowdStrike.QuarantinedFile.aid | String | The agent identifier of the agent that quarantined the file. |
| CrowdStrike.QuarantinedFile.cid | String | The unique customer identifier of the agent that quarantined the file. |
| CrowdStrike.QuarantinedFile.sha256 | String | The SHA256 hash value of the quarantined file. |
| CrowdStrike.QuarantinedFile.paths.path | String | The full path of the quarantined file. |
| CrowdStrike.QuarantinedFile.paths.filename | String | The name of the quarantined file. |
| CrowdStrike.QuarantinedFile.paths.state | String | The current state of the quarantined file path (e.g., “purged”). |
| CrowdStrike.QuarantinedFile.state | String | The current state of the quarantined file (e.g., “unrelease_pending”). |
| CrowdStrike.QuarantinedFile.detect_ids | String | The detection identifiers associated with the quarantined file. |
| CrowdStrike.QuarantinedFile.hostname | String | The hostname of the agent that quarantined the file. |
| CrowdStrike.QuarantinedFile.username | String | The username associated with the quarantined file. |
| CrowdStrike.QuarantinedFile.date_updated | Date | The date the quarantined file was last updated. |
| CrowdStrike.QuarantinedFile.date_created | Date | The date the quarantined file was created. |
Command Example
!cs-falcon-list-quarantined-file limit=1
Context Example
{
"CrowdStrike": {
"QuarantinedFile": {
"aid": "a123456",
"cid": "c123456",
"date_created": "2022-12-13T14:23:49Z",
"date_updated": "2023-03-06T13:47:30Z",
"detect_ids": [
"ldt:a123456:456789"
],
"hostname": "INSTANCE-1",
"id": "a123456_sha123456",
"paths": [
{
"filename": "nc.exe",
"path": "\\Device\\HarddiskVolume3\\Users\\admin\\Downloads\\hamuzim\\test.exe",
"state": "quarantined"
}
],
"sha256": "sha123456",
"state": "deleted",
"username": "admin"
}
}
}
Human Readable Output
CrowdStrike Falcon Quarantined File
Id Aid Cid Sha256 Paths State DetectIds Hostname Username DateUpdated DateCreated a123456_sha123456 a123456 c123456 sha123456 - path: \Device\HarddiskVolume3\Users\admin\Downloads\hamuzim\netcat-1.11\nc.exe
filename: nc.exe
state: quarantineddeleted values: ldt:a123456:456789 INSTANCE-1 admin 2023-03-06T13:47:30Z 2022-12-13T14:23:49Z
cs-falcon-apply-quarantine-file-action
Apply action to quarantined files by file IDs or filter.
Base Command
cs-falcon-apply-quarantine-file-action
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of quarantined file IDs to update. | Optional |
| action | Action to perform against the quarantined file. Possible values are: delete, release, unrelease. | Required |
| comment | Comment to appear along with the action taken. | Required |
| filter | Update files based on a custom filter. | Optional |
| sha256 | A comma-separated list of quarantined SHA256 files to update. | Optional |
| filename | A comma-separated list of quarantined filenames to update. | Optional |
| state | Update files based on the state. | Optional |
| hostname | A comma-separated list of quarantined file hostnames to update. | Optional |
| username | A comma-separated list of quarantined file usernames to update. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-apply-quarantine-file-action filename=nc.exe action=delete comment=demo-comment
Human Readable Output
The Quarantined File with IDs [‘a123456_sha123456’] was successfully updated.
cs-falcon-ods-query-scan
Retrieve ODS scan details.
Base Command
cs-falcon-ods-query-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Valid CS-Falcon-FQL filter to query with. | Optional |
| ids | Comma-separated list of scan IDs to retrieve details about. If set, will override all other arguments. | Optional |
| initiated_from | Comma-separated list of scan initiation sources to filter by. | Optional |
| status | Comma-separated list of scan statuses to filter by. | Optional |
| severity | Comma-separated list of scan severities to filter by. | Optional |
| scan_started_on | UTC-format of the scan start time to filter by. | Optional |
| scan_completed_on | UTC-format of the scan completion time to filter by. | Optional |
| offset | Starting index of overall result set from which to return IDs. | Optional |
| limit | Maximum number of resources to return. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.ODSScan.id | String | A unique identifier for the scan event. |
| CrowdStrike.ODSScan.cid | String | A unique identifier for the client that triggered the scan. |
| CrowdStrike.ODSScan.profile_id | String | A unique identifier for the scan profile used in the scan. |
| CrowdStrike.ODSScan.description | String | The ID of the description of the scan. |
| CrowdStrike.ODSScan.scan_inclusions | String | The files or folders included in the scan. |
| CrowdStrike.ODSScan.initiated_from | String | The source of the scan initiation. |
| CrowdStrike.ODSScan.quarantine | Boolean | Whether the scan was set to quarantine. |
| CrowdStrike.ODSScan.cpu_priority | Number | The CPU priority for the scan (1-5). |
| CrowdStrike.ODSScan.preemption_priority | Number | The preemption priority for the scan. |
| CrowdStrike.ODSScan.metadata.host_id | String | A unique identifier for the host that was scanned. |
| CrowdStrike.ODSScan.metadata.host_scan_id | String | A unique identifier for the scan that was performed on the host. |
| CrowdStrike.ODSScan.metadata.scan_host_metadata_id | String | A unique identifier for the metadata associated with the host scan. |
| CrowdStrike.ODSScan.metadata.filecount.scanned | Number | The number of files that were scanned. |
| CrowdStrike.ODSScan.metadata.filecount.malicious | Number | The number of files that were identified as malicious. |
| CrowdStrike.ODSScan.metadata.filecount.quarantined | Number | The number of files that were quarantined. |
| CrowdStrike.ODSScan.metadata.filecount.skipped | Number | The number of files that were skipped during the scan. |
| CrowdStrike.ODSScan.metadata.filecount.traversed | Number | The number of files that were traversed during the scan. |
| CrowdStrike.ODSScan.metadata.status | String | The status of the scan on this host. (e.g., “pending”, “running”, “completed”, or “failed”). |
| CrowdStrike.ODSScan.metadata.started_on | Date | The date and time that the scan started. |
| CrowdStrike.ODSScan.metadata.completed_on | Date | The date and time that the scan completed. |
| CrowdStrike.ODSScan.metadata.last_updated | Date | The date and time that the metadata was last updated. |
| CrowdStrike.ODSScan.status | String | The status of the scan (e.g., “pending”, “running”, “completed”, or “failed”). |
| CrowdStrike.ODSScan.hosts | String | A list of the host IDs that were scanned. |
| CrowdStrike.ODSScan.endpoint_notification | Boolean | Indicates whether endpoint notifications are enabled. |
| CrowdStrike.ODSScan.pause_duration | Number | The number of hours to pause between scanning each file. |
| CrowdStrike.ODSScan.max_duration | Number | The maximum amount of time to allow for the scan job in hours. |
| CrowdStrike.ODSScan.max_file_size | Number | The maximum file size (in MB) to scan. |
| CrowdStrike.ODSScan.sensor_ml_level_detection | Number | The level of detection sensitivity for the local sensor machine learning model. |
| CrowdStrike.ODSScan.sensor_ml_level_prevention | Number | The level of prevention sensitivity for the local sensor machine learning model. |
| CrowdStrike.ODSScan.cloud_ml_level_detection | Number | The level of detection sensitivity for the cloud machine learning model. |
| CrowdStrike.ODSScan.cloud_ml_level_prevention | Number | The level of prevention sensitivity for the cloud machine learning model. |
| CrowdStrike.ODSScan.policy_setting | Number | A list of policy setting IDs for the scan job (these correspond to specific policy settings in the Falcon console). |
| CrowdStrike.ODSScan.scan_started_on | Date | The timestamp when the scan was started. |
| CrowdStrike.ODSScan.scan_completed_on | Date | The timestamp when the scan was completed. |
| CrowdStrike.ODSScan.created_on | Date | The timestamp when the scan was created. |
| CrowdStrike.ODSScan.created_by | String | The ID of the user who created the scan job. |
| CrowdStrike.ODSScan.last_updated | Date | The timestamp when the scan job was last updated. |
Command Example
!cs-falcon-ods-query-scan initiated_from=some_admin_name severity=high scan_started_on=2023-02-27T09:51:33.91608286Z
Context Example
{
"CrowdStrike": {
"ODSScan": [
{
"cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"cloud_ml_level_detection": 4,
"cloud_ml_level_prevention": 4,
"cpu_priority": 5,
"created_by": "someone@email.com",
"created_on": "2023-05-03T08:45:41.688556439Z",
"endpoint_notification": true,
"file_paths": [
"C:\\Users\\admin\\Downloads\\hamuzim\\netcat-1.11\\eicar_com.exe"
],
"filecount": {},
"hosts": [
"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
],
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"initiated_from": "some_admin_name",
"last_updated": "2023-05-03T08:45:43.348230927Z",
"max_duration": 0,
"max_file_size": 60,
"metadata": [
{
"completed_on": "2023-05-03T08:45:43.274953782Z",
"filecount": {
"malicious": 0,
"quarantined": 0,
"scanned": 0,
"skipped": 0,
"traversed": 0
},
"host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"host_scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"last_updated": "2023-05-03T08:45:43.61797613Z",
"scan_host_metadata_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"started_on": "2023-05-03T08:45:43.069273028Z",
"status": "completed"
}
],
"pause_duration": 2,
"policy_setting": [
26439818675190,
26405458936832,
26405458936833,
26405458936834,
26405458936835,
26405458936840,
26405458936841,
26405458936842,
26405458936843,
26456998543793,
26456998544045,
26456998543652,
26456998543653,
26456998543656,
26456998543654,
26456998543950,
26456998543963
],
"preemption_priority": 1,
"profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"quarantine": true,
"scan_completed_on": "2023-05-03T08:45:43.274953782Z",
"scan_inclusions": [
"**\\Downloads\\**"
],
"scan_started_on": "2023-02-27T09:51:33.91608286Z",
"sensor_ml_level_detection": 4,
"sensor_ml_level_prevention": 4,
"status": "completed"
},
{
"cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"cloud_ml_level_detection": 3,
"cloud_ml_level_prevention": 3,
"cpu_priority": 4,
"created_by": "someone@email.com",
"created_on": "2023-03-12T14:54:43.659773852Z",
"endpoint_notification": true,
"filecount": {},
"hosts": [
"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
],
"id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"initiated_from": "some_admin_name",
"last_updated": "2023-04-05T16:56:14.972317443Z",
"max_duration": 2,
"max_file_size": 60,
"metadata": [
{
"completed_on": "2023-03-12T14:57:37.338506965Z",
"filecount": {
"malicious": 0,
"quarantined": 0,
"scanned": 0,
"skipped": 0,
"traversed": 518485
},
"host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"host_scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"last_updated": "2023-03-12T14:57:37.338585331Z",
"scan_host_metadata_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"started_on": "2023-02-27T09:51:33.91608286Z",
"status": "completed"
}
],
"pause_duration": 2,
"policy_setting": [
26439818674573,
26439818674574,
26439818674575,
26405458936832,
26456998543653,
26456998543656,
26456998543654,
26456998543950,
26456998543963
],
"preemption_priority": 1,
"profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"quarantine": true,
"scan_completed_on": "2023-03-12T14:57:37.338506965Z",
"scan_inclusions": [
"*"
],
"scan_started_on": "2023-03-12T14:54:45.210172175Z",
"sensor_ml_level_detection": 3,
"sensor_ml_level_prevention": 3,
"status": "failed"
}
]
}
}
Human Readable Output
CrowdStrike Falcon ODS Scans
ID Status Severity File Count Description Hosts/Host groups End time Start time Run by a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 failed scanned: 0
malicious: 0
quarantined: 0
skipped: 0
traversed: 518464desc3456346 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2023-02-27T09:51:33.91608286Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 failed scanned: 0
malicious: 0
quarantined: 0
skipped: 0
traversed: 518511a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2023-03-13T14:50:26.259846586Z 2023-02-27T09:51:33.91608286Z someone@email.com
cs-falcon-ods-query-scheduled-scan
Retrieve ODS scheduled scan details.
Base Command
cs-falcon-ods-query-scheduled-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Valid CS-Falcon-FQL filter to query with. | Optional |
| ids | Comma-separated list of scan IDs to retrieve details about. If set, will override all other arguments. | Optional |
| initiated_from | Comma-separated list of scan initiation sources to filter by. | Optional |
| status | Comma-separated list of scan statuses to filter by. | Optional |
| created_on | UTC-format of the scan creation time to filter by. | Optional |
| created_by | UTC-format time of the scan creator to filter by. | Optional |
| start_timestamp | UTC-format of scan start time to filter by. | Optional |
| deleted | Deleted scans only. | Optional |
| offset | Starting index of overall result set from which to return IDs. | Optional |
| limit | Maximum number of resources to return. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.ODSScheduledScan.id | String | Unique identifier for the scan. |
| CrowdStrike.ODSScheduledScan.cid | String | Identifier for the customer or organization that owns the scan. |
| CrowdStrike.ODSScheduledScan.description | String | The ID of the description of the scan. |
| CrowdStrike.ODSScheduledScan.file_paths | String | The file or folder paths scanned. |
| CrowdStrike.ODSScheduledScan.scan_exclusions | String | The file or folder exclusions from the scan. |
| CrowdStrike.ODSScheduledScan.initiated_from | String | The source of the scan initiation. |
| CrowdStrike.ODSScheduledScan.cpu_priority | Number | The CPU priority for the scan (1-5). |
| CrowdStrike.ODSScheduledScan.preemption_priority | Number | The preemption priority for the scan. |
| CrowdStrike.ODSScheduledScan.status | String | The status of the scan, whether it’s “scheduled”, “running”, “completed”, etc. |
| CrowdStrike.ODSScheduledScan.host_groups | String | The host groups targeted by the scan. |
| CrowdStrike.ODSScheduledScan.endpoint_notification | Boolean | Whether notifications of the scan were sent to endpoints. |
| CrowdStrike.ODSScheduledScan.pause_duration | Number | The pause duration of the scan in hours. |
| CrowdStrike.ODSScheduledScan.max_duration | Number | The maximum duration of the scan in hours. |
| CrowdStrike.ODSScheduledScan.max_file_size | Number | The maximum file size that the scan can handle in MB. |
| CrowdStrike.ODSScheduledScan.sensor_ml_level_detection | Number | The machine learning detection level for the sensor. |
| CrowdStrike.ODSScheduledScan.cloud_ml_level_detection | Number | The machine learning detection level for the cloud. |
| CrowdStrike.ODSScheduledScan.schedule.start_timestamp | Date | The timestamp when the first scan was created. |
| CrowdStrike.ODSScheduledScan.schedule.interval | Number | The interval between scans. |
| CrowdStrike.ODSScheduledScan.created_on | Date | The timestamp when the scan was created. |
| CrowdStrike.ODSScheduledScan.created_by | String | The user who created the scan. |
| CrowdStrike.ODSScheduledScan.last_updated | Date | The timestamp when the scan was last updated. |
| CrowdStrike.ODSScheduledScan.deleted | Boolean | Whether the scan was deleted. |
| CrowdStrike.ODSScheduledScan.quarantine | Boolean | Whether the scan was set to quarantine. |
| CrowdStrike.ODSScheduledScan.metadata.host_id | String | Scan host IDs. |
| CrowdStrike.ODSScheduledScan.metadata.last_updated | Date | The date and time when the detection event was last updated. |
| CrowdStrike.ODSScheduledScan.sensor_ml_level_prevention | Number | The machine learning prevention level for the sensor. |
| CrowdStrike.ODSScheduledScan.cloud_ml_level_prevention | Number | The machine learning prevention level for the cloud. |
Command Example
!cs-falcon-ods-query-scheduled-scan ids=123456789
Context Example
{
"CrowdStrike": {
"ODSScheduledScan": {
"cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"cloud_ml_level_detection": 2,
"cloud_ml_level_prevention": 2,
"cpu_priority": 3,
"created_by": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"created_on": "2023-05-08T09:04:20.8414225Z",
"deleted": false,
"endpoint_notification": true,
"host_groups": [
"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
],
"id": "123456789",
"initiated_from": "cloud_scheduled",
"last_updated": "2023-05-08T09:22:48.408487143Z",
"max_duration": 2,
"max_file_size": 60,
"metadata": [
{
"host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"last_updated": "2023-05-08T09:22:48.408487143Z"
}
],
"pause_duration": 3,
"policy_setting": [
26439818674573,
26439818674574,
26439818675074,
26405458936702,
26405458936703,
26405458936707,
26439818675124,
26439818675125,
26439818675157,
26439818675158,
26439818675182,
26439818675183,
26439818675190,
26439818675191,
26439818675196,
26439818675197,
26439818675204,
26439818675205,
26405458936760,
26405458936761,
26405458936793,
26405458936794,
26405458936818,
26405458936819,
26405458936825,
26405458936826,
26405458936832,
26405458936833,
26405458936840,
26405458936841,
26456998543793,
26456998544045,
26456998543652,
26456998543653,
26456998543656,
26456998543654,
26456998543950,
26456998543963
],
"preemption_priority": 15,
"quarantine": true,
"scan_inclusions": [
"*"
],
"schedule": {
"interval": 14,
"start_timestamp": "2023-05-20T06:49"
},
"sensor_ml_level_detection": 2,
"sensor_ml_level_prevention": 2,
"status": "scheduled"
}
}
}
Human Readable Output
CrowdStrike Falcon ODS Scheduled Scans
ID Hosts targeted Description Host groups Start time Created by 123456789 1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2023-05-20T06:49 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
cs-falcon-ods-query-scan-host
Retrieve ODS scan host details.
Base Command
cs-falcon-ods-query-scan-host
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Valid CS-Falcon-FQL filter to query with. | Optional |
| host_ids | Comma-separated list of host IDs to filter by. | Optional |
| scan_ids | Comma-separated list of scan IDs to filter by. | Optional |
| status | Comma-separated list of scan statuses to filter by. | Optional |
| started_on | UTC-format of scan start time to filter by. | Optional |
| completed_on | UTC-format of scan completion time to filter by. | Optional |
| offset | Starting index of the overall result set from which to return IDs. | Optional |
| limit | Maximum number of resources to return. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.ODSScanHost.id | String | A unique identifier for the scan event. |
| CrowdStrike.ODSScanHost.cid | String | A unique identifier for the client that triggered the scan. |
| CrowdStrike.ODSScanHost.scan_id | String | A unique identifier for the scan. |
| CrowdStrike.ODSScanHost.profile_id | String | A unique identifier for the scan profile used in the scan. |
| CrowdStrike.ODSScanHost.host_id | String | A unique identifier for the host that was scanned. |
| CrowdStrike.ODSScanHost.host_scan_id | String | A unique identifier for the scan that was performed on the host. |
| CrowdStrike.ODSScanHost.filecount.scanned | Number | The number of files that were scanned during the scan. |
| CrowdStrike.ODSScanHost.filecount.malicious | Number | The number of files that were detected as malicious during the scan. |
| CrowdStrike.ODSScanHost.filecount.quarantined | Number | The number of files that were quarantined during the scan. |
| CrowdStrike.ODSScanHost.filecount.skipped | Number | The number of files that were skipped during the scan. |
| CrowdStrike.ODSScanHost.status | String | The status of the scan. (e.g., “completed”, “pending”, “cancelled”, “running”, or “failed”). |
| CrowdStrike.ODSScanHost.severity | Number | A severity score assigned to the scan, ranging from 0 to 100. |
| CrowdStrike.ODSScanHost.started_on | Date | The date and time when the scan started. |
| CrowdStrike.ODSScanHost.completed_on | Date | The date and time when the scan completed. |
| CrowdStrike.ODSScanHost.last_updated | Date | The date and time when the scan event was last updated. |
Command Example
!cs-falcon-ods-query-scan-host filter="scan_id:[\"123456789\",\"987654321\"]"
Context Example
{
"CrowdStrike": {
"ODSScanHost": [
{
"cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"filecount": {},
"host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"id": "123456789",
"last_updated": "2022-11-27T17:15:50.056840267Z",
"profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"status": "pending"
},
{
"cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"completed_on": "2023-05-07T08:28:56.856506979Z",
"filecount": {
"malicious": 0,
"quarantined": 0,
"scanned": 0,
"skipped": 0,
"traversed": 524581
},
"host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"host_scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"id": "987654321",
"last_updated": "2023-05-07T08:28:56.856575358Z",
"profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
"started_on": "2023-05-07T08:25:48.336234188Z",
"status": "completed"
}
]
}
}
Human Readable Output
CrowdStrike Falcon ODS Scan Hosts
ID Scan ID Host ID Filecount Status Severity Started on 123456789 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 pending 987654321 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 scanned: 0
malicious: 0
quarantined: 0
skipped: 0
traversed: 524581completed 2023-05-07T08:25:48.336234188Z
cs-falcon-ods-query-malicious-files
Retrieve ODS malicious file details.
Base Command
cs-falcon-ods-query-malicious-files
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Valid CS-Falcon-FQL filter to query with. | Optional |
| file_ids | Comma-separated list of malicious file IDs to retrieve details about. If set, will override all other arguments. | Optional |
| host_ids | Comma-separated list of host IDs to filter by. | Optional |
| scan_ids | Comma-separated list of scan IDs to filter by. | Optional |
| file_paths | Comma-separated list of file paths to filter by. | Optional |
| file_names | Comma-separated list of filenames to filter by. | Optional |
| hash | Comma-separated list of hashes to filter by. | Optional |
| offset | Starting index of the overall result set from which to return IDs. | Optional |
| limit | Maximum number of resources to return. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.ODSMaliciousFile.id | String | A unique identifier of the detection event. |
| CrowdStrike.ODSMaliciousFile.cid | String | A unique identifier for the client that triggered the detection event. |
| CrowdStrike.ODSMaliciousFile.scan_id | String | A unique identifier for the scan that triggered the detection event. |
| CrowdStrike.ODSMaliciousFile.host_id | String | A unique identifier for the host that was scanned. |
| CrowdStrike.ODSMaliciousFile.host_scan_id | String | A unique identifier for the scan that detected the file on the host. |
| CrowdStrike.ODSMaliciousFile.filepath | String | The full path to the malicious file on the host system. |
| CrowdStrike.ODSMaliciousFile.filename | String | The name of the malicious file. |
| CrowdStrike.ODSMaliciousFile.hash | String | A SHA256 hash of the malicious file, which can be used to identify it. |
| CrowdStrike.ODSMaliciousFile.pattern_id | Number | The identifier of the pattern used to detect the malicious file. |
| CrowdStrike.ODSMaliciousFile.severity | Number | A severity score assigned to the detection event, ranging from 0 to 100. |
| CrowdStrike.ODSMaliciousFile.quarantined | Boolean | Indicates whether the file was quarantined. |
| CrowdStrike.ODSMaliciousFile.last_updated | Date | The date and time when the detection event was last updated. |
Command Example
#### Human Readable Output
>No malicious files match the arguments/filter.
### cs-falcon-ods-create-scan
***
Create an ODS scan and wait for the results.
#### Base Command
`cs-falcon-ods-create-scan`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| hosts | A comma-separated list of hosts to be scanned. "hosts" OR "host_groups" must be set. | Optional |
| host_groups | A comma-separated list of host groups to be scanned. "hosts" OR "host_groups" must be set. | Optional |
| file_paths | A comma-separated list of file paths to be scanned. "file_paths" OR "scan_inclusions" must be set. | Optional |
| scan_inclusions | A comma-separated list of included files or locations for this scan. "file_paths" OR "scan_inclusions" must be set. | Optional |
| scan_exclusions | A comma-separated list of excluded files or locations for this scan. | Optional |
| initiated_from | Scan origin. | Optional |
| cpu_priority | The scan CPU priority. Possible values are: Highest, High, Medium, Low, Lowest. Default is Low. | Optional |
| description | Scan description. | Optional |
| quarantine | Flag indicating if identified threats should be quarantined. | Optional |
| pause_duration | Amount of time (in hours) for scan pauses. Default is 2. | Optional |
| sensor_ml_level_detection | Sensor ML detection level. | Optional |
| sensor_ml_level_prevention | Sensor ML prevention level. | Optional |
| cloud_ml_level_detection | Cloud ML detection level for the scan. | Optional |
| cloud_ml_level_prevention | Cloud ML prevention level for the scan. | Optional |
| max_duration | Maximum time (in hours) the scan is allowed to execute. Default is 2. | Optional |
| interval_in_seconds | The interval in seconds between each poll. Default is 30. | Optional |
| timeout_in_seconds | The timeout in seconds until polling ends. Default is 600. | Optional |
| cloud_pup_adware_level_detection | Potentially unwanted programs (PUPs) adware detection level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. | Optional |
| cloud_pup_adware_level_prevention | Potentially unwanted programs (PUPs) adware prevention level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. | Optional |
| polling | Whether to use polling to wait for the scan result. If set to false, the command will only create the scan and return immediately without waiting for results. Possible values are: true, false. Default is true. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| CrowdStrike.ODSScan.id | String | A unique identifier for the scan event. |
| CrowdStrike.ODSScan.cid | String | A unique identifier for the client that triggered the scan. |
| CrowdStrike.ODSScan.profile_id | String | A unique identifier for the scan profile used in the scan. |
| CrowdStrike.ODSScan.description | String | The ID of the description of the scan. |
| CrowdStrike.ODSScan.scan_inclusions | String | The files or folders included in the scan. |
| CrowdStrike.ODSScan.initiated_from | String | The source of the scan initiation. |
| CrowdStrike.ODSScan.quarantine | Boolean | Whether the scan was set to quarantine. |
| CrowdStrike.ODSScan.cpu_priority | Number | The CPU priority for the scan \(1-5\). |
| CrowdStrike.ODSScan.preemption_priority | Number | The preemption priority for the scan. |
| CrowdStrike.ODSScan.metadata.host_id | String | A unique identifier for the host that was scanned. |
| CrowdStrike.ODSScan.metadata.host_scan_id | String | A unique identifier for the scan that was performed on the host. |
| CrowdStrike.ODSScan.metadata.scan_host_metadata_id | String | A unique identifier for the metadata associated with the host scan. |
| CrowdStrike.ODSScan.metadata.filecount.scanned | Number | The number of files that were scanned. |
| CrowdStrike.ODSScan.metadata.filecount.malicious | Number | The number of files that were identified as malicious. |
| CrowdStrike.ODSScan.metadata.filecount.quarantined | Number | The number of files that were quarantined. |
| CrowdStrike.ODSScan.metadata.filecount.skipped | Number | The number of files that were skipped during the scan. |
| CrowdStrike.ODSScan.metadata.filecount.traversed | Number | The number of files that were traversed during the scan. |
| CrowdStrike.ODSScan.metadata.status | String | The status of the scan on this host \(e.g., "pending", "running", "completed", or "failed"\). |
| CrowdStrike.ODSScan.metadata.started_on | Date | The date and time that the scan started. |
| CrowdStrike.ODSScan.metadata.completed_on | Date | The date and time that the scan completed. |
| CrowdStrike.ODSScan.metadata.last_updated | Date | The date and time that the metadata was last updated. |
| CrowdStrike.ODSScan.status | String | The status of the scan \(e.g., "pending", "running", "completed", or "failed"\). |
| CrowdStrike.ODSScan.hosts | String | A list of the host IDs that were scanned. |
| CrowdStrike.ODSScan.endpoint_notification | Boolean | Indicates whether endpoint notifications are enabled. |
| CrowdStrike.ODSScan.pause_duration | Number | The number of hours to pause between scanning each file. |
| CrowdStrike.ODSScan.max_duration | Number | The maximum amount of time to allow for the scan job in hours. |
| CrowdStrike.ODSScan.max_file_size | Number | The maximum file size \(in MB\) to scan. |
| CrowdStrike.ODSScan.sensor_ml_level_detection | Number | The level of detection sensitivity for the local sensor machine learning model. |
| CrowdStrike.ODSScan.sensor_ml_level_prevention | Number | The level of prevention sensitivity for the local sensor machine learning model. |
| CrowdStrike.ODSScan.cloud_ml_level_detection | Number | The level of detection sensitivity for the cloud machine learning model. |
| CrowdStrike.ODSScan.cloud_ml_level_prevention | Number | The level of prevention sensitivity for the cloud machine learning model. |
| CrowdStrike.ODSScan.policy_setting | Number | A list of policy setting IDs for the scan job \(these correspond to specific policy settings in the Falcon console\). |
| CrowdStrike.ODSScan.scan_started_on | Date | The timestamp when the scan was started. |
| CrowdStrike.ODSScan.scan_completed_on | Date | The timestamp when the scan was completed. |
| CrowdStrike.ODSScan.created_on | Date | The timestamp when the scan was created. |
| CrowdStrike.ODSScan.created_by | String | The ID of the user who created the scan job. |
| CrowdStrike.ODSScan.last_updated | Date | The timestamp when the scan job was last updated. |
| CrowdStrike.ODSScan.cloud_pup_adware_level_detection | Number | Potentially unwanted programs \(PUPs\) Adware detection level. |
| CrowdStrike.ODSScan.cloud_pup_adware_level_prevention | Number | Potentially unwanted programs \(PUPs\) Adware prevention level. |
#### Command Example
```!cs-falcon-ods-create-scan host_groups=7471ba0636b34cbb8c65fae7979a6a9b scan_inclusions=* cpu_priority=Highest max_duration=1 pause_duration=1```
#### Context Example
```json
{
"CrowdStrike": {
"ODSScan": {
"cid": "20879a8064904ecfbb62c118a6a19411",
"cloud_ml_level_detection": 2,
"cloud_ml_level_prevention": 2,
"cpu_priority": 5,
"created_by": "f7acf1bd5d3d4b40afe77546cbbaefde",
"created_on": "2023-06-11T13:23:05.139153881Z",
"filecount": {
"malicious": 0,
"quarantined": 0,
"scanned": 0,
"skipped": 0,
"traversed": 0
},
"host_groups": [
"7471ba0636b34cbb8c65fae7979a6a9b"
],
"id": "9ba8489e9f604b61bf9b4a2c5f95ede7",
"initiated_from": "cloud_adhoc",
"last_updated": "2023-06-11T13:23:05.139153881Z",
"max_duration": 1,
"max_file_size": 60,
"metadata": [
{
"filecount": {},
"host_id": "046761c46ec84f40b27b6f79ce7cd32c",
"last_updated": "2023-06-11T13:23:05.139153881Z",
"scan_host_metadata_id": "31052e821a5a4189a1a9a2814cc88e4e",
"status": "complete"
}
],
"pause_duration": 1,
"policy_setting": [
26439818674573,
26439818674574,
26439818675074,
26405458936702,
26405458936703,
26456998543654,
26456998543950,
26456998543963
],
"preemption_priority": 1,
"profile_id": "335198a96e1a4a6b880d62b2e7ccbb91",
"quarantine": true,
"scan_inclusions": [
"*"
],
"sensor_ml_level_detection": 2,
"sensor_ml_level_prevention": 2,
"cloud_pup_adware_level_prevention": 1,
"cloud_pup_adware_level_detection": 1,
"status": "complete"
}
}
}
Human Readable Output
CrowdStrike Falcon ODS Scans
ID Status Severity File Count Description Hosts/Host groups End time Start time Run by 9ba8489e9f604b61bf9b4a2c5f95ede7 complete 7471ba0636b34cbb8c65fae7979a6a9b f7acf1bd5d3d4b40afe77546cbbaefde
cs-falcon-ods-create-scheduled-scan
Create an ODS scheduled scan.
Base Command
cs-falcon-ods-create-scheduled-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| host_groups | A comma-separated list of host groups to be scanned. | Required |
| file_paths | A comma-separated list of file paths to be scanned. “file_paths” OR “scan_inclusions” must be set. | Optional |
| scan_inclusions | A comma-separated list of included files or locations for this scan. “file_paths” OR “scan_inclusions” must be set. | Optional |
| scan_exclusions | A comma-separated list of excluded files or locations for this scan. | Optional |
| initiated_from | Scan origin. | Optional |
| cpu_priority | The scan CPU priority. Possible values are: Highest, High, Medium, Low, Lowest. Default is Low. | Optional |
| description | Scan description. | Optional |
| quarantine | Flag indicating if identified threats should be quarantined. | Optional |
| pause_duration | Amount of time (in hours) for scan pauses. Default is 2. | Optional |
| sensor_ml_level_detection | Sensor ML detection level. | Optional |
| sensor_ml_level_prevention | Sensor ML prevention level. | Optional |
| cloud_ml_level_detection | Cloud ML detection level for the scan. | Optional |
| cloud_ml_level_prevention | Cloud ML prevention level for the scan. | Optional |
| max_duration | Maximum time (in hours) the scan is allowed to execute. Default is 2. | Optional |
| schedule_start_timestamp | When to start the first scan. Supports english expressions such as “tomorrow” or “in an hour”. | Required |
| schedule_interval | The schedule interval. Possible values are: Never, Daily, Weekly, Every other week, Every four weeks, Monthly. | Required |
| cloud_pup_adware_level_detection | Potentially unwanted programs (PUPs) adware detection level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. | Optional |
| cloud_pup_adware_level_prevention | Potentially unwanted programs (PUPs) adware prevention level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.ODSScheduledScan.id | String | Unique identifier for the scan. |
| CrowdStrike.ODSScheduledScan.cid | String | Identifier for the customer or organization that owns the scan. |
| CrowdStrike.ODSScheduledScan.description | String | The ID of the description of the scan. |
| CrowdStrike.ODSScheduledScan.file_paths | String | The file or folder paths scanned. |
| CrowdStrike.ODSScheduledScan.scan_exclusions | String | The file or folder exclusions from the scan. |
| CrowdStrike.ODSScheduledScan.initiated_from | String | The source of the scan initiation. |
| CrowdStrike.ODSScheduledScan.cpu_priority | Number | The CPU priority for the scan (1-5). |
| CrowdStrike.ODSScheduledScan.preemption_priority | Number | The preemption priority for the scan. |
| CrowdStrike.ODSScheduledScan.status | String | The status of the scan, whether it’s “scheduled”, “running”, “completed”, etc. |
| CrowdStrike.ODSScheduledScan.host_groups | String | The host groups targeted by the scan. |
| CrowdStrike.ODSScheduledScan.endpoint_notification | Boolean | Whether notifications of the scan were sent to endpoints. |
| CrowdStrike.ODSScheduledScan.pause_duration | Number | The pause duration of the scan in hours. |
| CrowdStrike.ODSScheduledScan.max_duration | Number | The maximum duration of the scan in hours. |
| CrowdStrike.ODSScheduledScan.max_file_size | Number | The maximum file size that the scan can handle in MB. |
| CrowdStrike.ODSScheduledScan.sensor_ml_level_detection | Number | The machine learning detection level for the sensor. |
| CrowdStrike.ODSScheduledScan.cloud_ml_level_detection | Number | The machine learning detection level for the cloud. |
| CrowdStrike.ODSScheduledScan.schedule.start_timestamp | Date | The timestamp when the first scan was created. |
| CrowdStrike.ODSScheduledScan.schedule.interval | Number | The interval between scans. |
| CrowdStrike.ODSScheduledScan.created_on | Date | The timestamp when the scan was created. |
| CrowdStrike.ODSScheduledScan.created_by | String | The user who created the scan. |
| CrowdStrike.ODSScheduledScan.last_updated | Date | The timestamp when the scan was last updated. |
| CrowdStrike.ODSScheduledScan.deleted | Boolean | Whether the scan was deleted. |
| CrowdStrike.ODSScheduledScan.quarantine | Boolean | Whether the scan was set to quarantine. |
| CrowdStrike.ODSScheduledScan.metadata.host_id | String | Scan host IDs. |
| CrowdStrike.ODSScheduledScan.metadata.last_updated | Date | The date and time when the detection event was last updated. |
| CrowdStrike.ODSScheduledScan.sensor_ml_level_prevention | Number | The machine learning prevention level for the sensor. |
| CrowdStrike.ODSScheduledScan.cloud_ml_level_prevention | Number | The machine learning prevention level for the cloud. |
| CrowdStrike.ODSScheduledScan.cloud_pup_adware_level_detection | Number | Potentially unwanted programs (PUPs) Adware detection level. |
| CrowdStrike.ODSScheduledScan.cloud_pup_adware_level_prevention | Number | Potentially unwanted programs (PUPs) Adware prevention level. |
Command Example
!cs-falcon-ods-create-scheduled-scan host_groups=7471ba0636b34cbb8c65fae7979a6a9b schedule_interval=daily schedule_start_timestamp=tomorrow cpu_priority=Highest scan_inclusions=*
Context Example
{
"CrowdStrike": {
"ODSScan": {
"cid": "20879a8064904ecfbb62c118a6a19411",
"cloud_ml_level_detection": 2,
"cloud_ml_level_prevention": 2,
"cpu_priority": 5,
"created_by": "f7acf1bd5d3d4b40afe77546cbbaefde",
"created_on": "2023-06-11T13:23:10.564070276Z",
"deleted": false,
"host_groups": [
"7471ba0636b34cbb8c65fae7979a6a9b"
],
"id": "7d08d9a3088f49b3aa20efafc355aef0",
"initiated_from": "cloud_scheduled",
"last_updated": "2023-06-11T13:23:10.564070276Z",
"max_duration": 2,
"max_file_size": 60,
"metadata": [
{
"host_id": "046761c46ec84f40b27b6f79ce7cd32c",
"last_updated": "2023-06-11T13:23:10.564070276Z"
}
],
"pause_duration": 2,
"policy_setting": [
26439818674573,
26439818674574,
26439818675074,
26405458936702,
26405458936703,
26405458936707,
26439818675124,
26439818675125,
26439818675157,
26439818675158,
26439818675182,
26439818675183,
26439818675190,
26439818675191,
26439818675196,
26439818675197,
26439818675204,
26439818675205,
26405458936760,
26405458936761,
26405458936793,
26405458936794,
26405458936818,
26405458936819,
26405458936825,
26405458936826,
26405458936832,
26405458936833,
26405458936840,
26405458936841,
26456998543793,
26456998544045,
26456998543652,
26456998543653,
26456998543656,
26456998543654,
26456998543950,
26456998543963
],
"preemption_priority": 15,
"quarantine": true,
"scan_inclusions": [
"*"
],
"schedule": {
"interval": 1,
"start_timestamp": "2023-06-12T13:23"
},
"sensor_ml_level_detection": 2,
"sensor_ml_level_prevention": 2,
"cloud_pup_adware_level_prevention": 1,
"cloud_pup_adware_level_detection": 1,
"status": "scheduled"
}
}
}
Human Readable Output
Scheduled Scan Created
Scan ID 7d08d9a3088f49b3aa20efafc355aef0
cs-falcon-ods-delete-scheduled-scan
Delete ODS scheduled scans.
Base Command
cs-falcon-ods-delete-scheduled-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | Comma-separated list of scheduled scan IDs to delete. | Optional |
| filter | Valid CS-Falcon-FQL filter to delete scans by. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-ods-delete-scheduled-scan ids=9acf0c069d3d4a5b82badb170966e77c
Human Readable Output
Deleted Scans
Scan ID 9acf0c069d3d4a5b82badb170966e77c
cs-falcon-list-identity-entities
List identity entities.
Base Command
cs-falcon-list-identity-entities
Input
| Argument Name | Description | Required |
|---|---|---|
| type | API type. Possible values are: USER, ENDPOINT. | Required |
| sort_key | The key to sort by. Possible values are: RISK_SCORE, PRIMARY_DISPLAY_NAME, SECONDARY_DISPLAY_NAME, MOST_RECENT_ACTIVITY, ENTITY_ID. | Optional |
| sort_order | The sort order. Possible values are: DESCENDING, ASCENDING. Default is ASCENDING. | Optional |
| entity_id | A comma-separated list of entity IDs to look for. | Optional |
| primary_display_name | A comma-separated list of primary display names to filter by. | Optional |
| secondary_display_name | A comma-separated list of secondary display names to filter by. | Optional |
| max_risk_score_severity | The maximum risk score severity to filter by. Possible values are: NORMAL, MEDIUM, HIGH. | Optional |
| min_risk_score_severity | The minimum risk score severity to filter by. Possible values are: NORMAL, MEDIUM, HIGH. | Optional |
| enabled | Whether to get only enabled or disabled identity entities. Possible values are: true, false. | Optional |
| Email to filter by. | Optional | |
| next_token | The hash for the next page. | Optional |
| page_size | The maximum number of items to fetch per page. The maximum value allowed is 1000. Default is 50. | Optional |
| page | The page number. Default is 1. | Optional |
| limit | The maximum number of identity entities to list. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IDPEntity.Ishuman | Boolean | Whether the identity entity is human made. |
| CrowdStrike.IDPEntity.Isprogrammatic | Boolean | Whether the identity entity is programmatic made. |
| CrowdStrike.IDPEntity.Isadmin | String | Whether the identity entity is admin made. |
| CrowdStrike.IDPEntity.Primarydisplayname | String | The identity entity primary display name. |
| CrowdStrike.IDPEntity.Riskfactors.Type | Unknown | The identity entity risk factor type. |
| CrowdStrike.IDPEntity.Riskfactors.Severity | Unknown | The identity entity risk factor severity. |
| CrowdStrike.IDPEntity.Riskscore | Number | The identity entity risk score. |
| CrowdStrike.IDPEntity.Riskscoreseverity | String | The identity entity risk score severity. |
| CrowdStrike.IDPEntity.Secondarydisplayname | String | The identity entity secondary display name. |
| CrowdStrike.IDPEntity.Emailaddresses | String | The identity entity email address. |
cs-falcon-cspm-list-policy-details
Given a CSV list of policy IDs, returns detailed policy information.
Base Command
cs-falcon-cspm-list-policy-details
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_ids | Comma-separated list of policy IDs to look for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.CSPMPolicy.ID | Integer | The policy ID. |
| CrowdStrike.CSPMPolicy.CreatedAt | Date | The creation date. |
| CrowdStrike.CSPMPolicy.UpdatedAt | Date | The update date. |
| CrowdStrike.CSPMPolicy.DeletedAt | Date | The deletion date. |
| CrowdStrike.CSPMPolicy.description | String | The policy description. |
| CrowdStrike.CSPMPolicy.policy_statement | String | The policy statement. |
| CrowdStrike.CSPMPolicy.policy_remediation | String | The policy remediation. |
| CrowdStrike.CSPMPolicy.cloud_service_subtype | String | The cloud service subtype. |
| CrowdStrike.CSPMPolicy.cloud_document | String | The cloud document. |
| CrowdStrike.CSPMPolicy.mitre_attack_cloud_matrix | String | URL to the MITRE attack tactics. |
| CrowdStrike.CSPMPolicy.mitre_attack_cloud_subtype | String | URL to the MITRE attack techniques. |
| CrowdStrike.CSPMPolicy.alert_logic | String | The alert logic. |
| CrowdStrike.CSPMPolicy.api_command | String | The API command. |
| CrowdStrike.CSPMPolicy.cli_command | String | The CLI command. |
| CrowdStrike.CSPMPolicy.cloud_platform_type | String | The cloud platform type. |
| CrowdStrike.CSPMPolicy.cloud_service_type | String | The cloud service type. |
| CrowdStrike.CSPMPolicy.default_severity | String | The default severity. |
| CrowdStrike.CSPMPolicy.cis_benchmark_ids | Array | The CIS benchmark IDs. |
| CrowdStrike.CSPMPolicy.nist_benchmark_ids | Array | The NIST benchmark IDs. |
| CrowdStrike.CSPMPolicy.pci_benchmark_ids | Array | The PCI benchmark IDs. |
| CrowdStrike.CSPMPolicy.policy_type | String | The policy type. |
| CrowdStrike.CSPMPolicy.tactic_url | String | The tactic URL. |
| CrowdStrike.CSPMPolicy.technique_url | String | The technique URL. |
| CrowdStrike.CSPMPolicy.tactic | String | The tactic used. |
| CrowdStrike.CSPMPolicy.technique | String | The technique used. |
| CrowdStrike.CSPMPolicy.tactic_id | String | The tactic ID. |
| CrowdStrike.CSPMPolicy.technique_id | String | The technique ID. |
| CrowdStrike.CSPMPolicy.attack_types | Array | The attack types. |
| CrowdStrike.CSPMPolicy.asset_type_id | Integer | The asset type ID. |
| CrowdStrike.CSPMPolicy.cloud_asset_type | String | The cloud asset type. |
| CrowdStrike.CSPMPolicy.is_remediable | Boolean | Whether the policy is remediable or not. |
| CrowdStrike.CSPMPolicy.is_enabled | Boolean | Whether the policy is enabled or not. |
| CrowdStrike.CSPMPolicy.account_scope | String | The account scope. |
Command Example
!cs-falcon-cspm-list-policy-details policy_ids=1,2
Context Example
{
"CrowdStrike": {
"CSPMPolicy": [
{
"CreatedAt": "2020-08-18T08:30:21.760579Z",
"DeletedAt": null,
"ID": 1,
"UpdatedAt": "2023-06-21T18:47:44.371539Z",
"account_scope": "",
"alert_logic": "1. List all IAM users.|\n2. Filter on users with active access keys.|\n3. Filter on access keys that have not been rotated in 90 days.|\n4. Alert on each user.",
"api_command": "ListUsers, ListAccessKeys",
"asset_type_id": 8,
"attack_types": [
"Credential policy violation"
],
"cis_benchmark_ids": [
108,
641,
740
],
"cli_command": "aws2 iam list-users, aws2 iam list-access-keys",
"cloud_asset_type": "user",
"cloud_document": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html",
"cloud_platform_type": "aws",
"cloud_service_subtype": "Access Keys",
"cloud_service_type": "IAM",
"default_severity": "informational",
"description": "Because IAM access keys are long-term credentials, as time goes on, the risk of these keys being exposed is increased.\n\nKeys are often left on old servers, accidentally published through Git, or stolen from developer machines. The longer the keys are valid, the more likely they are to be discovered in one of these places. By ensuring keys are rotated at least every 90 days, you can be confident that if those keys are discovered, they cannot be abused.",
"is_enabled": true,
"is_remediable": false,
"mitre_attack_cloud_matrix": "https://attack.mitre.org/tactics/TA0006/",
"mitre_attack_cloud_subtype": "https://attack.mitre.org/techniques/T1528/",
"nist_benchmark_ids": [
2,
3,
281,
941
],
"pci_benchmark_ids": [
120
],
"policy_remediation": "Step 1. From the AWS Console, navigate to the IAM page.|\nStep 2. Locate and click on the offending IAM User.|\nStep 3. Click on the Security Credentials tab.|\nStep 4. Navigate to the Access Keys section and choose between making the access key inactive, deleting the key, or rotating the key.",
"policy_statement": "IAM user access key active longer than 90 days",
"policy_type": "Configuration",
"tactic": "Credential Access",
"tactic_id": "TA0006",
"tactic_url": "https://attack.mitre.org/tactics/TA0006/",
"technique": "Steal Application Access Token",
"technique_id": "T1528",
"technique_url": "https://attack.mitre.org/techniques/T1528/"
},
{
"CreatedAt": "2022-10-19T15:00:00Z",
"DeletedAt": null,
"ID": 2,
"UpdatedAt": "2023-07-20T19:14:47.972998Z",
"account_scope": "",
"alert_logic": "1. List Launch Configurations.|\n2. Decode Base64-encoded User Data field.|\n3. Search for strings indicating credentials are present.|\n4. Alert on each instance.",
"api_command": "DescribeLaunchConfigurations",
"asset_type_id": 81,
"cis_benchmark_ids": [
714
],
"cisa_benchmark_ids": [
16
],
"cli_command": "aws autoscaling describe-launch-configurations",
"cloud_asset_type": "launchconfig",
"cloud_document": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html",
"cloud_platform_type": "aws",
"cloud_service_type": "Auto Scaling",
"default_severity": "informational",
"description": "EC2 instance data is used to pass start up information into the EC2 instance. This User Data must not contain any sort of credentials. Instead, use an IAM Instance Profile assigned to the instance to grant access to other AWS Services.",
"is_enabled": true,
"is_remediable": false,
"iso_benchmark_ids": [
10,
15,
62,
63
],
"mitre_attack_cloud_matrix": "https://attack.mitre.org/tactics/TA0006/",
"mitre_attack_cloud_subtype": "https://attack.mitre.org/techniques/T1552/005/",
"nist_benchmark_ids": [
16,
65,
66,
531
],
"policy_remediation": "Step 1. From the console navigate to the EC2 page.|\nStep 2. From the sub-menu, click on 'Launch Configurations' under 'Auto Scaling'.|\nStep 3. Select the offending launch configuration.|\nStep 4. Scroll down to the 'Details' section and click 'View user data'.|\nStep 5. Validate whether or not any credentials are present.|\nStep 6. If credentials are present, re-create the launch configuration without exposing any credentials in the user data field.",
"policy_statement": "Auto Scaling group launch configuration User Data with potential credentials exposed",
"policy_type": "Configuration",
"soc2_benchmark_ids": [
15,
17
],
"tactic": "Credential Access",
"tactic_id": "TA0006",
"tactic_url": "https://attack.mitre.org/tactics/TA0006/",
"technique": "Unsecured Credentials: Cloud Instance Metadata API",
"technique_id": "T1552.005",
"technique_url": "https://attack.mitre.org/techniques/T1552/005/"
}
]
}
}
Human Readable Output
CSPM Policy Details
Id Description Policy Statement Policy Remediation Cloud Service Subtype Cloud Platform Type Cloud Service Type Default Severity Policy Type Tactic Technique 1 Because IAM access keys are long-term credentials, as time goes on, the risk of these keys being exposed is increased.
Keys are often left on old servers, accidentally published through Git, or stolen from developer machines. The longer the keys are valid, the more likely they are to be discovered in one of these places. By ensuring keys are rotated at least every 90 days, you can be confident that if those keys are discovered, they cannot be abused.IAM user access key active longer than 90 days Step 1. From the AWS Console, navigate to the IAM page.|
Step 2. Locate and click on the offending IAM User.|
Step 3. Click on the Security Credentials tab.|
Step 4. Navigate to the Access Keys section and choose between making the access key inactive, deleting the key, or rotating the key.Access Keys aws IAM informational Configuration Credential Access Steal Application Access Token 2 EC2 instance data is used to pass start up information into the EC2 instance. This User Data must not contain any sort of credentials. Instead, use an IAM Instance Profile assigned to the instance to grant access to other AWS Services. Auto Scaling group launch configuration User Data with potential credentials exposed Step 1. From the console navigate to the EC2 page.|
Step 2. From the sub-menu, click on ‘Launch Configurations’ under ‘Auto Scaling’.|
Step 3. Select the offending launch configuration.|
Step 4. Scroll down to the ‘Details’ section and click ‘View user data’.|
Step 5. Validate whether or not any credentials are present.|
Step 6. If credentials are present, re-create the launch configuration without exposing any credentials in the user data field.aws Auto Scaling informational Configuration Credential Access Unsecured Credentials: Cloud Instance Metadata API
cs-falcon-cspm-list-service-policy-settings
Returns information about current policy settings.
Base Command
cs-falcon-cspm-list-service-policy-settings
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_id | The policy ID. | Optional |
| cloud_platform | The cloud provider. Possible values are: aws, gcp, azure. Default is aws. | Optional |
| service | Service type to filter by. | Optional |
| limit | The maximum number of entities to list. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.CSPMPolicySetting.is_remediable | Boolean | Whether the policy setting is remediable or not. |
| CrowdStrike.CSPMPolicySetting.created_at | String | The creation date. |
| CrowdStrike.CSPMPolicySetting.updated_at | String | The update date. |
| CrowdStrike.CSPMPolicySetting.policy_id | Integer | The policy ID. |
| CrowdStrike.CSPMPolicySetting.name | String | The policy setting name. |
| CrowdStrike.CSPMPolicySetting.policy_type | String | The policy type. |
| CrowdStrike.CSPMPolicySetting.cloud_service_subtype | String | The cloud service subtype. |
| CrowdStrike.CSPMPolicySetting.cloud_service | String | The cloud service. |
| CrowdStrike.CSPMPolicySetting.cloud_service_friendly | String | The cloud friendly service. |
| CrowdStrike.CSPMPolicySetting.cloud_asset_type | String | The cloud asset type. |
| CrowdStrike.CSPMPolicySetting.cloud_asset_type_id | Integer | The cloud asset type ID. |
| CrowdStrike.CSPMPolicySetting.cloud_provider | String | The cloud provider. |
| CrowdStrike.CSPMPolicySetting.default_severity | String | The default severity. |
| CrowdStrike.CSPMPolicySetting.policy_timestamp | Date | The policy timestamp. |
| CrowdStrike.CSPMPolicySetting.policy_settings | Array | An array that holds policy settings. |
| CrowdStrike.CSPMPolicySetting.policy_settings.account_id | String | The account ID correlated to the policy. |
| CrowdStrike.CSPMPolicySetting.policy_settings.regions | Array | The regions in which the policy is configured. |
| CrowdStrike.CSPMPolicySetting.policy_settings.severity | String | The severity of the policy. |
| CrowdStrike.CSPMPolicySetting.policy_settings.enabled | Boolean | Whether the policy settings are enabled or not. |
| CrowdStrike.CSPMPolicySetting.policy_settings.tag_excluded | Boolean | Whether the tag is excluded or not. |
| CrowdStrike.CSPMPolicySetting.cis_benchmark | Array | An array of CIS benchmark details. |
| CrowdStrike.CSPMPolicySetting.cis_benchmark.id | Integer | The CIS benchmark ID. |
| CrowdStrike.CSPMPolicySetting.cis_benchmark.benchmark_short | String | The CIS benchmark shortname. |
| CrowdStrike.CSPMPolicySetting.cis_benchmark.recommendation_number | String | The CIS benchmark recommendation number. |
| CrowdStrike.CSPMPolicySetting.pci_benchmark | Array | An array of PCI benchmark details. |
| CrowdStrike.CSPMPolicySetting.pci_benchmark.id | Integer | The PCI benchmark ID. |
| CrowdStrike.CSPMPolicySetting.pci_benchmark.benchmark_short | String | The PCI benchmark shortname. |
| CrowdStrike.CSPMPolicySetting.pci_benchmark.recommendation_number | String | The PCI benchmark recommendation number. |
| CrowdStrike.CSPMPolicySetting.nist_benchmark | Array | An array of NIST benchmark details. |
| CrowdStrike.CSPMPolicySetting.nist_benchmark.id | Integer | The NIST benchmark ID. |
| CrowdStrike.CSPMPolicySetting.nist_benchmark.benchmark_short | String | The NIST benchmark shortname. |
| CrowdStrike.CSPMPolicySetting.nist_benchmark.recommendation_number | String | The NIST benchmark recommendation number. |
| CrowdStrike.CSPMPolicySetting.attack_types | Array | The attack types. |
Command Example
!cs-falcon-cspm-list-service-policy-settings limit=2
Context Example
{
"CrowdStrike": {
"CSPMPolicySetting": [
{
"cis_benchmark": [
{
"benchmark_short": "CIS Controls v8",
"id": 722,
"recommendation_number": "3.11"
}
],
"cloud_asset_type": "filesystem",
"cloud_asset_type_id": 107,
"cloud_provider": "aws",
"cloud_service": "efs",
"cloud_service_friendly": "EFS",
"cloud_service_subtype": "N/A",
"created_at": "2022-08-02T22:17:56.53081Z",
"default_severity": "informational",
"fql_policy": "aws_encrypted:['true']+aws_kms_key_id:['']",
"is_remediable": false,
"name": "EFS File System is encrypted without CMK",
"nist_benchmark": [
{
"benchmark_short": "NIST 800-53 REV 5",
"id": 932,
"recommendation_number": "SC-8(1)"
},
{
"benchmark_short": "NIST 800-53 REV 5",
"id": 989,
"recommendation_number": "SC-28(1)"
}
],
"pci_benchmark": [
{
"benchmark_short": "PCI DSS v3.2.1",
"id": 41,
"recommendation_number": "3.4"
}
],
"policy_id": 1,
"policy_settings": [
{
"account_id": "537409938058",
"enabled": true,
"regions": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3"
],
"severity": "informational",
"tag_excluded": false
}
],
"policy_timestamp": "0001-01-01T00:00:00Z",
"policy_type": "Configuration",
"updated_at": "2023-07-19T17:31:45.372476Z"
},
{
"cis_benchmark": [
{
"benchmark_short": "CIS 1.4.0 AWS Foundations",
"id": 143,
"recommendation_number": "4.13"
}
],
"cloud_asset_type": "awsaccount",
"cloud_asset_type_id": 116,
"cloud_provider": "aws",
"cloud_service": "awsaccount",
"cloud_service_friendly": "CloudWatch",
"cloud_service_subtype": "Route Table",
"created_at": "2023-01-04T19:57:23.897865Z",
"default_severity": "informational",
"is_remediable": false,
"iso_benchmark": [
{
"benchmark_short": "ISO",
"id": 25,
"recommendation_number": "5.25"
}
],
"name": "CloudWatch log metric filter and alarm missing for changes to route tables",
"nist_benchmark": [
{
"benchmark_short": "NIST 800-53 REV 5",
"id": 184,
"recommendation_number": "AU-6(1)"
},
{
"benchmark_short": "NIST 800-53 REV 5",
"id": 183,
"recommendation_number": "AU-6"
}
],
"pci_benchmark": [
{
"benchmark_short": "PCI DSS v4.0",
"id": 428,
"recommendation_number": "10.4.1"
}
],
"policy_id": 2,
"policy_settings": [
{
"account_id": "537409938058",
"enabled": true,
"regions": [
"af-south-1",
"ap-east-1"
],
"severity": "informational",
"tag_excluded": false
}
],
"policy_timestamp": "0001-01-01T00:00:00Z",
"policy_type": "Configuration",
"soc2_benchmark": [
{
"benchmark_short": "TSC 2017 rev 2020",
"id": 27,
"recommendation_number": "CC7.3"
}
],
"updated_at": "2023-09-18T16:11:58.369644Z"
}
]
}
}
Human Readable Output
CSPM Policy Settings
Policy Id Is Remediable Remediation Summary Name Policy Type Cloud Service Subtype Cloud Service Default Severity 1 false EFS File System is encrypted without CMK Configuration N/A efs informational 2 false CloudWatch log metric filter and alarm missing for changes to route tables Configuration Route Table awsaccount informational
cs-falcon-cspm-update-policy_settings
Updates a policy setting. Can be used to override policy severity or to disable a policy entirely.
Base Command
cs-falcon-cspm-update-policy_settings
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_id | Policy ID to be updated. | Required |
| account_id | Cloud account ID to impact. | Optional |
| enabled | Flag indicating if this policy is enabled. Possible values are: false, true. Default is true. | Optional |
| regions | A comma-separated list of regions where this policy is enforced. | Optional |
| severity | Policy severity value. Possible values are: critical, high, medium, informational. | Optional |
| tag_excluded | Tag exclusion flag. Possible values are: false, true. | Optional |
Context Output
There is no context output for this command.
Command Example
!cs-falcon-cspm-update-policy_settings policy_id=1 enabled=true regions="eu-central-1,eu-central-2" severity=high tag_excluded=false
Human Readable Output
Policy 1 was updated successfully
cs-falcon-resolve-identity-detection
Perform actions on identity detection alerts.
Base Command
cs-falcon-resolve-identity-detection
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of IDs of the alerts to update. | Required |
| assign_to_name | Assign the specified detections to a user based on their username. | Optional |
| assign_to_user_id | Assign the specified detections to a user based on their user ID (Email). | Optional |
| assign_to_uuid | Assign the specified detections to a user based on their UUID. | Optional |
| append_comment | Appends a new comment to any existing comments for the specified detections. | Optional |
| add_tag | Add a tag to the specified detections. | Optional |
| remove_tag | Remove a tag from the specified detections. | Optional |
| update_status | Update the status of the alert to the specified value. Possible values are: new, in_progress, closed, reopened. | Optional |
| unassign | Whether to unassign any assigned users to the specified detections. Possible values are: false, true. | Optional |
| show_in_ui | If true, displays the detection in the UI. Possible values are: false, true. | Optional |
Context Output
There is no context output for this command.
cs-falcon-resolve-mobile-detection
Perform actions on mobile detection alerts.
Base Command
cs-falcon-resolve-mobile-detection
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of IDs of the alerts to update. | Required |
| assign_to_name | Assign the specified detections to a user based on their username. | Optional |
| assign_to_user_id | Assign the specified detections to a user based on their user ID (Email). | Optional |
| assign_to_uuid | Assign the specified detections to a user based on their UUID. | Optional |
| append_comment | Appends a new comment to any existing comments for the specified detections. | Optional |
| add_tag | Add a tag to the specified detections. | Optional |
| remove_tag | Remove a tag from the specified detections. | Optional |
| update_status | Update the status of the alert to the specified value. Possible values are: new, in_progress, closed, reopened. | Optional |
| unassign | Whether to unassign any assigned users to the specified detections. Possible values are: false, true. | Optional |
| show_in_ui | If true, displays the detection in the UI. Possible values are: false, true. | Optional |
Context Output
There is no context output for this command.
cs-falcon-list-users
List users.
Base Command
cs-falcon-list-users
Input
| Argument Name | Description | Required |
|---|---|---|
| id | A comma-separated list of IDs (UUIDs) of specific users to list. | Optional |
| filter | The filter expression that should be used to limit the results. FQL syntax. Available values: assigned_cids, cid, first_name, last_name, name, uid. Example: “first_name:’John’”. | Optional |
| offset | The integer offset to start retrieving records from. | Optional |
| limit | The maximum number of records to return. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Users.uuid | String | The user’s UUID. |
| CrowdStrike.Users.cid | String | The customer ID. |
| CrowdStrike.Users.uid | String | The user’s ID. |
| CrowdStrike.Users.first_name | String | The user’s first name. |
| CrowdStrike.Users.last_name | String | The user’s last name. |
| CrowdStrike.Users.last_login_at | String | The timestamp of the user’s last login. |
| CrowdStrike.Users.created_at | String | The timestamp of the user’s creation. |
cs-falcon-get-ioarules
Get IOA Rules.
Base Command
cs-falcon-get-ioarules
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_ids | A comma-separated list of rule IDs to get IOA rules for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.IOARules.instance_id | String | The IOA rule’s instance ID. |
| CrowdStrike.IOARules.customer_id | String | The customer ID. |
| CrowdStrike.IOARules.action_label | String | The IOA rule’s action label. |
| CrowdStrike.IOARules.comment | String | The IOA rule’s comment. |
| CrowdStrike.IOARules.committed_on | String | The timestamp of the IOA rule’s commitment. |
| CrowdStrike.IOARules.created_by | String | The IOA rule’s creator. |
| CrowdStrike.IOARules.created_on | String | The timestamp of the IOA rule’s creation. |
| CrowdStrike.IOARules.deleted | Boolean | Whether the IOA rule is in a deleted status. |
| CrowdStrike.IOARules.description | String | The IOA rule’s description. |
| CrowdStrike.IOARules.disposition_id | String | The disposition ID used by the IOA rule. |
| CrowdStrike.IOARules.enabled | Boolean | Whether the IOA rule is enabled. |
| CrowdStrike.IOARules.field_values | String | The IOA rule’s field values. |
| CrowdStrike.IOARules.instance_version | String | The IOA rule’s instance version. |
| CrowdStrike.IOARules.magic_cookie | String | The IOA rule’s magic cookie. |
| CrowdStrike.IOARules.modified_by | String | The last user who modified the IOA rule. |
| CrowdStrike.IOARules.modified_on | String | The timestamp of the IOA rule’s last modification. |
| CrowdStrike.IOARules.name | String | The IOA rule name. |
| CrowdStrike.IOARules.pattern_id | String | The IOA rule’s pattern ID. |
| CrowdStrike.IOARules.pattern_severity | String | The IOA rule’s pattern severity. |
| CrowdStrike.IOARules.rulegroup_id | String | The IOA rule’s rule group ID. |
| CrowdStrike.IOARules.ruletype_id | String | The IOA rule’s rule type ID. |
| CrowdStrike.IOARules.ruletype_name | String | The IOA rule’s rule type name. |
| CrowdStrike.IOARules.version_ids | String | The IOA rule’s version ID. |
Using Spotlight APIs
Spotlight identifies and gives info about specific vulnerabilities on your hosts using the Falcon sensor.
Required API client scope
To access the Spotlight API, your API client must be assigned the spotlight-vulnerabilities:read scope.
Validating API data
The Falcon sensor continuously monitors hosts for any changes and reports them as they occur.
Depending on the timing of requests, Spotlight APIs can return values that are different from those shown by the Falcon console or an external source.
There are other factors that can cause differences between API responses and other data sources.
API query syntax
If an API query doesn’t exactly match the query used on the Spotlight Vulnerabilities page, the values might differ.
Expired vulnerabilities in Spotlight APIs
If a host is deleted or inactive for 45 days, the status of vulnerabilities on that host changes to expired. Expired vulnerabilities are removed from Spotlight after 3 days.
Expired vulnerabilities are only visible in API responses and are not included in reports or the Falcon console.
An external data source might not use the same data retention policy, which can lead to discrepancies with Spotlight APIs. For more info, see Data retention in Spotlight [https://falcon.crowdstrike.com/login/?next=%2Fdocumentation%2F43%2Ffalcon-spotlight-overview#data-retention-in-spotlight].
The following commands uses the Spotlight API
cs-falcon-spotlight-search-vulnerability
Retrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.
Base Command
cs-falcon-spotlight-search-vulnerability
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Limit the vulnerabilities returned to specific properties. Each value must be enclosed in single quotes and placed immediately after the colon with no space. For example, ‘filter=status:’open’+cve.id:[‘CVE-2013-3900’,’CVE-2021-1675’]’. | Optional |
| aid | A comma-separated list of unique agent identifiers (AIDs) of a sensor. | Optional |
| cve_id | A comma-separated list of unique identifiers for a vulnerability as cataloged in the National Vulnerability Database (NVD). This filter supports multiple values and negation. | Optional |
| cve_severity | A comma-separated list of severities of the CVE. The possible values are: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN, or NONE. | Optional |
| tags | A comma-separated list of names of a tag assigned to a host. Retrieve tags from Host Tags APIs. | Optional |
| status | Status of a vulnerability. This filter supports multiple values and negation. The possible values are: open, closed, reopen, expired. | Optional |
| platform_name | Operating system platform. This filter supports negation. The possible values are: Windows, Mac, Linux. | Optional |
| host_group | A comma-separated list of unique system-assigned IDs of a host group. Retrieve the host group ID from Host Group APIs. | Optional |
| host_type | A comma-separated list of types of hosts a sensor is running on. | Optional |
| last_seen_within | Filter for vulnerabilities based on the number of days since a host last connected to CrowdStrike Falcon. Enter a numeric value from 3 to 45 to indicate the number of days to look back. For example, last_seen_within:10. | Optional |
| is_suppressed | Indicates if the vulnerability is suppressed by a suppression rule. Possible values are: true, false. | Optional |
| display_remediation_info | Display remediation information type of data to be returned for each vulnerability entity. Possible values are: True, False. Default is True. | Optional |
| display_evaluation_logic_info | Whether to return logic information type of data for each vulnerability entity. Possible values are: True, False. Default is True. | Optional |
| display_host_info | Whether to return host information type of data for each vulnerability entity. Possible values are: True, False. Default is False. | Optional |
| limit | The maximum number of items to return (1-2500). Use next_token to retrieve additional pages. Default is 50. |
Optional |
| next_token | Pagination cursor from a previous run’s CrowdStrike.VulnerabilityNextToken output. Provide this value to fetch the next page of vulnerabilities. Expires in 120 seconds. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Vulnerability.id | String | Unique system-assigned ID of the vulnerability. |
| CrowdStrike.Vulnerability.cid | String | Unique system-generated customer identifier (CID) of the account. |
| CrowdStrike.Vulnerability.aid | String | Unique agent identifier (AID) of the sensor where the vulnerability was found. |
| CrowdStrike.Vulnerability.created_timestamp | Date | UTC date and time of when the vulnerability was created in Spotlight. |
| CrowdStrike.Vulnerability.updated_timestamp | Date | UTC date and time of the last update made on the vulnerability. |
| CrowdStrike.Vulnerability.status | String | Vulnerability’s current status. Possible values are: open, closed, reopen, or expired. |
| CrowdStrike.Vulnerability.apps.product_name_version | String | Name and version of the product associated with the vulnerability. |
| CrowdStrike.Vulnerability.apps.sub_status | String | Status of each product associated with the vulnerability. Possible values are: open, closed, or reopen. |
| CrowdStrike.Vulnerability.apps.remediation.ids | String | Remediation ID of each product associated with the vulnerability. |
| CrowdStrike.Vulnerability.host_info.hostname | String | Name of the machine. |
| CrowdStrike.Vulnerability.host_info.instance_id | String | Cloud instance ID of the host. |
| CrowdStrike.Vulnerability.host_info.service_provider_account_id | String | Cloud service provider account ID for the host. |
| CrowdStrike.Vulnerability.host_info.service_provider | String | Cloud service provider for the host. |
| CrowdStrike.Vulnerability.host_info.os_build | String | Operating system build. |
| CrowdStrike.Vulnerability.host_info.product_type_desc | String | Type of host a sensor is running on. |
| CrowdStrike.Vulnerability.host_info.local_ip | String | Device’s local IP address. |
| CrowdStrike.Vulnerability.host_info.machine_domain | String | Active directory domain name. |
| CrowdStrike.Vulnerability.host_info.os_version | String | Operating system version. |
| CrowdStrike.Vulnerability.host_info.ou | String | Active directory organizational unit name. |
| CrowdStrike.Vulnerability.host_info.site_name | String | Active directory site name. |
| CrowdStrike.Vulnerability.host_info.system_manufacturer | String | Name of the system manufacturer. |
| CrowdStrike.Vulnerability.host_info.groups.id | String | Array of host group IDs that the host is assigned to. |
| CrowdStrike.Vulnerability.host_info.groups.name | String | Array of host group names that the host is assigned to. |
| CrowdStrike.Vulnerability.host_info.tags | String | Name of a tag assigned to a host. |
| CrowdStrike.Vulnerability.host_info.platform | String | Operating system platform. This filter supports negation. |
| CrowdStrike.Vulnerability.remediation.entities.id | String | Unique ID of the remediation. |
| CrowdStrike.Vulnerability.remediation.entities.reference | String | Relevant reference for the remediation that can be used to get additional details for the remediation. |
| CrowdStrike.Vulnerability.remediation.entities.title | String | Short description of the remediation. |
| CrowdStrike.Vulnerability.remediation.entities.action | String | Expanded description of the remediation. |
| CrowdStrike.Vulnerability.remediation.entities.link | String | Link to the remediation page for the vendor. In certain cases, this field is null. |
| CrowdStrike.Vulnerability.cve.id | String | Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD). |
| CrowdStrike.Vulnerability.cve.base_score | Number | Base score of the CVE (float value between 1 and 10). |
| CrowdStrike.Vulnerability.cve.severity | String | CVSS severity rating of the vulnerability. |
| CrowdStrike.Vulnerability.cve.exploit_status | Number | Numeric value of the most severe known exploit. |
| CrowdStrike.Vulnerability.cve.exprt_rating | String | ExPRT rating assigned by CrowdStrike’s predictive AI rating system. |
| CrowdStrike.Vulnerability.cve.description | String | Brief description of the CVE. |
| CrowdStrike.Vulnerability.cve.published_date | Date | UTC timestamp with the date and time of when the vendor published the CVE. |
| CrowdStrike.Vulnerability.cve.vendor_advisory | String | Link to the vendor page where the CVE was disclosed. |
| CrowdStrike.Vulnerability.cve.exploitability_score | Number | Exploitability score of the CVE (float values from 1-4). |
| CrowdStrike.Vulnerability.cve.impact_score | Number | Impact score of the CVE (float values from 1-6). |
| CrowdStrike.Vulnerability.cve.vector | String | Textual representation of the metric values used to score the vulnerability. |
| CrowdStrike.Vulnerability.cve.remediation_level | String | CVSS remediation level of the vulnerability (U = Unavailable, or O = Official fix). |
| CrowdStrike.Vulnerability.cve.cisa_info.is_cisa_kev | Boolean | Whether to filter for vulnerabilities that are in the CISA Known Exploited Vulnerabilities (KEV) catalog. |
| CrowdStrike.Vulnerability.cve.cisa_info.due_date | Date | Date before which CISA mandates subject organizations to patch the vulnerability. |
| CrowdStrike.Vulnerability.cve.spotlight_published_date | Date | UTC timestamp with the date and time Spotlight enabled coverage for the vulnerability. |
| CrowdStrike.Vulnerability.cve.actors | String | Adversaries associated with the vulnerability. |
| CrowdStrike.Vulnerability.cve.name | String | The vulnerability name. |
| CrowdStrike.VulnerabilityNextToken | String | Pagination cursor returned by CrowdStrike when more results are available. Pass this value back as the `next_token` argument on the next invocation. Absent when no more pages exist. Expires in 120 seconds. |
Command Example
cs-falcon-spotlight-search-vulnerability filter=status:['open','closed'] cve_id=CVE-2021-2222 cve_severity='LOW,HIGH' display_host_info=false display_evaluation_logic_info=false display_remediation_info=false limit=1
Context Example
{
"resources": [
{
"id": "id_num",
"cid": "cid_num",
"aid": "aid_num",
"created_timestamp": "2021-07-13T01:12:57Z",
"updated_timestamp": "2022-10-27T18:32:21Z",
"status": "open",
"apps": [
{
"product_name_version": "product",
"sub_status": "open",
"remediation": {
"ids": [
"1234"
]
},
"evaluation_logic": {
"id": "1234"
}
}
],
"suppression_info": {
"is_suppressed": false
},
"cve": {
"id": "CVE-2021-2222",
"base_score": 5.5,
"severity": "MEDIUM",
"exploit_status": 0,
"exprt_rating": "LOW",
"remediation_level": "O",
"cisa_info": {
"is_cisa_kev": false
},
"spotlight_published_date": "2021-05-10T17:08:00Z",
"description": "description\n",
"published_date": "2021-02-25T23:15:00Z",
"vendor_advisory": [
"web address"
],
"exploitability_score": 1.8,
"impact_score": 3.6,
"vector": "vendor"
}
}
],
"VulnerabilityNextToken": "next_token"
}
Human Readable Output
| CVE ID | CVE Severity | CVE Base Score | CVE Published Date | CVE Impact Score | CVE Exploitability Score | CVE Vector |
|---|---|---|---|---|---|---|
| CVE-2021-2222 | LOW | 5.5 | 2021-05-10T17:08:00Z | 3.6 | 0 | vendor |
cs-falcon-spotlight-list-host-by-vulnerability
Retrieve vulnerability details for a specific ID and host. Supported with the CrowdStrike Spotlight license.
Base Command
cs-falcon-spotlight-list-host-by-vulnerability
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | Maximum number of items to return (1-5000). Default is 50. | Optional |
| cve_ids | Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD). This filter supports multiple values and negation. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.VulnerabilityHost.id | String | Unique system-assigned ID of the vulnerability. |
| CrowdStrike.VulnerabilityHost.cid | String | Unique system-generated customer identifier (CID) of the account. |
| CrowdStrike.VulnerabilityHost.aid | String | Unique agent identifier (AID) of the sensor where the vulnerability was found. |
| CrowdStrike.VulnerabilityHost.created_timestamp | Date | UTC date and time of when the vulnerability was created in Spotlight. |
| CrowdStrike.VulnerabilityHost.updated_timestamp | Date | UTC date and time of the last update made on the vulnerability. |
| CrowdStrike.VulnerabilityHost.status | String | Vulnerability’s current status. Possible values are: open, closed, reopen, or expired. |
| CrowdStrike.VulnerabilityHost.apps.product_name_version | String | Name and version of the product associated with the vulnerability. |
| CrowdStrike.VulnerabilityHost.apps.sub_status | String | Status of each product associated with the vulnerability. Possible values are: open, closed, or reopen. |
| CrowdStrike.VulnerabilityHost.apps.remediation.ids | String | Remediation ID of each product associated with the vulnerability. |
| CrowdStrike.VulnerabilityHost.apps.evaluation_logic.id | String | Unique system-assigned ID of the vulnerability evaluation logic. |
| CrowdStrike.VulnerabilityHost.suppression_info.is_suppressed | Boolean | Indicates if the vulnerability is suppressed by a suppression rule. |
| CrowdStrike.VulnerabilityHost.host_info.hostname | String | Name of the machine. |
| CrowdStrike.VulnerabilityHost.host_info.local_ip | String | Device’s local IP address. |
| CrowdStrike.VulnerabilityHost.host_info.machine_domain | String | Active directory domain name. |
| CrowdStrike.VulnerabilityHost.host_info.os_version | String | Operating system version. |
| CrowdStrike.VulnerabilityHost.host_info.ou | String | Active directory organizational unit name. |
| CrowdStrike.VulnerabilityHost.host_info.site_name | String | Active directory site name. |
| CrowdStrike.VulnerabilityHost.host_info.system_manufacturer | String | Name of the system manufacturer. |
| CrowdStrike.VulnerabilityHost.host_info.platform | String | Operating system platform. This filter supports negation. |
| CrowdStrike.VulnerabilityHost.host_info.instance_id | String | Cloud instance ID of the host. |
| CrowdStrike.VulnerabilityHost.host_info.service_provider_account_id | String | Cloud service provider account ID for the host. |
| CrowdStrike.VulnerabilityHost.host_info.service_provider | String | Cloud service provider for the host. |
| CrowdStrike.VulnerabilityHost.host_info.os_build | String | Operating system build. |
| CrowdStrike.VulnerabilityHost.host_info.product_type_desc | String | Type of host a sensor is running on. |
| CrowdStrike.VulnerabilityHost.cve.id | String | Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD). |
Command Example
cs-falcon-spotlight-list-host-by-vulnerability cve_ids=CVE-2021-2222
Context Example
{
{
"id": "id",
"cid": "cid",
"aid": "aid",
"created_timestamp": "2021-09-16T15:12:42Z",
"updated_timestamp": "2022-10-19T00:54:43Z",
"status": "open",
"apps": [
{
"product_name_version": "prod",
"sub_status": "open",
"remediation": {
"ids": [
"id"
]
},
"evaluation_logic": {
"id": "id"
}
}
],
"suppression_info": {
"is_suppressed": false
},
"host_info": {
"hostname": "host",
"local_ip": "10.128.0.7",
"machine_domain": "",
"os_version": "version",
"ou": "",
"site_name": "",
"system_manufacturer": "manufactor",
"tags": [],
"platform": "Windows",
"instance_id": "instance id",
"service_provider_account_id": "id",
"service_provider": "id",
"os_build": "os build",
"product_type_desc": "Server"
},
"cve": {
"id": "CVE-20212-2222"
}
}
}
Human Readable Output
| CVE ID | Host Info hostname | Host Info os Version | Host Info Product Type Desc | Host Info Local IP | Host Info ou | Host Info Machine Domain | Host Info Site Name | CVE Exploitability Score | CVE Vector |
|---|---|---|---|---|---|---|---|---|---|
| CVE-20212-2222 | host | 1 | Server | ip | site | 5.5 |
cs-falcon-list-cnapp-alerts
Returns a list of CNAPP alerts. Used for debugging fetch-assets.
Base Command
cs-falcon-list-cnapp-alerts
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | The filter to use for the query. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.CnappAlert.containers_impacted_count | String | The number of containers impacted by the alert. |
| CrowdStrike.CnappAlert.containers_impacted_ids | Array | The list of the ids of containers impacted by the alert. |
| CrowdStrike.CnappAlert.detection_description | String | The description of the alert. |
| CrowdStrike.CnappAlert.detection_event_simple_name | String | The simple name of the alert. |
| CrowdStrike.CnappAlert.detection_name | String | The name of the alert. |
| CrowdStrike.CnappAlert.first_seen_timestamp | String | The first time the alert was seen. |
| CrowdStrike.CnappAlert.last_seen_timestamp | String | The last time the alert was seen. |
| CrowdStrike.CnappAlert.severity | String | The severity of the alert. |
Command Example
cs-falcon-list-cnapp-alerts
Context Example
{
"severity": "Critical",
"first_seen_timestamp": "2025-11-24T11:04:03Z",
"last_seen_timestamp": "2025-11-26T10:18:35Z",
"detection_name": "PotentialKernelTampering",
"detection_event_simple_name": "BPFCommandIssued",
"detection_description": "some decription.",
"containers_impacted_count": "1",
"containers_impacted_ids": ["id1", "id2"],
}
Human Readable Output
| severity | first_seen_timestamp | last_seen_timestamp | detection_name | detection_event_simple_name | detection_description | containers_impacted_count | containers_impacted_ids |
|---|---|---|---|---|---|---|---|
| Critical | 2025-11-24T11:04:03Z | 2025-11-26T10:18:35Z | PotentialKernelTampering | BPFCommandIssued | alert description. | 1 | test |
Troubleshooting
- In the different fetch query configuration parameters such as “Endpoint Detections fetch query” and “Endpoint Incidents fetch query”, to query for multiple values in the same field use the following format:
field:['value1','value2','value3'].- For example, filtering by “severity_name” equal to “Medium”, “High”, or “Critical” can be achieved by specifying
severity_name:['Medium','High','Critical']
- For example, filtering by “severity_name” equal to “Medium”, “High”, or “Critical” can be achieved by specifying
- When encountering the error “400 - Reason: Bad Request: Invalid element in the request”, ensure the integration instance is configured correctly and verify the command arguments.
- For example, the error appears when using the ID of a detection prior to the Raptor release (legacy API) in an integration configured to run with Raptor.
- When experiencing connectivity or authorization errors in Cortex XSOAR 8 or Cortex XSIAM, ensure that the IP addresses associated with the relevant CrowdStrike Falcon region are added to the allow list for the Cortex tenant. For detailed instructions, refer to the Egress section or search for Egress in the product documentation:
- When encountering HTTP 429 errors from CrowdStrike Falcon, install custom engine on the the Cortex tenant and use it in the configuration of the integration instance:
- When encountering missing incidents in Cortex XSOAR, make sure that the ‘Fetch Type’ integration parameter includes the type of the missing incidents.
- Optional types are:
- Endpoint Incident
- Endpoint Detection
- IDP Detection
- Indicator of Misconfiguration
- Indicator of Attack
- Mobile Detection
- On-Demand Scans Detection
- OFP Detection
- Notes:
- Records from the detection endpoint of the CrowdStrike Falcon UI could be of types: “Endpoint Detection” and “OFP Detection”.
- For CNAPP Alerts, you will need one of the following subscriptions:
- Falcon Cloud Security with Containers CNAPP
- Falcon Cloud Security CNAPP
- Falcon Cloud Security with Containers Runtime Protection
- Falcon for Managed Containers Runtime Protection
- Falcon Cloud Security Proactive
- Falcon Cloud Security with Containers
- Falcon for Managed Containers
- Optional types are:
cs-falcon-list-case-summaries
Lists case summaries.
Base Command
cs-falcon-list-case-summaries
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of case IDs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.Case.id | String | The ID of the case. |
| CrowdStrike.Case.name | String | The name of the case. |
| CrowdStrike.Case.created_timestamp | Date | The date and time the case was created. |
| CrowdStrike.Case.status | String | The status of the case. |
| CrowdStrike.Case.version | String | The version of the case. |
| CrowdStrike.Case.description | String | The description of the case. |
| CrowdStrike.Case.severity | String | The severity of the case. |
| CrowdStrike.Case.assigned_to | String | The name of the user assigned to the case. |
| CrowdStrike.Case.tags | String | The tags of the case. |
cs-falcon-add-case-tag
Adds tags to the specified case.
Base Command
cs-falcon-add-case-tag
Input
| Argument Name | Description | Required |
|---|---|---|
| tags | A comma-separated list of tags. | Required |
| id | The ID of the case the tags will be added to. | Required |
Context Output
There is no context output for this command.
cs-falcon-get-evidence-for-case
Get evidence for a specific case.
Base Command
cs-falcon-get-evidence-for-case
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the case to retrieve evidence for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.CaseEvidence.alerts | Array | The alerts associated with the case. |
| CrowdStrike.CaseEvidence.events | Array | The events associated with the case. |
| CrowdStrike.CaseEvidence.leads | Array | The leads associated with the case. |
cs-falcon-resolve-case
Resolves or updates a case.
Base Command
cs-falcon-resolve-case
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the case to resolve. | Required |
| status | The status to set for the case. Possible values are: new, in_progress, closed, reopened. | Optional |
| assigned_to_uuid | A UUID of a user to assign the case to. | Optional |
| description | A new description for the case. | Optional |
| remove_user_assignment | Whether to remove case assignment from the current user. If set to true and assigned_to_uuid is not provided, the case becomes unassigned. If set to false and assigned_to_uuid is provided, the case is reassigned to the specified user UUID. If set to true and assigned_to_uuid is provided, the case is reassigned to the specified user UUID. If this field is omitted and assigned_to_uuid is provided, the case is reassigned to the specified user UUID. Possible values are: true, false. Default is false. |
Optional |
| severity | The new case severity rating (10-100). | Optional |
| template_id | The unique ID of the template to apply to the case. | Optional |
| name | The new name for the case. | Optional |
Context Output
There is no context output for this command.
cs-falcon-delete-case-tag
Deletes a tag from the specified case.
Base Command
cs-falcon-delete-case-tag
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the case the tags will be deleted from. | Required |
| tag | The tag to delete. | Required |
Context Output
There is no context output for this command.
cs-falcon-search-ngsiem-events
Search NGSIEM historical events. Requires NGSIEM scope with read and write permissions.
Base Command
cs-falcon-search-ngsiem-events
Input
| Argument Name | Description | Required |
|---|---|---|
| repository | The repository to run the query against. . Possible values are: search-all, third-party, falcon_for_it_view, forensics_view, investigate_view. Default is search-all. |
Optional |
| query | The CQL query to use for the search. Note: Double quotes (“) and backslashes () in the queryString must be escaped with a backslash to ensure they are properly interpreted. Example: query=”#event_simpleName = "Event_name"””, For more details see: https://library.humio.com/data-analysis/syntax.html. | Required |
| start | The start of the search window, based on the event timestamp. Note: ‘end’ must be later than ‘start’. If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND). Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000). . |
Optional |
| end | The end of the search window, based on the event timestamp. Note: ‘end’ must be later than ‘start’. If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND). Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000). . |
Optional |
| around_event_id | The ID of the event to search around. Must be provided together with around_timestamp. | Optional |
| around_number_events_before | Number of events to return before the target event. Requires around_event_id and around_timestamp. | Optional |
| around_number_events_after | Number of events to return after the target event. Requires around_event_id and around_timestamp. | Optional |
| around_timestamp | Timestamp for around search. Must be provided together with around_event_id. | Optional |
| ingest_start | The start of the search window, based on the event ingesttimestamp. Note: ‘ingest_end’ must be later than ‘ingest_start’. If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND). Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000). . |
Optional |
| ingest_end | The end of the search window, based on the event ingesttimestamp. Note: ‘ingest_end’ must be later than ‘ingest_start’. If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND). Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000). . |
Optional |
| use_ingest_time | When true, the server uses ingest_start/ingest_end as the query window. when false (or not set), it uses start/end. If both windows are provided, results are constrained by BOTH (AND). Possible values are: true, false. | Optional |
| limit | Maximum number of events to return. Ignored when around_number_events_before or around_number_events_after parameters are specified. Default is 50. | Optional |
| interval_in_seconds | Interval between polling attempts in seconds. To prevent search timeouts, set this value within the 60–90 second range. Default is 60. | Optional |
| timeout_in_seconds | Timeout for polling in seconds. Default is 600. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CrowdStrike.NGSiemEvent | Array | The list of all events returned from the search. |
| CrowdStrike.NGSiemEvent.id | String | The event ID. |
| CrowdStrike.NGSiemEvent.timestamp | String | Event timestamp. |
Command Example
!cs-falcon-search-ngsiem-events query="#event_simpleName = \"Event_name\"" repository="search-all" start="1d" end="now" limit=2 interval=60 timeout=120
!cs-falcon-search-ngsiem-events query="*" repository="search-all" start="7d" end="now" around_event_id="aaa" around_number_events_before=1 around_number_events_after=1 around_timestamp=1700000000000 interval=60 timeout=120
!cs-falcon-search-ngsiem-events query="id=aaaaa_anchor_event" repository="search-all" ingest_start=1700000000000 ingest_end=1700000002000 use_ingest_time=true limit=2 interval=60 timeout=120
Context Example
{
"CrowdStrike": {
"NGSiemEvent": [
{
"@id": "aaaaa_event_1",
"@timestamp": 1700000000000,
"@ingesttimestamp": "1700000001000",
"id": "aaaaa_event_1",
"event_simpleName": "Event_APIActivityAuditEvent"
},
{
"@id": "aaaaa_event_2",
"@timestamp": 1700000002000,
"@ingesttimestamp": "1700000003000",
"id": "aaaaa_event_2",
"event_simpleName": "Event_APIActivityAuditEvent"
}
]
}
}
Workflow Commands
The following commands are available from the CrowdStrike Falcon integration. Note that for these commands, the required API client scope is: Workflows - Read and Write.
cs-falcon-list-workflow-definitions
Lists workflow definitions from CrowdStrike Falcon.
Base Command
cs-falcon-list-workflow-definitions
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Filters results using a query in Falcon Query Language (FQL). For more information, see the FQL Syntax Documentation: https://www.falconpy.io/Usage/Falcon-Query-Language.html. For a list of available properties, see Workflow execution FQL filters - https://falcon.crowdstrike.com/documentation/page/z028de1a/fusion-workflow-apis#zec519e3. | Optional |
| definition_id | The workflow definition ID to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| activity_id | The activity ID to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| name | The workflow name to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| description | The workflow description to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| offset | The offset to start retrieving records from. Default is 0. | Optional |
| limit | The maximum number of records to return. Max is 500. Default is 50. | Optional |
| sort | A comma-separated list of properties to sort by in the format property.direction (for example name.desc, time.asc, created_at.desc). | Optional |
Context Output
There is no context output for this command.
Command example
!cs-falcon-list-workflow-definitions limit=5
cs-falcon-workflow-execute
Executes an on-demand workflow. Use cs-falcon-list-workflow-definitions to find workflows to run. Note: This command executes on-demand workflows only.
Base Command
cs-falcon-workflow-execute
Input
| Argument Name | Description | Required |
|---|---|---|
| definition_id | A comma-separated list of workflow definition IDs to execute. Either definition_id or name must be provided. | Optional |
| name | The workflow name to execute. Either definition_id or name must be provided. | Optional |
| execution_cid | A comma-separated list of CID(s) to execute the workflow on. | Optional |
| key | The key used for deduplication of workflow executions. If not set, a new UUID is used. | Optional |
| source_event_url | The URL of the source that triggered the workflow execution. | Optional |
| body | The JSON body to pass to the workflow execution. Can be an empty object {}. | Optional |
Context Output
There is no context output for this command.
Command example
!cs-falcon-workflow-execute definition_id="abc123" body="{}"
cs-falcon-list-workflow-executions
Lists workflow executions from CrowdStrike Falcon. Use cs-falcon-workflow-execute to find executions IDs.
Base Command
cs-falcon-list-workflow-executions
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Filters results using a query in Falcon Query Language (FQL). For more information, see the FQL Syntax Documentation: https://www.falconpy.io/Usage/Falcon-Query-Language.html. For a list of available properties, see Workflow execution FQL filters - https://falcon.crowdstrike.com/documentation/page/z028de1a/fusion-workflow-apis#zec519e3. | Optional |
| definition_id | The workflow definition ID to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| definition_name | The workflow definition name to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| execution_id | The execution ID to filter by. If ‘filter’ is provided, this argument is ignored. | Optional |
| offset | The offset to start retrieving records from. Default is 0. | Optional |
| limit | The maximum number of records to return. Max is 500. Default is 50. | Optional |
| sort | A comma-separated list of properties to sort by in the format property.direction (for example name.desc, time.asc, created_at.desc). | Optional |
Context Output
There is no context output for this command.
Command example
!cs-falcon-list-workflow-executions limit=10
cs-falcon-list-workflow-execution-results
Gets detailed results for specific workflow executions. Use cs-falcon-list-workflow-executions to find execution IDs.
Base Command
cs-falcon-list-workflow-execution-results
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of workflow execution IDs to retrieve results for. | Required |
Context Output
There is no context output for this command.
Command example
!cs-falcon-list-workflow-execution-results ids="exec_id_1,exec_id_2"
cs-falcon-workflow-execution-action
Performs an action (cancel or resume) on one or more workflow executions. Use cs-falcon-list-workflow-execution-results to find execution activity status.
Base Command
cs-falcon-workflow-execution-action
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | A comma-separated list of workflow execution IDs to perform the action on. Get the workflow execution ID using cs-falcon-list-workflow-executions. | Required |
| action_name | The action to perform on the workflow executions. Possible values are: cancel, resume. | Required |
Context Output
There is no context output for this command.
Command example
!cs-falcon-workflow-execution-action ids="exec_id_1" action_name="cancel"
<~PLATFORM>
License Requirements
The following configuration parameters require the Cortex XSIAM license:
- Fetch events
The following configuration parameters require Cortex XSIAM with the Exposure Management add-on:
- Fetch assets and vulnerabilities
The following configuration parameters require one of these licenses: Cortex XSIAM or Agentix:
- Fetch incidents
</~PLATFORM>
Configuration parameters
legacy_version— Use legacy APIurl— Server URL (e.g., https://api.crowdstrike.com) (required)credentials— Client IDclient_id— Client IDsecret— SecretReliability— Source ReliabilityisFetch— Fetch incidentsfetch_incidents_or_detections— Incident Fetch typesincidentType— Incident typefetch_time— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)incidents_per_fetch— Max incidents per fetchincidentFetchInterval— Incidents Fetch Intervallook_back— Advanced: Time in minutes to look back when fetching incidents and detectionsfetch_query— Endpoint Detections filter queryidp_detections_fetch_query— IDP Detections filter querymobile_detections_fetch_query— Mobile Detections filter queryiom_fetch_query— IOM filter queryioa_fetch_query— IOA filter queryon_demand_fetch_query— Detections from On-Demand Scans filter queryofp_detection_fetch_query— OFP Detections filter querythird_party_detection_fetch_query— Third Party Detection fetch queryngsiem_detection_fetch_query— NGSIEM Detection fetch queryautomated_leads_fetch_query— NGSIEM automated leads fetch queryngsiem_cases_fetch_query— NGSIEM cases fetch queryngsiem_incidents_fetch_query— NGSIEM incidents fetch queryrecon_fetch_query— Recon filter querymirror_direction— Mirroring Directionclose_incident— Close Mirrored XSOAR Incidentclose_in_cs_falcon— Close Mirrored CrowdStrike Falcon Incident or Detectionreopen_statuses— Reopen StatusesisFetchEvents— Fetch eventsfetch_events_or_detections— Event Fetch typeseventFetchInterval— Events Fetch Intervallook_back_xsiam— Advanced: Time in minutes to look back when fetching events and detectionsisFetchAssets— Fetch assets and vulnerabilitiesfetch_assets_type— Fetch Asset typesassetsFetchInterval— Assets Fetch Intervalinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (98)
-
cs-device-ran-onDeprecatedReturns a list of device IDs an indicator ran on.
-
cs-falcon-add-case-tagAdds tags to the specified case.
-
cs-falcon-add-host-group-membersAdd host group members.
-
cs-falcon-apply-quarantine-file-actionApply action to quarantined files by file IDs or filter.
-
cs-falcon-batch-upload-custom-iocUploads a batch of indicators.
-
cs-falcon-contain-hostContains containment for a specified host. When contained, a host can only communicate with the CrowdStrike cloud and any IPs specified in your containment policy.
-
cs-falcon-create-host-groupCreate a host group.
-
cs-falcon-create-ioa-exclusionCreate an IOA exclusion.
-
cs-falcon-create-ml-exclusionCreate an ML exclusion.
-
cs-falcon-cspm-list-policy-detailsGiven a CSV list of policy IDs, returns detailed policy information.
-
cs-falcon-cspm-list-service-policy-settingsReturns information about current policy settings.
-
cs-falcon-cspm-update-policy_settingsUpdates a policy setting. Can be used to override policy severity or to disable a policy entirely.
-
cs-falcon-delete-case-tagDeletes a tag from the specified case.
-
cs-falcon-delete-custom-iocDeletes a monitored indicator.
-
cs-falcon-delete-fileDeletes a file based on the provided ID or name. Can delete only one file at a time.
-
cs-falcon-delete-host-groupsDeletes the requested host groups.
-
cs-falcon-delete-ioa-exclusionDelete the IOA exclusions by ID.
-
cs-falcon-delete-iocDeprecatedDeprecated. Use the cs-falcon-delete-custom-ioc command instead.
-
cs-falcon-delete-ml-exclusionDelete the ML exclusions by ID.
-
cs-falcon-delete-scriptDeletes a custom-script based on the provided ID. Can delete only one script at a time.
-
cs-falcon-device-count-iocThe number of hosts that observed the provided IOC.
-
cs-falcon-device-ran-onReturns a list of device IDs an indicator ran on.
-
cs-falcon-get-behaviorDeprecatedSearches for and fetches the behavior that matches the query. Deprecated - No replacement available.
-
cs-falcon-get-custom-iocGets the full definition of one or more indicators that you are watching.
-
cs-falcon-get-evidence-for-caseGet evidence for a specific case.
-
cs-falcon-get-extracted-fileGets the RTR extracted file contents for the specified session and SHA256 hash.
-
cs-falcon-get-fileReturns files based on the provided IDs. These files are used for the RTR 'put' command.
-
cs-falcon-get-ioarulesGet IOA Rules.
-
cs-falcon-get-iocDeprecatedDeprecated. Use the cs-falcon-get-custom-ioc command instead.
-
cs-falcon-get-scriptReturns custom scripts based on the provided ID. Used for the RTR 'runscript' command.
-
cs-falcon-lift-host-containmentLifts containment on the host, which returns its network communications to normal. When lift_filesystem_containment_all is set to true, lifts filesystem containment instead.
-
cs-falcon-list-case-summariesLists case summaries.
-
cs-falcon-list-cnapp-alertsReturns a list of CNAPP alerts. Used for debugging fetch-assets.
-
cs-falcon-list-detection-summariesLists detection summaries.
-
cs-falcon-list-filesReturns a list of put-file IDs that are available for the user in the 'put' command. Due to an API limitation, the maximum number of files returned is 100.
-
cs-falcon-list-host-filesGets a list of files for the specified RTR session on a host.
-
cs-falcon-list-host-group-membersGets the list of host group members.
-
cs-falcon-list-host-groupsList the available host groups.
-
cs-falcon-list-identity-entitiesList identity entities.
-
cs-falcon-list-quarantined-fileGet quarantine file metadata by specified IDs or filter.
-
cs-falcon-list-scriptsReturns a list of custom script IDs that are available for the user in the 'runscript' command.
-
cs-falcon-list-usersList users.
-
cs-falcon-list-workflow-definitionsLists workflow definitions from CrowdStrike Falcon.
-
cs-falcon-list-workflow-execution-resultsGets detailed results for specific workflow executions. Use cs-falcon-list-workflow-executions to find execution IDs.
-
cs-falcon-list-workflow-executionsLists workflow executions from CrowdStrike Falcon. Use cs-falcon-workflow-execute to find executions IDs.
-
cs-falcon-ods-create-scanCreate an ODS scan and wait for the results.
-
cs-falcon-ods-create-scheduled-scanCreate an ODS scheduled scan.
-
cs-falcon-ods-delete-scheduled-scanDelete ODS scheduled scans.
-
cs-falcon-ods-query-malicious-filesRetrieve ODS malicious file details.
-
cs-falcon-ods-query-scanRetrieve ODS scan details.
-
cs-falcon-ods-query-scan-hostRetrieve ODS scan host details.
-
cs-falcon-ods-query-scheduled-scanRetrieve ODS scheduled scan details.
-
cs-falcon-process-detailsRetrieves the details of a process, according to the process ID that is running or that previously ran.
-
cs-falcon-processes-ran-onGet processes associated with a given IOC.
-
cs-falcon-refresh-sessionRefresh a session timeout on a single host.
-
cs-falcon-remove-host-group-membersRemove host group members.
-
cs-falcon-resolve-caseResolves or updates a case.
-
cs-falcon-resolve-detectionResolves and updates a detection using the provided arguments. At least one optional argument must be passed, otherwise no change will take place. Note that IDP detections are not supported.
-
cs-falcon-resolve-identity-detectionPerform actions on identity detection alerts.
-
cs-falcon-resolve-mobile-detectionPerform actions on mobile detection alerts.
-
cs-falcon-rtr-kill-processExecute an active responder kill command on a single host.
-
cs-falcon-rtr-list-network-statsExecutes an RTR active-responder netstat command to get a list of network status and protocol statistics across the given host.
-
cs-falcon-rtr-list-processesExecutes an RTR active-responder ps command to get a list of active processes across the given host.
-
cs-falcon-rtr-list-scheduled-tasksExecutes an RTR active-responder netstat command to get a list of scheduled tasks across the given host. This command is valid only for Windows hosts.
-
cs-falcon-rtr-read-registryExecutes an RTR active-responder read registry keys command across the given hosts. This command is valid only for Windows hosts.
-
cs-falcon-rtr-remove-fileBatch executes an RTR active-responder remove file across the hosts mapped to the given batch ID.
-
cs-falcon-rtr-retrieve-fileGets the RTR extracted file contents for the specified file path.
-
cs-falcon-run-commandSends commands to hosts.
-
cs-falcon-run-get-commandBatch executes 'get' command across hosts to retrieve files.
-
cs-falcon-run-scriptRuns a script on the agent host.
-
cs-falcon-search-custom-iocsReturns a list of your uploaded IOCs that match the search criteria.
-
cs-falcon-search-detectionSearch for details of specific detections, either using a filter query, or by providing the IDs of the detections.
-
cs-falcon-search-deviceSearches for a device that matches the query.
-
cs-falcon-search-ioa-exclusionGet a list of IOA exclusions by specifying their IDs or a filter.
-
cs-falcon-search-iocsDeprecatedDeprecated. Use the cs-falcon-search-custom-iocs command instead.
-
cs-falcon-search-ml-exclusionGet a list of ML exclusions by specifying their IDs, value, or a specific filter.
-
cs-falcon-search-ngsiem-eventsSearch NGSIEM historical events. Requires NGSIEM scope with read and write permissions.
-
cs-falcon-spotlight-list-host-by-vulnerabilityRetrieve vulnerability details for a specific ID and host. Supported with the CrowdStrike Spotlight license.
-
cs-falcon-spotlight-search-vulnerabilityRetrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.
-
cs-falcon-status-commandGets the status of a command executed on a host.
-
cs-falcon-status-get-commandRetrieves the status of the specified batch 'get' command.
-
cs-falcon-update-custom-iocUpdates an indicator for CrowdStrike to monitor.
-
cs-falcon-update-host-groupUpdates a host group.
-
cs-falcon-update-ioa-exclusionUpdates an IOA exclusion. At least one argument is required in addition to the id argument.
-
cs-falcon-update-iocDeprecatedDeprecated. Use the cs-falcon-update-custom-ioc command instead.
-
cs-falcon-update-ml-exclusionUpdates an ML exclusion. At least one argument is required in addition to the id argument.
-
cs-falcon-upload-custom-iocUploads an indicator for CrowdStrike to monitor.
-
cs-falcon-upload-fileUploads a file to the CrowdStrike cloud. (Can be used for the RTR 'put' command).
-
cs-falcon-upload-iocDeprecatedDeprecated. Use the cs-falcon-upload-custom-ioc command instead.
-
cs-falcon-upload-scriptUploads a script to Falcon CrowdStrike.
-
cs-falcon-workflow-executeExecutes an on-demand workflow. Use cs-falcon-list-workflow-definitions to find workflows to run. Note: This command executes on-demand workflows only.
-
cs-falcon-workflow-execution-actionPerforms an action (cancel or resume) on one or more workflow executions. Use cs-falcon-list-workflow-execution-results to find execution activity status.
-
cveRetrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.
-
endpointReturns information about an endpoint. Does not support regex.
-
get-mapping-fieldsReturns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes. Note that this command is supported in Cortex XSOAR only.
-
get-modified-remote-dataGets the list of incidents and detections that were modified since the last update time. This method is used for debugging purposes. The get-modified-remote-data command is used as part of the Mirroring feature that was introduced in Cortex XSOAR version 6.1. Note that this command is supported in Cortex XSOAR only.
-
get-remote-dataGets remote data from a remote incident or detection. This method does not update the current incident or detection, and should be used for debugging purposes only. Note that this command is supported in Cortex XSOAR only.
-
update-remote-systemUpdates the remote incident or detection with local incident or detection changes. This method is only used for debugging purposes and will not update the current incident or detection. Note that this command is supported in Cortex XSOAR only.
import demistomock as demisto # noqa: F401 from CommonServerPython import * from ContentClientApiModule import * """ IMPORTS """ import base64 import email import hashlib import json from collections.abc import Callable from enum import Enum, IntEnum from threading import Timer from typing import Any import urllib.parse import requests import asyncio import aiohttp import gzip # Disable insecure warnings import urllib3 from gql import Client, gql from gql.transport.requests import RequestsHTTPTransport urllib3.disable_warnings() """ GLOBALS/PARAMS """ VENDOR = "CrowdStrike" PRODUCT = "Falcon_Event" CNAPP_PRODUCT = "Falcon_CNAPP" SPOTLIGHT_VULN_PRODUCT = "Falcon_Spotlight_Vulnerabilities" SPOTLIGHT_ASSETS_PRODUCT = "Falcon_Spotlight_Assets" INTEGRATION_NAME = "CrowdStrike Falcon" # Incidents Type names - use for debugging and context save. IDP_DETECTION = "IDP detection" MOBILE_DETECTION = "MOBILE detection" ON_DEMAND_SCANS_DETECTION = "On-Demand Scans detection" OFP_DETECTION = "OFP detection" NGSIEM_DETECTION = "ngsiem_detection" NGSIEM_INCIDENT = "ngsiem_incident" NGSIEM_AUTOMATED_LEAD = "ngsiem_automated_lead" NGSIEM_CASE = "ngsiem_case" THIRD_PARTY_DETECTION = "thirdparty_detection" IOA_DETECTION = "ioa_detection" RECON_NOTIFICATION = "Recon notifications" # Fetch type names as they appear in the .yml instance configurations DETECTION_FETCH_TYPES = ["Detections", "Endpoint Detection"] IDP_DETECTION_FETCH_TYPE = "IDP Detection" MOBILE_DETECTION_FETCH_TYPE = "Mobile Detection" ON_DEMAND_SCANS_DETECTION_TYPE = "On-Demand Scans Detection" OFP_DETECTION_TYPE = "OFP Detection" IOM_FETCH_TYPE = "Indicator of Misconfiguration" IOA_FETCH_TYPE = "Indicator of Attack" NGSIEM_DETECTION_FETCH_TYPE = "NGSIEM Detection" NGSIEM_INCIDENT_FETCH_TYPE = "NGSIEM Incident (XDR Alert)" NGSIEM_AUTOMATED_LEADS_FETCH_TYPE = "NGSIEM Automated Lead" NGSIEM_CASES_FETCH_TYPE = "NGSIEM Case" THIRD_PARTY_DETECTION_FETCH_TYPE = "Third Party Detection" RECON_FETCH_TYPE = "Recon notifications" ENDPOINT_DETECTION = "detection" SUPPORTED_DETECTIONS_TYPES = [ IDP_DETECTION_FETCH_TYPE, ON_DEMAND_SCANS_DETECTION_TYPE, OFP_DETECTION_TYPE, NGSIEM_DETECTION_FETCH_TYPE, NGSIEM_INCIDENT_FETCH_TYPE, NGSIEM_AUTOMATED_LEADS_FETCH_TYPE, NGSIEM_CASES_FETCH_TYPE, THIRD_PARTY_DETECTION_FETCH_TYPE, ] PARAMS = demisto.params() PROXY = PARAMS.get("proxy", False) CLIENT_ID = PARAMS.get("credentials", {}).get("identifier") or PARAMS.get("client_id") SECRET = PARAMS.get("credentials", {}).get("password") or PARAMS.get("secret") # Remove trailing slash to prevent wrong URL path to service SERVER = PARAMS["url"].removesuffix("/") # Should we use SSL USE_SSL = not PARAMS.get("insecure", False) # How much time before the first fetch to retrieve incidents FETCH_TIME = "now" if demisto.command() == "fetch-events" else PARAMS.get("fetch_time", "3 days") MAX_FETCH_SIZE = 10000 MAX_FETCH_DETECTION_PER_API_CALL = 10000 # fetch limit for get ids call - detections MAX_FETCH_DETECTION_PER_API_CALL_ENTITY = 1000 # fetch limit for get entities call - detections MAX_FETCH_SPOTLIGHT_ASSETS = 5000 # Below the 5000 server-side maximum to keep payloads under XSOAR's auto-file threshold. MAX_SPOTLIGHT_VULNERABILITY_PAGE_SIZE = 2500 MAX_PENDING_TASKS_PER_SEVERITY = 5 # Backpressure: max concurrent pending XSIAM send tasks per severity stream SPOTLIGHT_LOOKBACK_DAYS = 100 # Only fetch vulnerabilities updated within this many days (bounds dataset size) RECON_API_LIMIT = 100 MAX_FETCH_RECON = 100 # Spotlight vulnerability severity levels for parallel fetching SPOTLIGHT_SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "NONE", "UNKNOWN"] BYTE_CREDS = f"{CLIENT_ID}:{SECRET}".encode() # Headers to be sent in requests HEADERS = { "Content-Type": "application/json", "Accept": "application/json", "Authorization": f"Basic {base64.b64encode(BYTE_CREDS).decode()}", } # Note: True life time of token is actually 30 mins TOKEN_LIFE_TIME = 28 INCIDENTS_PER_FETCH = int(PARAMS.get("incidents_per_fetch", 15)) DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" DETECTION_DATE_FORMAT = IOM_DATE_FORMAT = RECON_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" DEFAULT_TIMEOUT = 30 DEFAULT_INTERVAL = 60 DEFAULT_TIMEOUT_NGSIEM_SEARCH = 600 DEFAULT_TIMEOUT_ON_GENERIC_HTTP_REQUEST = 60 TOTAL_RETRIES_ON_ENRICHMENT = 0 TIMEOUT_ON_ENRICHMENT = 15 """ KEY DICTIONARY """ LEGACY_DETECTIONS_BASE_KEY_MAP = { "device.hostname": "System", "device.cid": "CustomerID", "hostinfo.domain": "MachineDomain", "detection_id": "ID", "created_timestamp": "ProcessStartTime", "max_severity": "MaxSeverity", "show_in_ui": "ShowInUi", "status": "Status", "first_behavior": "FirstBehavior", "last_behavior": "LastBehavior", "max_confidence": "MaxConfidence", } DETECTIONS_BASE_KEY_MAP = { "device.hostname": "System", "device.cid": "CustomerID", "device.hostinfo.domain": "MachineDomain", "composite_id": "ID", "created_timestamp": "ProcessStartTime", "severity": "MaxSeverity", "show_in_ui": "ShowInUi", "status": "Status", "confidence": "MaxConfidence", } DETECTIONS_BEHAVIORS_KEY_MAP = { "filename": "FileName", "scenario": "Scenario", "md5": "MD5", "sha256": "SHA256", "ioc_type": "IOCType", "ioc_value": "IOCValue", "cmdline": "CommandLine", "user_name": "UserName", "behavior_id": "ID", "alleged_filetype": "AllegedFiletype", "confidence": "Confidence", "description": "Description", "display_name": "DisplayName", "filepath": "Filepath", "parent_md5": "ParentMD5", "parent_sha256": "ParentSHA256", "pattern_disposition": "PatternDisposition", "pattern_disposition_details": "PatternDispositionDetails", "tactic": "Tactic", "tactic_id": "TacticID", "technique": "Technique", "technique_id": "TechniqueId", } IOC_KEY_MAP = { "type": "Type", "value": "Value", "policy": "Policy", "source": "Source", "share_level": "ShareLevel", "expiration": "Expiration", "description": "Description", "created_on": "CreatedTime", "created_by": "CreatedBy", "modified_on": "ModifiedTime", "modified_by": "ModifiedBy", "id": "ID", "platforms": "Platforms", "action": "Action", "severity": "Severity", "tags": "Tags", "mobile_action": "MobileAction", } IOC_HEADERS = [ "ID", "Action", "MobileAction", "Severity", "Type", "Value", "Expiration", "CreatedBy", "CreatedTime", "Description", "ModifiedBy", "ModifiedTime", "Platforms", "Policy", "ShareLevel", "Source", "Tags", ] IOC_DEVICE_COUNT_MAP = {"id": "ID", "type": "Type", "value": "Value", "device_count": "DeviceCount"} SEARCH_DEVICE_KEY_MAP = { "device_id": "ID", "external_ip": "ExternalIP", "local_ip": "LocalIP", "hostname": "Hostname", "os_version": "OS", "mac_address": "MacAddress", "first_seen": "FirstSeen", "last_seen": "LastSeen", "status": "Status", } SEARCH_DEVICE_VERBOSE_KEY_MAP = { "agent_load_flags": "AgentLoadFlags", "agent_local_time": "AgentLocalTime", "agent_version": "AgentVersion", "bios_manufacturer": "BiosManufacturer", "bios_version": "BiosVersion", "cid": "CID", "config_id_base": "ConfigIdBase", "config_id_build": "ConfigIdBuild", "config_id_platform": "ConfigIdPlatform", "connection_ip": "ConnectionIp", "connection_mac_address": "ConnectionMacAddress", "cpu_signature": "CpuSignature", "default_gateway_ip": "DefaultGatewayIP", "device_id": "ID", "device_policies": "DevicePolicies", "external_ip": "ExternalIP", "first_seen": "FirstSeen", "group_hash": "GroupHash", "group_name": "GroupName", "group_names": "GroupNames", "groups": "Groups", "hostname": "Hostname", "kernel_version": "KernelVersion", "last_seen": "LastSeen", "local_ip": "LocalIP", "mac_address": "MacAddress", "major_version": "MajorVersion", "meta": "Meta", "minor_version": "MinorVersion", "modified_timestamp": "ModifiedTimestamp", "os_version": "OS", "platform_id": "PlatformID", "platform_name": "PlatformName", "policies": "Policies", "product_type_desc": "ProductTypeDesc", "provision_status": "ProvisionStatus", "reduced_functionality_mode": "ReducedFunctionalityMode", "serial_number": "SerialNumber", "status": "Status", "system_manufacturer": "SystemManufacturer", "system_product_name": "SystemProductName", "tags": "Tags", } ENDPOINT_KEY_MAP = { "device_id": "ID", "local_ip": "IPAddress", "os_version": "OS", "hostname": "Hostname", "status": "Status", } """ SPLIT KEY DICTIONARY """ """ Pattern: { 'Path': 'Path to item', 'NewKey': 'Value of output key', 'Delim': 'Delimiter char', 'Index': Split Array Index } """ DETECTIONS_BEHAVIORS_SPLIT_KEY_MAP = [ {"Path": "parent_details.process_graph_id", "NewKey": "SensorID", "Delim": ":", "Index": 1}, {"Path": "parent_details.process_graph_id", "NewKey": "ParentProcessID", "Delim": ":", "Index": 2}, {"Path": "triggering_process_graph_id", "NewKey": "ProcessID", "Delim": ":", "Index": 2}, ] HOST_GROUP_HEADERS = [ "id", "name", "group_type", "description", "assignment_rule", "created_by", "created_timestamp", "modified_by", "modified_timestamp", ] """ MIRRORING DICTIONARIES & PARAMS """ STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES = {"new", "in_progress", "closed", "reopened"} CS_FALCON_DETECTION_OUTGOING_ARGS = { "status": f'Updated detection status, one of {"/".join(STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES)}' } LEGACY_CS_FALCON_DETECTION_INCOMING_ARGS = [ "status", "severity", "behaviors.tactic", "behaviors.scenario", "behaviors.objective", "behaviors.technique", "device.hostname", "detection_id", "behaviors.display_name", ] CS_FALCON_DETECTION_INCOMING_ARGS = [ "status", "severity", "tactic", "scenario", "objective", "technique", "device.hostname", "composite_id", "display_name", "tags", "comments", "assigned_to_uid", ] CS_FALCON_DETECTION_INCOMING_ARGS_IDP = ["status", "id", "tags", "comments", "assigned_to_uid"] NGSIEM_MIRRORING_FIELDS = ["status", "state"] CS_FALCON_RECON_INCOMING_ARGS = ["notification.status"] MIRROR_DIRECTION_DICT = {"None": None, "Incoming": "In", "Outgoing": "Out", "Incoming And Outgoing": "Both"} HOST_STATUS_DICT = {"online": "Online", "offline": "Offline", "unknown": "Unknown"} NO_QUARANTINED_FILES_MSG = "The arguments/filters you provided did not match any files." QUARANTINE_FILES_OUTPUT_HEADERS = [ "id", "aid", "cid", "sha256", "paths", "state", "detect_ids", "alert_ids", "hostname", "username", "date_updated", "date_created", "extracted", "release_path_for_removable_media", "primary_module", "is_on_removable_disk", "sandbox_report_id", "sandbox_report_state", ] CPU_UTILITY_INT_TO_STR_KEY_MAP = { 1: "Lowest", 2: "Low", 3: "Medium", 4: "High", 5: "Highest", } CPU_UTILITY_STR_TO_INT_KEY_MAP = {value: key for key, value in CPU_UTILITY_INT_TO_STR_KEY_MAP.items()} SCHEDULE_INTERVAL_STR_TO_INT = { "never": 0, "daily": 1, "weekly": 7, "every other week": 14, "every four weeks": 28, "monthly": 30, } TOTAL_FETCH_TYPE_XSOAR = 14 # Matches the total number of fetch types for XSOAR in the LastRunIndex class TOTAL_FETCH_TYPE_XSIAM = 6 # Matches the total number of fetch types for XSIAM in the LastRunIndex class class LastRunIndex(IntEnum): """ The last_run object is defined as a list of dictionaries. Each index in the list represents a different fetch type. The last_run object is supported only in the following scenario: The fetch_incidents command runs on XSOAR (and not on XSIAM), while the fetch_events command runs on XSIAM (and not on XSOAR). """ # Common fetch types for fetch-incidents and fetch-events. DETECTIONS = 0 _RESERVED_INCIDENTS = 1 # Formerly Endpoint Incidents. Do not reuse. IDP_DETECTIONS = 2 MOBILE_DETECTIONS = 3 ON_DEMAND_DETECTIONS = 4 OFP_DETECTION = 5 # Fetch types only for fetch-incidents IOM = 6 IOA = 7 THIRD_PARTY_DETECTIONS = 8 NGSIEM_DETECTIONS = 9 NGSIEM_INCIDENTS = 10 NGSIEM_AUTOMATED_LEADS = 11 NGSIEM_CASES = 12 RECON_NOTIFICATIONS = 13 class IncidentType(Enum): LEGACY_ENDPOINT_DETECTION = "ldt" ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION = ":ind:" # OFP was joined here since it has ':ind:' too in its id IOM_CONFIGURATIONS = "iom_configurations" IOA_TYPE_TAG = "cloud-ioa" ON_DEMAND = "ods" OFP = "ofp" THIRD_PARTY = ":thirdparty:" RECON = ":recon:" NGSIEM_DETECTION = ":ngsiem:" NGSIEM_AUTOMATED_LEAD = ":automated-lead:" NGSIEM_CASE = ":case" MIRROR_DIRECTION = MIRROR_DIRECTION_DICT.get(demisto.params().get("mirror_direction")) INTEGRATION_INSTANCE = demisto.integrationInstance() """ HELPER FUNCTIONS """ def is_detection_fetch_type_selected(selected_types: list): return any(detection_type in selected_types for detection_type in DETECTION_FETCH_TYPES) def disable_for_xsiam(): """Validates if command is not running on an unsupported Cortex platform. Raises: DemistoException: If command is being run on XSIAM. """ if is_xsiam() or is_platform(): raise DemistoException("This command is not supported on this Cortex platform.") def truncate_long_time_str(detections: List[Dict], time_key: str) -> List[Dict]: """ Truncates the time string in each detection to a maximum of 26 characters, to prevent an error when parsing the time. Args: detections (List[Dict]): The list of detections, each represented as a dictionary. time_key (str): The key in each detection dictionary that corresponds to the time string. Returns: List[Dict]: The list of detections with the time string truncated. """ for event in detections: long_time_str = event.get(time_key) if long_time_str and len(long_time_str) > 26: event[time_key] = long_time_str[:26] + "Z" return detections def modify_detection_outputs(detection): """ Modifies the detection outputs in the newer version (raptor release) to be in the same format as the legacy version. Args: detection: The detection to modify. Returns: The nested modified detection. """ behavior = {key: detection.pop(key, None) for key in DETECTIONS_BEHAVIORS_KEY_MAP} behavior.update( { "parent_details": detection.pop("parent_details", None), "triggering_process_graph_id": detection.pop("triggering_process_graph_id", None), } ) detection["behaviors"] = [behavior] return detection def error_handler(res): reason = res.reason demisto.debug(f"CrowdStrike Falcon error handler {res.status_code=} {reason=}") try: res_json = res.json() except ValueError: # Non-JSON response (common for NGSIEM errors: text/plain) body = (res.text or "").strip() # keep it short to avoid huge war-room errors body = body[:4000] raise DemistoException(f"Error in API call to CrowdStrike Falcon: code: {res.status_code} - reason: {reason}\n{body}") resources = res_json.get("resources", {}) extracted_error_message = "" if resources: if isinstance(resources, list): extracted_error_message += f"\n{resources!s}" else: for host_id, resource in resources.items(): errors = resource.get("errors", []) if isinstance(resource, dict) else "" # type: ignore[union-attr] if errors: error_message = errors[0].get("message") # type: ignore[union-attr] extracted_error_message += f"\nHost ID {host_id} - {error_message}" elif res_json.get("errors") and not extracted_error_message: errors = res_json.get("errors", []) for error in errors: extracted_error_message += f"\n{error.get('message')}" reason += extracted_error_message raise DemistoException(f"Error in API call to CrowdStrike Falcon: code: {res.status_code} - reason: {reason}") def http_request( method, url_suffix, params=None, data=None, files=None, headers=HEADERS, get_token_flag=True, no_json=False, json=None, status_code=None, timeout=None, ): """ A wrapper for requests lib to send our requests and handle requests and responses better. :param json: JSON body :type json ``dict`` or ``list`` :type method: ``str`` :param method: HTTP method for the request. :type url_suffix: ``str`` :param url_suffix: The suffix of the URL (endpoint) :type params: ``dict`` :param params: The URL params to be passed. :type data: ``str`` :param data: The body data of the request. :type headers: ``dict`` :param headers: Request headers :type get_token_flag: ``bool`` :param get_token_flag: If set to True will call get_token() :type no_json: ``bool`` :param no_json: If set to true will not parse the content and will return the raw response object for successful response :type status_code: ``int`` :param: status_code: The request codes to accept as OK. :type timeout: ``float`` :param: timeout: The timeout for the request. :return: Returns the http request response json :rtype: ``dict`` """ if get_token_flag: token = get_token() headers["Authorization"] = f"Bearer {token}" retries = 0 status_list_to_retry = [] # in case of 401,403,429 status codes we want to return the response, generate a new token and try again with retries. valid_status_codes = [200, 201, 202, 204, 401, 403, 429] else: # get_token_flag=False means that get_token_request() called http_request() with /oauth2/token, and we want to retry # to create the token in case of 429 in the first call to generic_http_request and not in the second call to avoid a # loop of calls to get_token_request(). retries = 5 # error code 401 - isn't relevant for requesting a token. # error code 403 - The IP is missing from the IP allowlist, no need to retry. status_list_to_retry = [429] valid_status_codes = [200, 201, 202, 204] demisto.debug(f"In http_request {get_token_flag=} updated retries, status_list_to_retry, valid_status_codes") headers["User-Agent"] = "PANW-XSOAR" if is_time_sensitive(): demisto.debug("Changing timeout to 15 seconds and retries to 0 due to time_sensitive=True") retries = TOTAL_RETRIES_ON_ENRICHMENT request_timeout = TIMEOUT_ON_ENRICHMENT else: request_timeout = int(timeout) if timeout else DEFAULT_TIMEOUT_ON_GENERIC_HTTP_REQUEST # Handling a case when we want to return an entry for 404 status code. if status_code: # To cover the condition when status_code is a list of status codes if isinstance(status_code, list): valid_status_codes = valid_status_codes + status_code else: valid_status_codes.append(status_code) try: res = generic_http_request( method=method, server_url=SERVER, headers=headers, url_suffix=url_suffix, data=data, files=files, params=params, proxy=PROXY, resp_type="response", verify=USE_SSL, error_handler=error_handler, json_data=json, timeout=request_timeout, ok_codes=valid_status_codes, retries=retries, status_list_to_retry=status_list_to_retry, ) demisto.debug(f"In http_request after the first call to generic_http_request {res=} {res.status_code=}") except requests.exceptions.RequestException as e: return_error(f"Error in connection to the server. Please make sure you entered the URL correctly. Exception is {e!s}.") try: if get_token_flag: # removing 401,403,429 status codes, now we want to generate a new token and try again valid_status_codes.remove(401) valid_status_codes.remove(403) valid_status_codes.remove(429) if res.status_code not in valid_status_codes: # try to create a new token if res.status_code in (401, 403, 429) and get_token_flag: demisto.debug(f"Try to create a new token because {res.status_code=}") token = get_token(new_token=True) headers["Authorization"] = f"Bearer {token}" demisto.debug(f"calling generic_http_request with retries={retries} and status_list_to_retry=[429]") # noqa: E501 res = generic_http_request( method=method, server_url=SERVER, headers=headers, url_suffix=url_suffix, data=data, files=files, params=params, proxy=PROXY, retries=5, status_list_to_retry=[429], resp_type="response", error_handler=error_handler, json_data=json, timeout=request_timeout, ok_codes=valid_status_codes, ) demisto.debug(f"In http_request after the second call to generic_http_request {res=} {res.status_code=}") return res if no_json else res.json() else: demisto.debug(f"In invalid status code and {get_token_flag=}") error_handler(res) demisto.debug("In http_request end") return res if no_json else res.json() except ValueError as exception: # type: ignore[str-bytes-safe] raise ValueError(f"Failed to parse json object from response: {exception} - {res.content}") def create_relationships(cve: dict) -> list: """ creates relationships between the cve and each actor from 'actors' field : args: cve contains the cve id and the actors field if it is exists. : return: a list of relationships by type THREAT_ACTOR. """ list_with_actors_field = [] if not cve.get("actors"): return [] for actor in cve.get("actors", {}): list_with_actors_field.append(actor) relationships_list: list[EntityRelationship] = [] # need to create entity for entity_b in list_with_actors_field: relationships_list.append( EntityRelationship( entity_a=cve.get("id"), entity_a_type=FeedIndicatorType.CVE, name=EntityRelationship.Relationships.TARGETED_BY, entity_b=entity_b, entity_b_type=ThreatIntel.ObjectsNames.THREAT_ACTOR, brand=INTEGRATION_NAME, reverse_name=EntityRelationship.Relationships.TARGETS, ) ) return relationships_list def create_dbot_Score(cve: dict, reliability: str) -> Common.DBotScore: """ Creates DBotScore CVE indicator, for get_cve_command. """ return Common.DBotScore( indicator=cve.get("id"), indicator_type=DBotScoreType.CVE, integration_name=INTEGRATION_NAME, score=Common.DBotScore.NONE, reliability=reliability, ) def create_publications(cve: dict) -> list: """ Creates publications list from CVE, while using get_cve_command. """ publications = [] if cve.get("references"): for reference in cve.get("references", {}): publications.append(Common.Publications(title="references", link=reference)) if cve.get("vendor_advisory"): for vendor_advisory in cve.get("vendor_advisory", {}): publications.append(Common.Publications(title="vendor_advisory", link=vendor_advisory)) return publications def build_query_params(query_params: dict) -> str: r""" Gets a dict of {property: value} and returns a string to use as an FQL ``q`` parameter. For example:: {} => "" {'name': 'test', 'os_name': 'WINDOWS'} => "name:'test'+os_name:'WINDOWS'" {'filename': ['a.txt']} => "filename:'a.txt'" {'filename': ['a.txt', 'b.txt']} => "filename:['a.txt','b.txt']" {'filename': []} => "" # empty list is skipped List values are unwrapped (single element) or rendered in FQL multi-value bracket notation (multiple elements). Without this, a list value would be interpolated as a Python ``repr`` (e.g. ``filename:'['a.txt']'``), which CrowdStrike's FQL parser does not match against. Single quotes inside values are escaped with a backslash so values like ``O'Brien.txt`` do not break the FQL syntax. Args: query_params: dict of property: value (value may be scalar or list). ``None`` values and empty lists are ignored. Returns: String to use as the FQL ``q`` query param (``""`` if no usable values). """ def _fql_quote(v: Any) -> str: # Escape single quotes inside the value so they don't terminate the FQL string. return "'" + str(v).replace("'", "\\'") + "'" parts: list[str] = [] for key, value in query_params.items(): if value is None: continue if isinstance(value, list): if not value: # Empty list: nothing to filter on for this key. continue if len(value) == 1: parts.append(f"{key}:{_fql_quote(value[0])}") else: joined = ",".join(_fql_quote(v) for v in value) parts.append(f"{key}:[{joined}]") else: parts.append(f"{key}:{_fql_quote(value)}") return "+".join(parts) def modify_detection_summaries_outputs(detection: dict): """ Modifies the detection summaries outputs in the new version (raptor release) to be in the same format as the legacy version. Args: detection: The detection to modify. Returns: The modified detection. """ keys_to_move = [ "pattern_disposition_details", "timestamp", "device_id", "filename", "alleged_filetype", "cmdline", "scenario", "objective", "tactic", "technique", "severity", "confidence", "ioc_type", "ioc_value", "user_name", "user_id", "control_graph_id", "triggering_process_graph_id", "sha256", "pattern_disposition", "parent_details", "md5", "filepath", ] # rename before adding to a nested dict parent_details = detection.get("parent_details", {}) parent_keys = ["sha256", "cmdline", "md5", "process_graph_id"] for key in parent_keys: if key in parent_details: new_key = f"parent_{key}" parent_details[new_key] = parent_details.pop(key) # change from a flat dict to nested dict nested_dict = {key: detection.pop(key, None) for key in keys_to_move if key in detection} nested_dict["device_id"] = detection.get("device", {}).get("device_id") detection["behaviors"] = nested_dict # change from nested to flat detection["hostinfo"] = detection.get("device", {}).get("hostinfo") # rename without moving to a nested dict detection["detection_id"] = detection.pop("composite_id", None) return detection def log_falcon_assets(log_line: str, log_type="debug", asset="Spotlight"): """Wrapper for log line for spotlight asset collector""" full_log_line = f"[Falcon Asset Collector] [{asset}] {log_line}" if log_type == "debug": demisto.debug(full_log_line) elif log_type == "info": demisto.info(full_log_line) else: demisto.error(full_log_line) def _get_process_memory_mb() -> str: """Get current process RSS memory usage and Python's tracked allocations. Reads VmRSS from /proc/self/status (Linux) to get the current resident set size, which reflects actual physical memory usage at this moment. Also reports tracemalloc's tracked Python allocations to quantify arena fragmentation (difference between OS RSS and Python's tracked memory = fragmentation overhead). Returns: Formatted string with current RSS, peak RSS, and Python tracked memory in MB. """ # Import locally to avoid shadowing the `resource` loop variable used in other functions import resource as resource_mod # noqa: F811 import sys import tracemalloc # Current RSS: read from /proc/self/status (Linux only) # VmRSS shows the actual physical memory currently used by the process current_rss_mb = 0.0 try: with open("/proc/self/status") as f: for line in f: if line.startswith("VmRSS:"): # VmRSS is reported in KB in /proc/self/status current_rss_mb = int(line.split()[1]) / 1024 break except (FileNotFoundError, ValueError): pass # Not on Linux or parse error — current RSS will show 0 # Peak RSS: the maximum RSS ever reached during the process lifetime # On Linux ru_maxrss is in KB, on macOS it's in bytes rusage = resource_mod.getrusage(resource_mod.RUSAGE_SELF) if sys.platform == "darwin": peak_rss_mb = rusage.ru_maxrss / (1024 * 1024) else: peak_rss_mb = rusage.ru_maxrss / 1024 # Python tracked allocations via tracemalloc # The gap between RSS and traced = arena fragmentation + non-Python allocations traced_mb = 0.0 traced_peak_mb = 0.0 if tracemalloc.is_tracing(): traced_current, traced_peak = tracemalloc.get_traced_memory() traced_mb = traced_current / (1024 * 1024) traced_peak_mb = traced_peak / (1024 * 1024) return f"current={current_rss_mb:.1f} MB, peak={peak_rss_mb:.1f} MB" + ( f", py_traced={traced_mb:.1f} MB, py_peak={traced_peak_mb:.1f} MB" if traced_mb > 0 else "" ) def _normalize_data_to_str(data: Union[str, list, None], data_type: str) -> str | None: """Convert data to a newline-separated JSON string for XSIAM ingestion. Handles multiple input types (list of dicts, list of strings, raw string, or None) and returns a unified string representation ready for chunking and sending. Args: data: The data to normalize. Can be a list of dicts/strings, a raw string, or None. data_type: The type of data being sent (e.g., "assets", "events"). Returns: The normalized string, or None if the data cannot be converted (signals the caller to skip sending). """ if isinstance(data, list): log_falcon_assets(f"Sending {len(data)} {data_type} (data type) to XSIAM") if data and isinstance(data[0], dict): data = [json.dumps(item) for item in data] return "\n".join(data) elif isinstance(data, str): return data elif not data and data_type == "assets": # Handle explicit None for assets seal return "" # Unknown type or empty data for non-assets return None """ API FUNCTIONS """ def create_entry_object(contents: list[Any] | dict[str, Any] = {}, ec: list[Any] | dict[str, Any] | None = None, hr: str = ""): """ Creates an entry object :type contents: ``dict`` :param contents: Raw response to output :type ec: ``dict`` :param ec: Entry context of the entry object :type hr: ``str`` :param hr: Human readable :return: Entry object :rtype: ``dict`` """ return { "Type": entryTypes["note"], "Contents": contents, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": ec, } def add_mirroring_fields(incident: dict): """ Updates the given incident to hold the needed mirroring fields. """ incident["mirror_direction"] = MIRROR_DIRECTION incident["mirror_instance"] = INTEGRATION_INSTANCE def extract_response_to_dataset_raw(resp: dict, raw: dict) -> None: """ Adds response data info to specific dataset raw. This function processes response keys and adds them to the raw ad new column. The "name" column is typically used for displaying "(fetched type) ID: (id)" format, The function renames the "name" key to "_name" column to avoid conflicts. Args: resp (dict): Response dictionary to process (events/incident) raw (dict): Target dataset raw to update """ for key, val in resp.items(): column_name = "_name" if key == "name" else key value = val if isinstance(val, str) else json.dumps(val) raw[column_name] = value def detection_to_incident(detection, is_fetch_events: bool = False): """ Creates an incident of a detection. :type detection: ``dict`` :param detection: Single detection object :return: Incident representation of a detection :rtype ``dict`` """ add_mirroring_fields(detection) # detection_id and severity key names change between the legacy and the new version detection_id = detection.get("detection_id") or detection.get("composite_id") severity = detection.get("max_severity_displayname") or detection.get("severity_name") incident = { "name": "Detection ID: " + str(detection_id), "occurred": str(detection.get("created_timestamp")), "severity": severity_string_to_int(severity), "rawJSON": json.dumps(detection), } if is_fetch_events: incident["_source_log_type"] = detection.get("incident_type") extract_response_to_dataset_raw(resp=detection, raw=incident) # new detection if not detection.get("updated_timestamp") or (detection.get("updated_timestamp") == detection.get("timestamp")): incident["_time"] = detection.get("timestamp") incident["_entry_status"] = "new" # updated detection else: incident["_time"] = detection.get("updated_timestamp") incident["_entry_status"] = "updated" return incident def fix_time_field(detection: dict, time_key: str): """ Fix the value of the date to have only 6 figures after the ".". The string representation of the created_timestamp value can contain from 6 to 9 figures after the dot, for example: 2024-02-22T14:16:04.973070837Z. The template supports only 6 digits, so there is a need to remove the extra digits to use datetime.strptime(). Args: detection (dict): the detection. time_key (str): the key of the wanted date&time field. """ demisto.debug(f"fix_time_field {time_key=}") str_date = detection[time_key] split_date = str_date.split(".") relevant_microseconds = split_date[1][:6] # if 'Z' isn't in relevant_microseconds it means that it was removed since there was more than 5 digits in the microseconds. fixed_date = f"{split_date[0]}.{relevant_microseconds}Z" if "Z" not in relevant_microseconds else str_date demisto.debug(f"fix_time_field, the original value in {time_key=} is {str_date} the updated value is {fixed_date} ") detection[time_key] = fixed_date def detection_to_incident_context(detection, detection_type, start_time_key: str = "start_time", is_fetch_events: bool = False): """ Creates an incident context from multiple detection types. :type detection: ``dict`` :param detection: Single detection object. :return: The incident context for the detection. :rtype ``dict`` """ add_mirroring_fields(detection) demisto.debug(f"detection_to_incident_context, {detection_type=}") if detection_type in ( IDP_DETECTION_FETCH_TYPE, NGSIEM_DETECTION_FETCH_TYPE, THIRD_PARTY_DETECTION_FETCH_TYPE, NGSIEM_INCIDENT_FETCH_TYPE, NGSIEM_AUTOMATED_LEADS_FETCH_TYPE, IOA_FETCH_TYPE, ): demisto.debug(f"detection_to_incident_context, {detection_type=} calling fix_time_field") fix_time_field(detection, start_time_key) incident_context = {"occurred": detection.get(start_time_key), "rawJSON": json.dumps(detection)} if detection_type in SUPPORTED_DETECTIONS_TYPES: incident_context["name"] = f'{detection_type} ID: {detection.get("composite_id")}' incident_context["last_updated"] = detection.get("updated_timestamp") elif detection_type == MOBILE_DETECTION_FETCH_TYPE: incident_context["name"] = f'{detection_type} ID: {detection.get("mobile_detection_id")}' incident_context["severity"] = detection.get("severity") elif detection_type == IOA_FETCH_TYPE: incident_context["name"] = f'{detection_type} ID: {detection.get("composite_id")}' incident_context["severity"] = severity_string_to_int(detection.get("severity_name")) if is_fetch_events: incident_context["_source_log_type"] = "detection" extract_response_to_dataset_raw(resp=detection, raw=incident_context) # new detection if not detection.get("updated_timestamp") or (detection.get("updated_timestamp") == detection.get("timestamp")): incident_context["_time"] = detection.get("timestamp") incident_context["_entry_status"] = "new" # updated detection else: incident_context["_time"] = detection.get("updated_timestamp") incident_context["_entry_status"] = "updated" return incident_context def severity_string_to_int(severity): """ Converts a severity string to DBot score representation :type severity: ``str`` :param severity: String representation of a severity :return: DBot score representation of the severity :rtype ``int`` """ if severity in ("Critical", "High"): return 3 elif severity in ("Medium", "Low"): return 2 return 0 def get_trasnformed_dict(old_dict, transformation_dict): """ Returns a dictionary with the same values as old_dict, with the correlating key:value in transformation_dict :type old_dict: ``dict`` :param old_dict: Old dictionary to pull values from :type transformation_dict: ``dict`` :param transformation_dict: Transformation dictionary that contains oldkeys:newkeys :return Transformed dictionart (according to transformation_dict values) :rtype ``dict`` """ new_dict = {} for k in list(old_dict.keys()): if k in transformation_dict: new_dict[transformation_dict[k]] = old_dict[k] return new_dict def extract_transformed_dict_with_split(old_dict, transformation_dict_arr): """ Extracts new values out of old_dict using a json structure of: {'Path': 'Path to item', 'NewKey': 'Value of output key', 'Delim': 'Delimiter char', 'Index': Split Array Index} """ new_dict = {} for trans_dict in transformation_dict_arr: try: val = demisto.get(old_dict, trans_dict["Path"]) if "split" in dir(val): i = trans_dict["Index"] new_dict[trans_dict["NewKey"]] = val.split(trans_dict["Delim"])[i] except Exception as ex: LOG(f"Error {ex} with: {trans_dict}") return new_dict def get_passed_mins(start_time, end_time_str): """ Returns the time passed in mins :param start_time: Start time in datetime :param end_time_str: End time in str :return: The passed mins in int """ time_delta = start_time - datetime.fromtimestamp(end_time_str) return time_delta.seconds / 60 def handle_response_errors(raw_res: dict, err_msg: str | None = None): """ Raise exception if raw_res is empty or contains errors """ if not err_msg: err_msg = "The server was unable to return a result, please run the command again." if not raw_res: raise DemistoException(err_msg) if raw_res.get("errors"): raise DemistoException(raw_res.get("errors")) def create_json_iocs_list( ioc_type: str, iocs_value: list[str], action: str, platforms: list[str], severity: str | None = None, source: str | None = None, description: str | None = None, expiration: str | None = None, applied_globally: bool | None = None, host_groups: list[str] | None = None, tags: list[str] | None = None, file_name: str | None = None, mobile_action: str | None = None, ) -> list[dict]: """ Get a list of iocs values and create a list of Json objects with the iocs data. This function is used for uploading multiple indicator with same arguments with different values. :param ioc_type: The type of the indicator. :param iocs_value: List of the indicator. :param action: Action to take when a host observes the custom IOC. :param platforms: The platforms that the indicator applies to. :param severity: The severity level to apply to this indicator. :param source: The source where this indicator originated. :param description: A meaningful description of the indicator. :param expiration: The date on which the indicator will become inactive. :param applied_globally: Whether the indicator is applied globally. :param host_groups: List of host group IDs that the indicator applies to. :param tags: List of tags to apply to the indicator. :param file_name: Name of the file for file indicators. :param mobile_action: Action to take on mobile when a host observes the custom IOC. """ iocs_list = [] for ioc_value in iocs_value: iocs_list.append( assign_params( type=ioc_type, value=ioc_value, action=action, platforms=platforms, severity=severity, source=source, description=description, expiration=expiration, applied_globally=applied_globally, host_groups=host_groups, tags=tags, mobile_action=mobile_action, metadata=assign_params(filename=file_name) if ioc_type in {"sha256", "md5"} else None, ) ) return iocs_list def list_workflow_definitions(filter_query: str = "", offset: str = "0", limit: int = 50, sort: str = "") -> dict: """ List workflow definitions from CrowdStrike Falcon. Args: filter_query: FQL filter query string. offset: The offset to start retrieving records from. limit: The maximum number of records to return. sort: The property to sort by (e.g., name.desc). Returns: Response JSON containing workflow definitions. """ params = assign_params(filter=filter_query, offset=offset, limit=limit, sort=sort) demisto.debug(f"[Workflow] list_workflow_definitions: calling API with {params=}") return http_request("GET", "/workflows/combined/definitions/v1", params=params) def execute_workflow( definition_id: list[str] | None = None, name: str | None = None, execution_cid: list[str] | None = None, key: str | None = None, source_event_url: str | None = None, body: str = "{}", ) -> dict: """ Execute an on-demand workflow. Args: definition_id: Workflow definition ID(s). name: Workflow name. execution_cid: CID(s) to execute the workflow on. key: Deduplication key. source_event_url: URL reference to the source that triggered the workflow. body: JSON body to pass to the workflow execution. Returns: Response JSON from the workflow execution. """ params = assign_params( definition_id=definition_id, name=name, execution_cid=execution_cid, key=key, source_event_url=source_event_url, ) try: json_body = json.loads(body) except json.JSONDecodeError as e: raise DemistoException(f"Invalid JSON in 'body' argument: {e}") demisto.debug(f"[Workflow] execute_workflow: calling API with {params=}, body_keys={list(json_body.keys())}") return http_request("POST", "/workflows/entities/execute/v1", params=params, json=json_body) def list_workflow_executions(filter_query: str = "", offset: str = "0", limit: int = 50, sort: str = "") -> dict: """ List workflow executions from CrowdStrike Falcon. Args: filter_query: FQL filter query string. offset: The offset to start retrieving records from. limit: The maximum number of records to return. sort: The property to sort by (e.g., created_at.desc). Returns: Response JSON containing workflow executions. """ params = assign_params(filter=filter_query, offset=offset, limit=limit, sort=sort) demisto.debug(f"[Workflow] list_workflow_executions: calling API with {params=}") return http_request("GET", "/workflows/combined/executions/v1", params=params) def get_workflow_execution_results(ids: list[str]) -> dict: """ Get detailed results for specific workflow executions. Args: ids: List of workflow execution IDs. Returns: Response JSON containing execution results. """ params = {"ids": ids} return http_request("GET", "/workflows/entities/execution-results/v1", params=params, status_code=404) def perform_workflow_execution_action(ids: list[str], action_name: str) -> dict: """ Perform an action (cancel or resume) on workflow executions. Args: ids: List of workflow execution IDs. action_name: The action to perform ('cancel' or 'resume'). Returns: Response JSON from the action. """ params = {"action_name": action_name} body = {"ids": ids} return http_request("POST", "/workflows/entities/execution-actions/v1", params=params, json=body, status_code=404) """ COMMAND SPECIFIC FUNCTIONS """ def init_rtr_single_session(host_id: str, queue_offline: bool = False) -> str: """ Start a session with single host. :param host_id: Host agent ID to initialize a RTR session on. :return: The session ID to execute the command on """ endpoint_url = "/real-time-response/entities/sessions/v1" body = json.dumps({"device_id": host_id, "queue_offline": queue_offline}) response = http_request("POST", endpoint_url, data=body) resources = response.get("resources") if resources and isinstance(resources, list) and isinstance(resources[0], dict): session_id = resources[0].get("session_id") if isinstance(session_id, str): return session_id raise ValueError("No session id found in the response") def init_rtr_batch_session(host_ids: list, offline=False) -> str: """ Start a session with one or more hosts :param host_ids: List of host agent ID’s to initialize a RTR session on. :return: The session batch ID to execute the command on """ endpoint_url = "/real-time-response/combined/batch-init-session/v1" body = json.dumps({"host_ids": host_ids, "queue_offline": offline}) response = http_request("POST", endpoint_url, data=body) return response.get("batch_id") def refresh_session(host_id: str) -> dict: """ Refresh a session timeout on a single host. :param host_id: Host agent ID to run RTR command on. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/refresh-session/v1" body = json.dumps({"device_id": host_id}) response = http_request("POST", endpoint_url, data=body) return response def batch_refresh_session(batch_id: str) -> None: """ Batch refresh a RTR session on multiple hosts. :param batch_id: Batch ID to execute the command on. """ demisto.debug("Starting session refresh") endpoint_url = "/real-time-response/combined/batch-refresh-session/v1" body = json.dumps({"batch_id": batch_id}) response = http_request("POST", endpoint_url, data=body) demisto.debug(f"Refresh session response: {response}") demisto.debug("Finished session refresh") def run_batch_read_cmd(batch_id: str, command_type: str, full_command: str, timeout: int = 30) -> dict: """ Sends RTR command scope with read access :param batch_id: Batch ID to execute the command on. :param command_type: Read-only command type we are going to execute, for example: ls or cd. :param full_command: Full command string for the command. :param timeout: The timeout for the request. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/combined/batch-command/v1" body = json.dumps({"base_command": command_type, "batch_id": batch_id, "command_string": full_command}) params = {"timeout": timeout} response = http_request("POST", endpoint_url, data=body, params=params, timeout=timeout) return response def run_batch_write_cmd( batch_id: str, command_type: str, full_command: str, optional_hosts: list | None = None, timeout: int = DEFAULT_TIMEOUT ) -> dict: """ Sends RTR command scope with write access :param batch_id: Batch ID to execute the command on. :param command_type: Read-only command type we are going to execute, for example: ls or cd. :param full_command: Full command string for the command. :param optional_hosts: The hosts ids to run the command on. :param timeout: The timeout for the request. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/combined/batch-active-responder-command/v1" default_body = {"base_command": command_type, "batch_id": batch_id, "command_string": full_command} params = {"timeout": timeout if timeout else DEFAULT_TIMEOUT} if optional_hosts: default_body["optional_hosts"] = optional_hosts # type:ignore body = json.dumps(default_body) response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params) return response def run_batch_admin_cmd( batch_id: str, command_type: str, full_command: str, timeout: int = 30, optional_hosts: list | None = None ) -> dict: """ Sends RTR command scope with write access :param batch_id: Batch ID to execute the command on. :param command_type: Read-only command type we are going to execute, for example: ls or cd. :param full_command: Full command string for the command. :param timeout: Timeout for how long to wait for the request in seconds. :param optional_hosts: The hosts ids to run the command on. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/combined/batch-admin-command/v1" params = {"timeout": timeout} default_body = {"base_command": command_type, "batch_id": batch_id, "command_string": full_command} if optional_hosts: default_body["optional_hosts"] = optional_hosts # type:ignore body = json.dumps(default_body) response = http_request("POST", endpoint_url, data=body, params=params, timeout=timeout) return response def run_batch_get_cmd( host_ids: list, file_path: str, optional_hosts: list | None = None, timeout: int | None = None, timeout_duration: str | None = None, offline: bool = False, ) -> dict: """ Batch executes `get` command across hosts to retrieve files. After this call is made `/real-time-response/combined/batch-get-command/v1` is used to query for the results. :param host_ids: List of host agent ID’s to run RTR command on. :param file_path: Full path to the file that is to be retrieved from each host in the batch. :param optional_hosts: List of a subset of hosts we want to run the command on. If this list is supplied, only these hosts will receive the command. :param timeout: Timeout for how long to wait for the request in seconds :param timeout_duration: Timeout duration for for how long to wait for the request in duration syntax :param offline: Whether the command will run against an offline-queued session for execution when the host comes online. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/combined/batch-get-command/v1" batch_id = init_rtr_batch_session(host_ids, offline) body = assign_params(batch_id=batch_id, file_path=f'"{file_path}"', optional_hosts=optional_hosts) params = assign_params(timeout=timeout, timeout_duration=timeout_duration) response = http_request("POST", endpoint_url, data=json.dumps(body), params=params) return response def status_get_cmd(request_id: str, timeout: int | None = None, timeout_duration: str | None = None) -> dict: """ Retrieves the status of the specified batch get command. Will return successful files when they are finished processing. :param request_id: ID to the request of `get` command. :param timeout: Timeout for how long to wait for the request in seconds :param timeout_duration: Timeout duration for how long to wait for the request in duration syntax :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/combined/batch-get-command/v1" params = assign_params(timeout=timeout, timeout_duration=timeout_duration, batch_get_cmd_req_id=request_id) response = http_request("GET", endpoint_url, params=params) return response def run_single_read_cmd(host_id: str, command_type: str, full_command: str, queue_offline: bool, timeout: int = 30) -> dict: """ Sends RTR command scope with read access :param host_id: Host agent ID to run RTR command on. :param command_type: Active-Responder command type we are going to execute, for example: get or cp. :param full_command: Full command string for the command. :param queue_offline: Whether the command will run against an offline-queued session and be queued for execution when the host comes online. # noqa: E501 :param timeout: The timeout for the request. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/command/v1" session_id = init_rtr_single_session(host_id, queue_offline) body = json.dumps({"base_command": command_type, "command_string": full_command, "session_id": session_id}) params = {"timeout": timeout} response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params) return response def run_single_write_cmd(host_id: str, command_type: str, full_command: str, queue_offline: bool, timeout: int = 30) -> dict: """ Sends RTR command scope with write access :param host_id: Host agent ID to run RTR command on. :param command_type: Active-Responder command type we are going to execute, for example: get or cp. :param full_command: Full command string for the command. :param queue_offline: Whether the command will run against an offline-queued session and be queued for execution when the host comes online. # noqa: E501 :param timeout: The timeout for the request. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/active-responder-command/v1" session_id = init_rtr_single_session(host_id, queue_offline) body = json.dumps({"base_command": command_type, "command_string": full_command, "session_id": session_id}) params = {"timeout": timeout} response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params) return response def run_single_admin_cmd(host_id: str, command_type: str, full_command: str, queue_offline: bool, timeout: int = 30) -> dict: """ Sends RTR command scope with admin access :param host_id: Host agent ID to run RTR command on. :param command_type: Active-Responder command type we are going to execute, for example: get or cp. :param full_command: Full command string for the command. :param queue_offline: Whether the command will run against an offline-queued session and be queued for execution when the host comes online. # noqa: E501 :param timeout: The timeout for the request. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/admin-command/v1" session_id = init_rtr_single_session(host_id, queue_offline) body = json.dumps({"base_command": command_type, "command_string": full_command, "session_id": session_id}) params = {"timeout": timeout} response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params) return response def status_read_cmd(request_id: str, sequence_id: int | None) -> dict: """ Get status of an executed command with read access on a single host. :param request_id: Cloud Request ID of the executed command to query :param sequence_id: Sequence ID that we want to retrieve. Command responses are chunked across sequences """ endpoint_url = "/real-time-response/entities/command/v1" params = {"cloud_request_id": request_id, "sequence_id": sequence_id or 0} response = http_request("GET", endpoint_url, params=params) return response def status_write_cmd(request_id: str, sequence_id: int | None) -> dict: """ Get status of an executed command with write access on a single host. :param request_id: Cloud Request ID of the executed command to query :param sequence_id: Sequence ID that we want to retrieve. Command responses are chunked across sequences """ endpoint_url = "/real-time-response/entities/active-responder-command/v1" params = {"cloud_request_id": request_id, "sequence_id": sequence_id or 0} response = http_request("GET", endpoint_url, params=params) return response def status_admin_cmd(request_id: str, sequence_id: int | None) -> dict: """ Get status of an executed command with admin access on a single host. :param request_id: Cloud Request ID of the executed command to query :param sequence_id: Sequence ID that we want to retrieve. Command responses are chunked across sequences """ endpoint_url = "/real-time-response/entities/admin-command/v1" params = {"cloud_request_id": request_id, "sequence_id": sequence_id or 0} response = http_request("GET", endpoint_url, params=params) return response def list_host_files(host_id: str, session_id: str | None = None) -> dict: """ Get a list of files for the specified RTR session on a host. :param host_id: Host agent ID to run RTR command on. :param session_id: optional session_id for the command, if not provided a new session_id will generate :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/file/v2" if not session_id: session_id = init_rtr_single_session(host_id) params = {"session_id": session_id} response = http_request("GET", endpoint_url, params=params) return response def upload_script(name: str, permission_type: str, content: str, entry_id: str) -> dict: """ Uploads a script by either given content or file :param name: Script name to upload :param permission_type: Permissions type of script to upload :param content: PowerShell script content :param entry_id: Script file to upload :return: Response JSON which contains errors (if exist) and how many resources were affected """ endpoint_url = "/real-time-response/entities/scripts/v1" body: dict[str, tuple[Any, Any]] = {"name": (None, name), "permission_type": (None, permission_type)} temp_file = None try: if content: body["content"] = (None, content) else: # entry_id was provided file_ = demisto.getFilePath(entry_id) file_name = file_.get("name") # pylint: disable=E1101 temp_file = open(file_.get("path"), "rb") # pylint: disable=E1101 body["file"] = (file_name, temp_file) headers = {"Authorization": HEADERS["Authorization"], "Accept": "application/json"} response = http_request("POST", endpoint_url, files=body, headers=headers) return response finally: if temp_file: temp_file.close() def get_script(script_id: list) -> dict: """ Retrieves a script given its ID :param script_id: ID of script to get :return: Response JSON which contains errors (if exist) and retrieved resource """ endpoint_url = "/real-time-response/entities/scripts/v2" params = {"ids": script_id} response = http_request("GET", endpoint_url, params=params) return response def delete_script(script_id: str) -> dict: """ Deletes a script given its ID :param script_id: ID of script to delete :return: Response JSON which contains errors (if exist) and how many resources were affected """ endpoint_url = "/real-time-response/entities/scripts/v1" params = {"ids": script_id} response = http_request("DELETE", endpoint_url, params=params) return response def list_scripts() -> dict: """ Retrieves list of scripts :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/scripts/v2" response = http_request("GET", endpoint_url) return response def get_extracted_file(host_id: str, sha256: str, filename: str | None = None, timeout=None): """ Get RTR extracted file contents for specified session and sha256. :param host_id: The host agent ID to initialize the RTR session on. :param sha256: Extracted SHA256 :param filename: Filename to use for the archive name and the file within the archive. """ endpoint_url = "/real-time-response/entities/extracted-file-contents/v1" session_id = init_rtr_single_session(host_id) params = {"session_id": session_id, "sha256": sha256} if filename: params["filename"] = filename response = http_request("GET", endpoint_url, params=params, no_json=True, timeout=timeout) return response def upload_file(entry_id: str, description: str) -> tuple: """ Uploads a file given entry ID :param entry_id: The entry ID of the file to upload :param description: String description of file to upload :return: Response JSON which contains errors (if exist) and how many resources were affected and the file name """ endpoint_url = "/real-time-response/entities/put-files/v1" temp_file = None try: file_ = demisto.getFilePath(entry_id) file_name = file_.get("name") # pylint: disable=E1101 temp_file = open(file_.get("path"), "rb") # pylint: disable=E1101 body = {"name": (None, file_name), "description": (None, description), "file": (file_name, temp_file)} headers = {"Authorization": HEADERS["Authorization"], "Accept": "application/json"} response = http_request("POST", endpoint_url, files=body, headers=headers) return response, file_name finally: if temp_file: temp_file.close() def delete_file(file_id: str) -> dict: """ Delete a put-file based on the ID given :param file_id: ID of file to delete :return: Response JSON which contains errors (if exist) and how many resources were affected """ endpoint_url = "/real-time-response/entities/put-files/v1" params = {"ids": file_id} response = http_request("DELETE", endpoint_url, params=params) return response def get_file(file_id: list) -> dict: """ Get put-files based on the ID's given :param file_id: ID of file to get :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/put-files/v2" params = {"ids": file_id} response = http_request("GET", endpoint_url, params=params) return response def get_file_id_by_name(file_name: str) -> str: """ Retrieve the file ID for a put-file by its name. :param file_name: Name of the file to search for :return: File ID that matches the given name, or empty string if none found """ endpoint_url = "/real-time-response/queries/put-files/v1" params = {"filter": f"name:'{file_name}'"} response = http_request("GET", endpoint_url, params=params) return response.get("resources", "") def list_files() -> dict: """ Get a list of put-file ID's that are available to the user for the put command. :return: Response JSON which contains errors (if exist) and retrieved resources """ endpoint_url = "/real-time-response/entities/put-files/v2" response = http_request("GET", endpoint_url) return response def get_token(new_token=False): """ Retrieves the token from the server if it's expired and updates the global HEADERS to include it :param new_token: If set to True will generate a new token regardless of time passed :rtype: ``str`` :return: Token """ now = datetime.now() ctx = demisto.getIntegrationContext() if ctx and not new_token: passed_mins = get_passed_mins(now, ctx.get("time")) demisto.debug(f"{passed_mins=}") if passed_mins >= TOKEN_LIFE_TIME: # token expired demisto.debug("token expired") auth_token = get_token_request() demisto.setIntegrationContext({"auth_token": auth_token, "time": date_to_timestamp(now) / 1000}) else: # token hasn't expired demisto.debug("token hasn't expired") auth_token = ctx.get("auth_token") else: # there is no token demisto.debug("there is no token") auth_token = get_token_request() demisto.setIntegrationContext({"auth_token": auth_token, "time": date_to_timestamp(now) / 1000}) return auth_token def get_token_request(): """ Sends token request :rtype ``str`` :return: Access token """ body = {"client_id": CLIENT_ID, "client_secret": SECRET} headers = {"Content-Type": "application/x-www-form-urlencoded"} token_res = http_request("POST", "/oauth2/token", data=body, headers=headers, get_token_flag=False) demisto.debug(f"In get_token_request, token_res is not None {token_res is not None}") if not token_res: err_msg = ( "Authorization Error: User has no authorization to create a token. Please make sure you entered the" " credentials correctly." ) raise Exception(err_msg) demisto.debug(f'{token_res.get("expires_in")=}') return token_res.get("access_token") def get_ioarules(rule_ids: list[str]) -> dict: """ Sends ioa rules entities request :param rule_ids: IDs of the requested ioa rule. :return: Response json of the get ioa rule entities endpoint (ioa rule objects) """ params = {"ids": rule_ids} return http_request( "GET", "/ioarules/entities/rules/v1", params=params, ) def get_detections(last_behavior_time=None, behavior_id=None, filter_arg=None): """ Sends detections request. The function will ignore the arguments passed according to priority: filter_arg > behavior_id > last_behavior_time :param last_behavior_time: 3rd priority. The last behavior time of results will be greater than this value :param behavior_id: 2nd priority. The result will only contain the detections with matching behavior id :param filter_arg: 1st priority. The result will be filtered using this argument. :return: Response json of the get detection endpoint (IDs of the detections) """ params = {"sort": "first_behavior.asc"} if filter_arg: params["filter"] = filter_arg elif behavior_id: params["filter"] = f"behaviors.behavior_id:'{behavior_id}'" elif last_behavior_time: params["filter"] = f"first_behavior:>'{last_behavior_time}'" endpoint_url = "alerts/queries/alerts/v2?filter=" if filter_arg: # in the new version we send only the filter_arg argument as encoded string without the params endpoint_url += urllib.parse.quote_plus(filter_arg) demisto.debug(f"In get_detections: {endpoint_url=}") return http_request("GET", endpoint_url, {"sort": "created_timestamp.asc"}) def get_fetch_detections( last_created_timestamp=None, filter_arg=None, offset: int = 0, last_updated_timestamp=None, has_limit=True, limit: int = INCIDENTS_PER_FETCH, ): """Sends detection request, based on the created_timestamp field. Used for fetch-incidents Args: last_created_timestamp: last created timestamp of the results will be greater than this value. filter_arg: The result will be filtered using this argument. Returns: Response json of the get detection endpoint (IDs of the detections) """ sort_key = "created_timestamp.asc" params = { "sort": sort_key, "offset": offset, } if has_limit: params["limit"] = limit if filter_arg: params["filter"] = filter_arg elif last_created_timestamp: params["filter"] = f"created_timestamp:>'{last_created_timestamp}'" elif last_updated_timestamp: timestamp_key = "updated_timestamp" params["filter"] = f"{timestamp_key}:>'{last_updated_timestamp}'" endpoint_url = "/alerts/queries/alerts/v2?filter=product" if params.get("filter"): endpoint_url += urllib.parse.quote_plus(f":'epp'+type:'ldt'+{params.pop('filter')}") else: endpoint_url += urllib.parse.quote_plus(":'epp'+type:'ldt'") demisto.debug(f"In get_fetch_detections: {endpoint_url=}, {params=}") response = http_request("GET", endpoint_url, params) return response def get_detections_entities(detections_ids: list): """ Sends detection entities request :param detections_ids: IDs of the requested detections. :return: Response json of the get detection entities endpoint (detection objects) """ if not detections_ids: return detections_ids combined_resources = [] url = "/alerts/entities/alerts/v2" # Iterate through the detections_ids list in chunks of 1000 (According to API documentation). for i in range(0, len(detections_ids), MAX_FETCH_DETECTION_PER_API_CALL_ENTITY): batch_ids = detections_ids[i : i + MAX_FETCH_DETECTION_PER_API_CALL_ENTITY] ids_json = {"composite_ids": batch_ids} demisto.debug(f"Getting detections entities from {url} with {ids_json=} " f"with batch_ids len {len(batch_ids)}.") # Make the API call with the current batch. response = http_request("POST", url, data=json.dumps(ids_json)) if "resources" in response: # Combine the resources from each response. combined_resources.extend(response["resources"]) # Return the combined result. return {"resources": combined_resources} def get_cases_data(url_filter: str = "", limit: int = 100, offset: int = 0) -> tuple[int, list[str]]: """ Fetches NGSIEM Case ids with provided filter :param url_filter: URL filter :param limit: number of cases to fetch :param offset: the fetch offset Returns: tuple[int, list[str]]: The number of total cases in the filter and the list of cases ids. """ params = {"sort": "created_timestamp.asc", "offset": offset, "limit": limit} if url_filter: params["filter"] = url_filter endpoint_url = "/cases/queries/cases/v1" response = http_request("GET", endpoint_url, params) total_cases: int = demisto.get(response, "meta.pagination.total") ids: list[str] = demisto.get(response, "resources", []) return total_cases, ids def get_detections_ids(filter_arg=None, offset: int = 0, limit=INCIDENTS_PER_FETCH, product_type="idp"): """ Send a request to retrieve IDP/ODS detections IDs. :type filter_arg: ``str`` :param filter_arg: The filter to add to the query. :type offset: ``int`` :param offset: The offset for the query. :type limit: ``int`` :param limit: limit of idp/ods detections to retrieve each request. :return: The response. :rtype ``dict`` """ params = {"sort": "created_timestamp.asc", "offset": offset, "filter": filter_arg} if limit: params["limit"] = limit endpoint_url = "/alerts/queries/alerts/v2?filter=" # in the new version we need to add the product type to the filter to the url as encoded string if params.get("filter"): endpoint_url += urllib.parse.quote_plus(params.pop("filter")) response = http_request("GET", endpoint_url, params) demisto.debug(f"CrowdStrikeFalconMsg: Getting {product_type} detections from {endpoint_url} with {params=}. {response=}.") return response def get_cases_entities(cases_ids: list): """ Sends case entities request :param cases_ids: IDs of the requested cases. :return: Response json of the get case entities endpoint (case objects) """ ids_json = {"ids": cases_ids} raw_res = http_request("POST", "/cases/entities/cases/v2", data=json.dumps(ids_json)) return raw_res["resources"] def get_cases_details(ids: list[str]) -> list[dict[str, Any]]: """ Get details on cases by providing case IDs Args: ids: List of case IDs to get details on Returns: list[dict[str, Any]]: Response data """ full_cases = [] for i in range(0, len(ids), MAX_FETCH_DETECTION_PER_API_CALL_ENTITY): batch_ids = ids[i : i + MAX_FETCH_DETECTION_PER_API_CALL_ENTITY] batch_cases = get_cases_entities(batch_ids) if batch_cases: # Combine the resources from each response. full_cases.extend(batch_cases) # Return the combined result. return full_cases def add_case_tags(case_id: str, tags: list[str]) -> dict: """ Add tags to a case. Args: case_id: The ID of the case to add tags to. tags: The list of tags to add. Returns: dict: The response from the API. """ body = {"id": case_id, "tags": tags} return http_request("POST", "/cases/entities/case-tags/v1", json=body) def delete_case_tags(case_id: str, tag: str) -> dict: """ Delete a tag from a case. Args: case_id: The ID of the case to delete the tag from. tag: The tag to delete. Returns: dict: The response from the API. """ params = {"id": case_id, "tag": tag} return http_request("DELETE", "/cases/entities/case-tags/v1", params=params) def get_detection_entities(incidents_ids: list): """ Send a request to retrieve IDP/ODS/OFP and mobile detection entities. :type incidents_ids: ``list`` :param incidents_ids: The list of ids to search their entities. :return: The response. :rtype ``dict`` """ combined_resources = [] url = "/alerts/entities/alerts/v2" for i in range(0, len(incidents_ids), MAX_FETCH_DETECTION_PER_API_CALL_ENTITY): batch_ids = incidents_ids[i : i + MAX_FETCH_DETECTION_PER_API_CALL_ENTITY] ids_json = {"composite_ids": batch_ids} demisto.debug(f"In get_detection_entities: Getting detection entities from\ {url} with {ids_json=} and with batch_ids len {len(batch_ids)}.") # Make the API call with the current batch. raw_res = http_request("POST", url, data=json.dumps(ids_json)) if "resources" in raw_res: # Combine the resources from each response. combined_resources.extend(raw_res["resources"]) # Return the combined result. return {"resources": combined_resources} def get_users(offset: int, limit: int, query_filter: str | None = None) -> dict: """ Get a list of users using pagination. Note: The result will include all collected paginated data, but the 'meta' key will only include information of the first page. Args: offset (int): The offset to begin from. limit (int): The maximum number of records to return. query_filter (str): Filter to use for the API request. Returns: dict: The response from the API (a combination of all paginated data). """ def generate_paginated_request(_offset: int, _limit: int) -> dict: result: dict = { "method": "GET", "url_suffix": "/user-management/queries/users/v1", "params": { "offset": _offset, "limit": _limit, # We need to use sort since the API doesn't guarantee a consistent order, # which can cause issues when using the offset parameter (repetitive & missing values) "sort": "uid", }, } if query_filter: result["params"]["filter"] = query_filter return result response = http_request(**generate_paginated_request(_offset=offset, _limit=limit)) total_results = response.get("meta", {}).get("pagination", {}).get("total", 0) fetched_results_count = len(response.get("resources", [])) while fetched_results_count < limit and fetched_results_count + offset < total_results: current_offset = offset + fetched_results_count remaining_results_count = min(limit, total_results) - fetched_results_count if remaining_results_count > 500: current_limit = 500 else: current_limit = remaining_results_count current_response = http_request(**generate_paginated_request(_offset=current_offset, _limit=current_limit)) response["resources"].extend(current_response.get("resources", [])) fetched_results_count += len(current_response.get("resources", [])) return response def get_users_data(user_ids: list[str]) -> dict: return http_request( "POST", "/user-management/entities/users/GET/v1", data=json.dumps({"ids": user_ids}), ) def upload_ioc(ioc_type, value, policy=None, expiration_days=None, share_level=None, description=None, source=None): """ Create a new IOC (or replace an existing one) """ payload = assign_params( type=ioc_type, value=value, policy=policy, share_level=share_level, expiration_days=expiration_days, source=source, description=description, ) return http_request("POST", "/indicators/entities/iocs/v1", json=[payload]) def update_ioc(ioc_type, value, policy=None, expiration_days=None, share_level=None, description=None, source=None): """ Update an existing IOC """ body = assign_params( type=ioc_type, value=value, policy=policy, share_level=share_level, expiration_days=expiration_days, source=source, description=description, ) params = assign_params(type=ioc_type, value=value) return http_request("PATCH", "/indicators/entities/iocs/v1", json=body, params=params) def search_iocs( types=None, values=None, policies=None, sources=None, expiration_from=None, expiration_to=None, limit=None, share_levels=None, ids=None, sort=None, offset=None, ): """ :param types: A list of indicator types. Separate multiple types by comma. :param values: Comma-separated list of indicator values :param policies: Comma-separated list of indicator policies :param sources: Comma-separated list of IOC sources :param expiration_from: Start of date range to search (YYYY-MM-DD format). :param expiration_to: End of date range to search (YYYY-MM-DD format). :param share_levels: A list of share levels. Only red is supported. :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100. :param sort: The order of the results. Format :param offset: The offset to begin the list from """ if not ids: payload = assign_params( types=argToList(types), values=argToList(values), policies=argToList(policies), sources=argToList(sources), share_levels=argToList(share_levels), sort=sort, offset=offset, limit=limit or "50", ) if expiration_from: payload["from.expiration_timestamp"] = expiration_from if expiration_to: payload["to.expiration_timestamp"] = expiration_to ids = http_request("GET", "/indicators/queries/iocs/v1", payload).get("resources") if not ids: return None else: ids = str(ids) payload = {"ids": ids} return http_request("GET", "/indicators/entities/iocs/v1", params=payload) def enrich_ioc_dict_with_ids(ioc_dict): """ Enriches the provided ioc_dict with IOC ID :param ioc_dict: IOC dict transformed using the SEARCH_IOC_KEY_MAP :return: ioc_dict with its ID key:value updated """ for ioc in ioc_dict: ioc["ID"] = "{type}:{val}".format(type=ioc.get("Type"), val=ioc.get("Value")) return ioc_dict def delete_ioc(ioc_type, value): """ Delete an IOC """ payload = assign_params(type=ioc_type, value=value) return http_request("DELETE", "/indicators/entities/iocs/v1", payload) def search_custom_iocs( types: list | str | None = None, values: list | str | None = None, sources: list | str | None = None, expiration: str | None = None, limit: str = "50", sort: str | None = None, offset: str | None = None, after: str | None = None, ) -> dict: """ :param types: A list of indicator types. Separate multiple types by comma. :param values: Comma-separated list of indicator values :param sources: Comma-separated list of IOC sources :param expiration: The date on which the indicator will become inactive. (YYYY-MM-DD format). :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100. :param sort: The order of the results. Format :param offset: The offset to begin the list from :param after: A pagination token used with the limit parameter to manage pagination of results. On your first request, don't provide an 'after' token. On subsequent requests, provide the 'after' token from the previous response to continue from that place in the results. To access more than 10k indicators, use the 'after' parameter instead of 'offset'. """ filter_list = [] if types: filter_list.append(f"type:{types}") if values: filter_list.append(f"value:{values}") if sources: filter_list.append(f"source:{sources}") if expiration: filter_list.append(f'expiration:"{expiration}"') params = { "filter": "+".join(filter_list), "sort": sort, "offset": offset, "limit": limit, "after": after, } return http_request("GET", "/iocs/combined/indicator/v1", params=params) def get_custom_ioc(ioc_id: str) -> dict: params = {"ids": ioc_id} return http_request("GET", "/iocs/entities/indicators/v1", params=params) def update_custom_ioc( ioc_id: str, action: str | None = None, platforms: str | None = None, severity: str | None = None, source: str | None = None, description: str | None = None, expiration: str | None = None, file_name: str | None = None, mobile_action: str | None = None, ) -> dict: """ Update an IOC """ payload = { "indicators": [ { "id": ioc_id, } | assign_params( action=action, platforms=platforms, severity=severity, source=source, description=description, expiration=expiration, mobile_action=mobile_action, metadata=assign_params(filename=file_name), ) ] } return http_request("PATCH", "/iocs/entities/indicators/v1", json=payload) def delete_custom_ioc(ids: str) -> dict: """ Delete an IOC """ params = {"ids": ids} return http_request("DELETE", "/iocs/entities/indicators/v1", params=params) def get_ioc_device_count(ioc_type, value): """ Gets the devices that encountered the IOC """ payload = assign_params(type=ioc_type, value=value) response = http_request("GET", "/indicators/aggregates/devices-count/v1", payload, status_code=404) errors = response.get("errors", []) for error in errors: if error.get("code") == 404: return f"No results found for {ioc_type} - {value}" return response def get_process_details(ids): """ Get given processes details """ payload = assign_params(ids=ids) return http_request("GET", "/processes/entities/processes/v1", payload) def get_proccesses_ran_on(ioc_type, value, device_id): """ Get processes ids that ran on the given device_id that encountered the ioc """ payload = assign_params(type=ioc_type, value=value, device_id=device_id) return http_request("GET", "/indicators/queries/processes/v1", payload) def search_device(filter_operator="AND"): """ Searches for devices using the argument provided by the command execution. Returns empty result if no device was found :param: filter_operator: the operator that should be used between filters, default is 'AND' :param: exact_hostname: Whether to return exact hostname :return: Search device response json """ args = demisto.args() input_arg_dict = { "device_id": str(args.get("ids", "")).split(","), "status": str(args.get("status", "")).split(","), "hostname": str(args.get("hostname", "")).split(","), "platform_name": str(args.get("platform_name", "")).split(","), "site_name": str(args.get("site_name", "")).split(","), "local_ip": str(args.get("ip", "")).split(","), } limit = int(args.get("limit", 50)) offset = int(args.get("offset", 0)) sort = args.get("sort", "") url_filter = "{}".format(str(args.get("filter", ""))) op = "," if filter_operator == "OR" else "+" # In Falcon Query Language, '+' stands for AND and ',' for OR # (https://falcon.crowdstrike.com/documentation/45/falcon-query-language-fql) for k, arg in input_arg_dict.items(): if arg: if type(arg) is list: arg_filter = "" for arg_elem in arg: if arg_elem: first_arg = f"{arg_filter},{k}" if arg_filter else k arg_filter = f"{first_arg}:'{arg_elem}'" if arg_filter: url_filter = "{url_filter}{arg_filter}".format( url_filter=url_filter + op if url_filter else "", arg_filter=arg_filter ) else: # All args should be a list. this is a fallback url_filter = f"{url_filter}{op}{k}:'{arg}'" raw_res = http_request( "GET", "/devices/queries/devices/v1", params={"filter": url_filter, "limit": limit, "offset": offset, "sort": sort} ) device_ids = raw_res.get("resources") if not device_ids: return None demisto.debug(f"number of devices returned from the api call is: {len(device_ids)}") return http_request("POST", "/devices/entities/devices/v2", json={"ids": device_ids}) def behavior_to_entry_context(behavior): """ Transforms a behavior to entry context representation :param behavior: Behavior dict in the format of crowdstrike's API response :return: Behavior in entry context representation """ raw_entry = get_trasnformed_dict(behavior, DETECTIONS_BEHAVIORS_KEY_MAP) split_key_map = DETECTIONS_BEHAVIORS_SPLIT_KEY_MAP raw_entry.update(extract_transformed_dict_with_split(behavior, split_key_map)) return raw_entry def resolve_detection(ids, status, assigned_to_uuid, username, show_in_ui, comment, tag): """ Sends a resolve detection request :param ids: Single or multiple ids in an array string format. :param status: New status of the detection. :param assigned_to_uuid: uuid to assign the detection to. :param username: Username to assign the detection to. :param show_in_ui: Boolean flag in string format (true/false). :param comment: Optional comment to add to the detection. :param The tag to add. :return: Resolve detection response json """ payload = {"ids": ids} if status: payload["status"] = status if assigned_to_uuid: payload["assigned_to_uuid"] = assigned_to_uuid if show_in_ui: payload["show_in_ui"] = show_in_ui if comment: payload["comment"] = comment demisto.debug(f"in resolve_detection: {payload=}") # modify the payload to match the Raptor API ids = payload.pop("ids") payload["assign_to_uuid"] = payload.pop("assigned_to_uuid") if "assigned_to_uuid" in payload else None payload["assign_to_user_id"] = username if username else None payload["update_status"] = payload.pop("status") if "status" in payload else None payload["append_comment"] = payload.pop("comment") if "comment" in payload else None if tag: payload["add_tag"] = tag data = json.dumps(resolve_detections_prepare_body_request(ids, payload)) url = "/alerts/entities/alerts/v3" return http_request("PATCH", url, data=data) def contain_host(ids): """ Contains host(s) with matching ids :param ids: IDs of host to contain :return: Contain host response json """ payload = {"ids": ids} data = json.dumps(payload) params = {"action_name": "contain"} return http_request("POST", "/devices/entities/devices-actions/v2", data=data, params=params) def lift_host_containment(ids, action_name: str = "lift_containment"): """ Lifts off containment from host(s) with matching ids :param ids: IDs of host to lift off containment from :param action_name: The action to perform. Either 'lift_containment' or 'lift_filesystem_containment_all'. :return: Lift off containment response json """ payload = {"ids": ids} data = json.dumps(payload) params = {"action_name": action_name} return http_request("POST", "/devices/entities/devices-actions/v2", data=data, params=params) def timestamp_length_equalization(timestamp1, timestamp2): """ Makes sure the timestamps are of the same length. Args: timestamp1: First timestamp to compare. timestamp2: Second timestamp to compare. Returns: the two timestamps in the same length (the longer one) """ diff_len = len(str(timestamp1)) - len(str(timestamp2)) # no difference in length if diff_len == 0: return int(timestamp1), int(timestamp2) # length of timestamp1 > timestamp2 if diff_len > 0: ten_times = pow(10, diff_len) timestamp2 = int(timestamp2) * ten_times # length of timestamp2 > timestamp1 else: ten_times = pow(10, diff_len * -1) timestamp1 = int(timestamp1) * ten_times return int(timestamp1), int(timestamp2) def change_host_group( is_post: bool, host_group_id: str | None = None, name: str | None = None, group_type: str | None = None, description: str | None = None, assignment_rule: str | None = None, ) -> dict: method = "POST" if is_post else "PATCH" data = { "resources": [ { "id": host_group_id, "name": name, "description": description, "group_type": group_type, "assignment_rule": assignment_rule, } ] } response = http_request(method=method, url_suffix="/devices/entities/host-groups/v1", json=data) return response def change_host_group_members(action_name: str, host_group_id: str, host_ids: list[str]) -> dict: allowed_actions = {"add-hosts", "remove-hosts"} if action_name not in allowed_actions: raise DemistoException(f"CrowdStrike Falcon error: action name should be in {allowed_actions}") data = {"action_parameters": [{"name": "filter", "value": f"(device_id:{host_ids!s})"}], "ids": [host_group_id]} response = http_request( method="POST", url_suffix="/devices/entities/host-group-actions/v1", params={"action_name": action_name}, json=data ) return response def host_group_members(filter: str | None, host_group_id: str | None, limit: str | None, offset: str | None, sort: str | None): params = {"id": host_group_id, "filter": filter, "offset": offset, "limit": limit, "sort": sort} response = http_request(method="GET", url_suffix="/devices/combined/host-group-members/v1", params=params) return response def update_detection_request(ids: list[str], status: str) -> dict: list_of_stats = STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES if status not in list_of_stats: raise DemistoException(f"CrowdStrike Falcon Error: Status given is {status} and it is not in {list_of_stats}") return resolve_detection( ids=ids, status=status, assigned_to_uuid=None, username=None, show_in_ui=None, comment=None, tag=None ) def update_request_for_multiple_detection_types(ids: list[str], status: str) -> dict: """ Manage the status to send to update to for IDP/Mobile detections. :type ids: ``list`` :param ids: The list of ids to update. :type status: ``str`` :param status: The new status to set. :return: The response. :rtype ``dict`` """ if status not in STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES: raise DemistoException( f"CrowdStrike Falcon Error: Status given is {status} and it is not in {STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES}" ) return resolve_detections_request(ids=ids, update_status=status) def list_host_groups(filter: str | None, limit: str | None, offset: str | None) -> dict: params = {"filter": filter, "offset": offset, "limit": limit} response = http_request(method="GET", url_suffix="/devices/combined/host-groups/v1", params=params) return response def delete_host_groups(host_group_ids: list[str]) -> dict: params = {"ids": host_group_ids} response = http_request(method="DELETE", url_suffix="/devices/entities/host-groups/v1", params=params) return response def upload_batch_custom_ioc(ioc_batch: list[dict], timeout: float | None = None) -> dict: """ Upload a list of IOC """ payload = {"indicators": ioc_batch} return http_request("POST", "/iocs/entities/indicators/v1", json=payload, timeout=timeout) def create_exclusion(exclusion_type: str, body: dict) -> dict: """ Creates an exclusions based on a given json object. Args: exclusion_type: The exclusion type can be either ml (machine learning) or IOA`. exclusion_ids: A dict contains the exclusion data. Returns: Info about the created exclusion. """ return http_request(method="POST", url_suffix=f"/policy/entities/{exclusion_type}-exclusions/v1", json=body) def update_exclusion(exclusion_type: str, body: dict) -> dict: """ Updates an exclusions based on its ID and a given json object. Args: exclusion_type: The exclusion type can be either ml (machine learning) or IOA`. exclusion_ids: A dict contains the exclusion data. Returns: Info about the updated exclusion. """ return http_request("PATCH", f"/policy/entities/{exclusion_type}-exclusions/v1", json=body) def delete_exclusion(exclusion_type: str, exclusion_ids: list) -> dict: """ Deletes an exclusions based on its ID. Args: exclusion_type: The exclusion type can be either ml (machine learning) or IOA`. exclusion_ids: A list of exclusion IDs to delete. Returns: Info about the deleted exclusion. """ return http_request( method="DELETE", url_suffix=f'/policy/entities/{exclusion_type}-exclusions/v1{"?ids=" + "&ids=".join(exclusion_ids)}' ) def get_exclusions(exclusion_type: str, filter_query: str | None, params: dict) -> dict: """ Returns IDs of exclusions that match the filter / value Args: exclusion_type: The exclusion type can be either ml (machine learning) or IOA`. filter_query: Custom filter, For example `value:'<value>'`. params: API query params (sort, limit, offset). Returns: List of exclusion IDs. """ return http_request( method="GET", url_suffix=f"/policy/queries/{exclusion_type}-exclusions/v1", params=assign_params(filter=filter_query, **params), ) def get_exclusion_entities(exclusion_type: str, exclusion_ids: list) -> dict: """ Returns the exclusions based on a list of IDs. Args: exclusion_type: The exclusion type can be either ml (machine learning) or IOA`. exclusion_ids: A list of exclusion IDs to retrieve. Returns: List of exclusions. """ return http_request( method="GET", url_suffix=f'/policy/entities/{exclusion_type}-exclusions/v1{"?ids=" + "&ids=".join(exclusion_ids)}' ) def list_quarantined_files_id(files_filter: dict | None, query: dict, pagination: dict) -> dict: """ Returns the files ID's that match the filter / value. Args: files_filter: The exclusion type can be either ml (machine learning) or IOA`. query: The exclusion type can be either ml (machine learning) or IOA`. pagination: API query params for pagination (limit, offset). Returns: list: List of exclusions. """ return http_request( method="GET", url_suffix="/quarantine/queries/quarantined-files/v1", params=assign_params(filter=files_filter, q=build_query_params(query), **pagination), ) def list_quarantined_files(ids: list) -> dict: """ Returns the file's metadata based a list of IDs. Args: ids: A list of the IDs of the files. Returns: A list contains metadata about the files. """ return http_request(method="POST", url_suffix="/quarantine/entities/quarantined-files/GET/v1", json={"ids": ids}) def apply_quarantined_files_action(body: dict) -> dict: """ Applies action to quarantined files. Args: body: The request body with the parameters to update. Returns: A list contains metadata about the updated files. """ return http_request(method="PATCH", url_suffix="/quarantine/entities/quarantined-files/v1", json=body) """ MIRRORING COMMANDS """ def get_remote_data_command(args: dict[str, Any]): """ get-remote-data command: Returns an updated remote incident or detection. Args: args: id: incident or detection id to retrieve. lastUpdate: when was the last time we retrieved data. Returns: GetRemoteDataResponse object, which contain the incident or detection data to update. """ remote_args = GetRemoteDataArgs(args) remote_incident_id = remote_args.remote_incident_id reopen_statuses_list = argToList(demisto.params().get("reopen_statuses", "")) demisto.debug(f"In get_remote_data_command {reopen_statuses_list=}") mirrored_data = {} entries: list = [] try: demisto.debug( f"Performing get-remote-data command with incident or detection id: {remote_incident_id} " f"and last_update: {remote_args.last_update}" ) incident_type = find_incident_type(remote_incident_id) demisto.debug(f"Successfully identified incident type: {incident_type} for remote incident id: {remote_incident_id}") # for legacy endpoint detections if incident_type == IncidentType.LEGACY_ENDPOINT_DETECTION: mirrored_data, updated_object = get_remote_detection_data(remote_incident_id) if updated_object: demisto.debug(f"Update detection {remote_incident_id} with fields: {updated_object}") detection_type = "Detection" set_xsoar_entries( updated_object, entries, remote_incident_id, detection_type, reopen_statuses_list ) # sets in place elif incident_type == IncidentType.NGSIEM_CASE: mirrored_data, updated_object = get_remote_ngsiem_case_data(remote_incident_id) if updated_object: demisto.debug(f"Update ngsiem case {remote_incident_id} with fields: {updated_object}") set_xsoar_entries(updated_object, entries, remote_incident_id, NGSIEM_CASE, reopen_statuses_list) # sets in place # for endpoint in the new version elif incident_type in ( IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION, IncidentType.ON_DEMAND, IncidentType.THIRD_PARTY, IncidentType.NGSIEM_DETECTION, IncidentType.NGSIEM_AUTOMATED_LEAD, ): mirrored_data, updated_object, detection_type = get_remote_detection_data_for_multiple_types(remote_incident_id) if updated_object: demisto.debug(f"Update {detection_type} detection {remote_incident_id} with fields: {updated_object}") set_xsoar_entries( updated_object, entries, remote_incident_id, detection_type, reopen_statuses_list ) # sets in place elif incident_type == IncidentType.RECON: mirrored_data, updated_object, incident_type = get_remote_recon_data(remote_incident_id) if updated_object: demisto.debug(f"Recon-Log Update {incident_type} incident {remote_incident_id} with fields: {updated_object}") set_xsoar_entries(updated_object, entries, remote_incident_id, incident_type, reopen_statuses_list) elif incident_type is None and remote_incident_id.startswith("inc:"): demisto.debug( f"Skipping get-remote-data for deprecated Endpoint Incident {remote_incident_id}. " "Endpoint Incident mirroring is no longer supported." ) return GetRemoteDataResponse(mirrored_object=mirrored_data, entries=entries) else: # this is here as prints can disrupt mirroring raise Exception(f"Executed get-remote-data command with undefined id: {remote_incident_id}") if not updated_object: demisto.debug(f"No delta was found for detection {remote_incident_id}.") return GetRemoteDataResponse(mirrored_object=updated_object, entries=entries) except Exception as e: demisto.debug( f"Error in CrowdStrike Falcon incoming mirror for incident or detection: {remote_incident_id}\n" f"Error message: {e!s}" ) if not mirrored_data: mirrored_data = {"id": remote_incident_id} mirrored_data["in_mirror_error"] = str(e) return GetRemoteDataResponse(mirrored_object=mirrored_data, entries=[]) def find_incident_type(remote_incident_id: str): if IncidentType.LEGACY_ENDPOINT_DETECTION.value in remote_incident_id: return IncidentType.LEGACY_ENDPOINT_DETECTION if IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION.value in remote_incident_id: return IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION if IncidentType.ON_DEMAND.value in remote_incident_id: return IncidentType.ON_DEMAND if IncidentType.NGSIEM_DETECTION.value in remote_incident_id: return IncidentType.NGSIEM_DETECTION if IncidentType.THIRD_PARTY.value in remote_incident_id: return IncidentType.THIRD_PARTY if IncidentType.RECON.value in remote_incident_id: return IncidentType.RECON if IncidentType.NGSIEM_AUTOMATED_LEAD.value in remote_incident_id: return IncidentType.NGSIEM_AUTOMATED_LEAD if IncidentType.NGSIEM_CASE.value in remote_incident_id: return IncidentType.NGSIEM_CASE demisto.debug(f"Unable to determine incident type for remote incident id: {remote_incident_id}") return None def get_remote_ngsiem_case_data(remote_case_id: str): """ Called every time get-remote-data command runs on a NGSIEM case. Gets the relevant case entity from the remote system (CrowdStrike Falcon). We take from this entity only the relevant incoming mirroring fields, in order to do the mirroring. """ # We remove the prefix IncidentType to make the API call, since the CS API does not recognize our internal prefix original_remote_case_id = remote_case_id.replace(f"{IncidentType.NGSIEM_CASE.value}:", "", 1) mirrored_case_list = get_cases_details([original_remote_case_id]) if not mirrored_case_list: raise DemistoException(f"Could not find ngsiem case with {original_remote_case_id=}") mirrored_case = mirrored_case_list[0] updated_object = {"incident_type": NGSIEM_CASE} set_updated_object(updated_object, mirrored_case, NGSIEM_MIRRORING_FIELDS) return mirrored_case, updated_object def get_remote_detection_data(remote_incident_id: str): """ Called every time get-remote-data command runs on an detection. Gets the relevant detection entity from the remote system (CrowdStrike Falcon). The remote system returns a list with this entity in it. We take from this entity only the relevant incoming mirroring fields, in order to do the mirroring. """ mirrored_data_list = get_detections_entities([remote_incident_id]).get("resources", []) # a list with one dict in it mirrored_data = mirrored_data_list[0] # severity key name is different in the raptor version severity = mirrored_data.get("severity_name") mirrored_data["severity"] = severity_string_to_int(severity) demisto.debug(f"In get_remote_detection_data {remote_incident_id=} {mirrored_data=}") incoming_args = CS_FALCON_DETECTION_INCOMING_ARGS updated_object: dict[str, Any] = {"incident_type": "detection"} set_updated_object(updated_object, mirrored_data, incoming_args) demisto.debug(f"After set_updated_object {updated_object=}") return mirrored_data, updated_object def get_remote_detection_data_for_multiple_types(remote_incident_id): """ Gets the relevant detection entity from the remote system (CrowdStrike Falcon). This function handles the following detection types: - IDP (Identity Protection) - Mobile - Detection (not legacy) - OFP (Other File Protection) - ODS (On-Demand Scans) - NGSIEM (Next-Generation Security Information and Event Management) - THIRD PARTY Detection :type remote_incident_id: ``str`` :param remote_incident_id: The incident id to return its information. :return: The detection entity. :rtype ``dict`` :return: The object with the updated fields. :rtype ``dict`` :return: The detection type. :rtype ``str`` """ mirrored_data_list = get_detection_entities([remote_incident_id]).get("resources", []) # a list with one dict in it mirrored_data = mirrored_data_list[0] detection_type = "" mirroring_fields = ["status"] updated_object: dict[str, Any] = {} # Check type-based conditions first (more specific) before product-based conditions (more generic). # ODS and OFP detections carry product=epp but must be classified by their type, not their product. if "ofp" in mirrored_data["type"]: updated_object = {"incident_type": OFP_DETECTION} detection_type = "ofp" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS elif "ods" in mirrored_data["type"]: updated_object = {"incident_type": ON_DEMAND_SCANS_DETECTION} detection_type = "ods" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS elif "idp" in mirrored_data["product"]: updated_object = {"incident_type": IDP_DETECTION} detection_type = "IDP" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS_IDP elif "mobile" in mirrored_data["product"]: updated_object = {"incident_type": MOBILE_DETECTION} detection_type = "Mobile" mirroring_fields.append("mobile_detection_id") elif "epp" in mirrored_data["product"]: updated_object = {"incident_type": ENDPOINT_DETECTION} detection_type = "Detection" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS elif "ngsiem" in mirrored_data["product"]: updated_object = {"incident_type": NGSIEM_DETECTION} detection_type = "ngsiem" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS elif "xdr" in mirrored_data["product"]: updated_object = {"incident_type": NGSIEM_INCIDENT} detection_type = "xdr" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS elif "automated-lead" in mirrored_data["product"]: updated_object = {"incident_type": NGSIEM_AUTOMATED_LEAD} detection_type = "automated-lead" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS elif "thirdparty" in mirrored_data["product"]: updated_object = {"incident_type": THIRD_PARTY_DETECTION} detection_type = "thirdparty" mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS set_updated_object(updated_object, mirrored_data, mirroring_fields) demisto.debug(f"in get_remote_detection_data_for_multiple_types {mirrored_data=} { mirroring_fields=} {updated_object=}") return mirrored_data, updated_object, detection_type def get_remote_recon_data(remote_incident_id: str): """ Called every time get-remote-data command runs on a Recon notification. Gets the relevant Recon notification entity from the remote system (CrowdStrike Falcon). We take from this entity only the relevant incoming mirroring fields, in order to do the mirroring. :param remote_incident_id: The remote incident ID. :return: The mirrored data, the updated object, and the incident type. """ demisto.debug(f"Recon-Log in get_remote_recon_data {remote_incident_id=}") remote_id = remote_incident_id.replace(f"{IncidentType.RECON.value}", "", 1) mirrored_data_list = get_recon_notifications_detailed([remote_id]) if not mirrored_data_list: raise DemistoException(f"No Recon notification found for ID: {remote_incident_id}") mirrored_data = mirrored_data_list[0] demisto.debug(f"Recon-Log in get_remote_recon_data {mirrored_data=}") updated_object = {"incident_type": RECON_NOTIFICATION} set_updated_object(updated_object, mirrored_data, CS_FALCON_RECON_INCOMING_ARGS) if "notification.status" in updated_object: updated_object["status"] = updated_object["notification.status"] demisto.debug(f"Recon-Log in get_remote_recon_data {mirrored_data=} {CS_FALCON_RECON_INCOMING_ARGS=} {updated_object=}") return mirrored_data, updated_object, RECON_NOTIFICATION def update_remote_recon_notification(delta: Dict[str, Any], inc_status: int, remote_incident_id: str) -> str: """ Updates the status of a CrowdStrike Falcon Recon Notification via PATCH API call (Mirror Out) using the generic http_request function. :type delta: ``dict`` :param delta: Dictionary of fields changed in the local XSOAR incident. :type inc_status: ``int`` :param inc_status: The current status of the local XSOAR incident (0=Closed, 1=Active). :type remote_incident_id: ``str`` :param remote_incident_id: The ID of the Recon Notification in CrowdStrike. :return: The API response payload on success, or empty string if no relevant change found. :type: ``str`` """ demisto.debug(f"Recon-Log in update_remote_recon_notification {delta=} {inc_status=} {remote_incident_id=}") remote_id = remote_incident_id.replace(f"{IncidentType.RECON.value}", "", 1) if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta): demisto.debug(f"Recon-Log Closing Recon Notification: {remote_id} in remote system.") close_reason = delta.get("closeReason") status = "closed-true-positive" if close_reason in ("True Positive", "Resolved") else "closed-false-positive" result = str(patch_remote_entity(remote_id, status=status, is_recon_type=True)) demisto.debug(f"Recon-Log result closing Recon Notification: {remote_id} in remote system. {result=}") return result elif "status" in delta: demisto.debug(f"Recon-Log Updating Recon Notification: {remote_id} with status: {delta.get('status')} in remote system.") result = str(patch_remote_entity(remote_id, status=delta.get("status"), is_recon_type=True)) demisto.debug(f"Recon-Log result closing Recon Notification: {remote_id} in remote system. {result=}") return result demisto.debug(f"Recon-Log No relevant change found for Recon Notification: {remote_id}") return "" def get_modified_recon_ids(last_update_timestamp: str) -> List[str]: """ Fetches the IDs of Recon notifications that have been modified (status update) since the last synchronization timestamp. :param last_update_timestamp: The last update timestamp. :return: A list of modified Recon notification IDs. """ mirror_status_filter = ( f"status:['in-progress','closed-false-positive','closed-true-positive']+updated_date:>'{last_update_timestamp}'" ) demisto.debug(f"Recon-Log get_modified_recon_ids filter: {mirror_status_filter=}") try: ids, _, _ = recon_notifications_pagination( filter=mirror_status_filter, api_limit=RECON_API_LIMIT, recon_offset=0, fetch_limit=MAX_FETCH_RECON, is_fetch=False, ) prefixed_incident_ids = [f"{IncidentType.RECON.value}{id}" for id in ids] demisto.debug(f"Recon-Log get_modified_recon_ids return: {prefixed_incident_ids}") return prefixed_incident_ids except Exception as e: error_msg = f"Failed to fetch modified Recon IDs. Filter: {mirror_status_filter}. Error: {str(e)}" demisto.error(error_msg) return [] def set_xsoar_entries( updated_object: dict[str, Any], entries: list, remote_detection_id: str, incident_type_name: str, reopen_statuses_list: list ): """ Send the updated object to the relevant status handler :type updated_object: ``dict`` :param updated_object: The updated object. :type entries: ``list`` :param entries: The list of entries to add the new entry into. :type remote_detection_id: ``str`` :param remote_detection_id: the remote detection id :type reopen_statuses_list: ``list`` :param reopen_statuses_list: the set of statuses that should reopen an incident in XSOAR. :return: The response. :rtype ``dict`` """ reopen_statuses_set = {str(status).lower().strip().replace(" ", "_").replace("-", "_") for status in reopen_statuses_list} demisto.debug(f"In set_xsoar_entries {reopen_statuses_set=} {remote_detection_id=}") if demisto.params().get("close_incident"): status = updated_object.get("status", "").lower() if status.startswith("closed"): close_in_xsoar(entries, remote_detection_id, incident_type_name) elif updated_object.get("status", "").lower().replace("-", "_") in reopen_statuses_set: reopen_in_xsoar(entries, remote_detection_id, incident_type_name) else: demisto.debug( f"In set_xsoar_entries not closing and not reopening {remote_detection_id=}" f" since {updated_object.get('status')=} and {reopen_statuses_set=}." ) def close_in_xsoar(entries: list, remote_incident_id: str, incident_type_name: str): demisto.debug(f"{incident_type_name} is closed: {remote_incident_id}") entries.append( { "Type": EntryType.NOTE, "Contents": {"dbotIncidentClose": True, "closeReason": f"{incident_type_name} was closed on CrowdStrike Falcon"}, "ContentsFormat": EntryFormat.JSON, } ) def reopen_in_xsoar(entries: list, remote_incident_id: str, incident_type_name: str): demisto.debug(f"{incident_type_name} is reopened: {remote_incident_id}") entries.append({"Type": EntryType.NOTE, "Contents": {"dbotIncidentReopen": True}, "ContentsFormat": EntryFormat.JSON}) def set_updated_object(updated_object: dict[str, Any], mirrored_data: dict[str, Any], mirroring_fields: list[str]): """ Sets the updated object (in place) for the incident or detection we want to mirror in, from the mirrored data, according to the mirroring fields. In the mirrored data, the mirroring fields might be nested in a dict or in a dict inside a list (if so, their name will have a dot in it). Note that the fields that we mirror right now may have only one dot in them, so we only deal with this case. :param updated_object: The dictionary to set its values, so it will hold the fields we want to mirror in, with their values. :param mirrored_data: The data of the incident or detection we want to mirror in. :param mirroring_fields: The mirroring fields that we want to mirror in, given according to whether we want to mirror an incident or a detection. """ for field in mirroring_fields: if mirrored_data.get(field): updated_object[field] = mirrored_data.get(field) # if the field is not in mirrored_data, it might be a nested field - that has a . in its name elif "." in field: field_name_parts = field.split(".") nested_mirrored_data = mirrored_data.get(field_name_parts[0]) if isinstance(nested_mirrored_data, list): # if it is a list, it should hold a dictionary in it because it is a json structure for nested_field in nested_mirrored_data: if nested_field.get(field_name_parts[1]): updated_object[field] = nested_field.get(field_name_parts[1]) # finding the field in the first time it is satisfying break elif isinstance(nested_mirrored_data, dict) and nested_mirrored_data.get(field_name_parts[1]): updated_object[field] = nested_mirrored_data.get(field_name_parts[1]) def get_modified_remote_data_command(args: dict[str, Any]): """ Gets the modified remote incidents and detections IDs. Args: args: last_update: the last time we retrieved modified incidents and detections. Returns: GetModifiedRemoteDataResponse object, which contains a list of the retrieved incidents and detections IDs. """ remote_args = GetModifiedRemoteDataArgs(args) last_update_utc = dateparser.parse(remote_args.last_update, settings={"TIMEZONE": "UTC"}) # convert to utc format assert last_update_utc is not None, f"could not parse{remote_args.last_update}" last_update_timestamp = last_update_utc.strftime("%Y-%m-%dT%H:%M:%SZ") demisto.debug(f"Remote arguments last_update in UTC is {last_update_timestamp}") fetch_types = demisto.params().get("fetch_incidents_or_detections", "") raw_ids = [] if "Detections" in fetch_types or "Endpoint Detection" in fetch_types: raw_ids += get_fetch_detections(last_updated_timestamp=last_update_timestamp, has_limit=False).get("resources", []) if IDP_DETECTION_FETCH_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'idp'" ).get("resources", []) if MOBILE_DETECTION_FETCH_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'mobile'" ).get("resources", []) if ON_DEMAND_SCANS_DETECTION_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+type:'ods'" ).get("resources", []) if OFP_DETECTION_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+type:'ofp'" ).get("resources", []) if NGSIEM_DETECTION_FETCH_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'ngsiem'" ).get("resources", []) if NGSIEM_INCIDENT_FETCH_TYPE in fetch_types: demisto.debug("fetching ngsiem incident ids") raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'xdr'" ).get("resources", []) demisto.debug(f"new {raw_ids=}") if NGSIEM_AUTOMATED_LEADS_FETCH_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'automated-lead'" ).get("resources", []) if THIRD_PARTY_DETECTION_FETCH_TYPE in fetch_types: raw_ids += get_detections_ids( filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'thirdparty'" ).get("resources", []) if RECON_FETCH_TYPE in fetch_types: raw_ids += get_modified_recon_ids(last_update_timestamp=last_update_timestamp) if NGSIEM_CASES_FETCH_TYPE in fetch_types: _, case_ids = get_cases_data( url_filter=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'", limit=INCIDENTS_PER_FETCH, offset=0, ) raw_ids += [f"{IncidentType.NGSIEM_CASE.value}:{case_id}" for case_id in case_ids] modified_ids_to_mirror = list(map(str, raw_ids)) demisto.debug(f"All ids to mirror in are: {modified_ids_to_mirror}") return GetModifiedRemoteDataResponse(modified_ids_to_mirror) def update_remote_system_command(args: dict[str, Any]) -> str: """ Mirrors out local changes to the remote system. Args: args: A dictionary containing the data regarding a modified incident, including: data, entries, incident_changed, remote_incident_id, inc_status, delta Returns: The remote incident id that was modified. This is important when the incident is newly created remotely. """ parsed_args = UpdateRemoteSystemArgs(args) delta = parsed_args.delta remote_incident_id = parsed_args.remote_incident_id demisto.debug(f"Got the following data {parsed_args.data}, and delta {delta} for the following {remote_incident_id=}.") if delta: demisto.debug(f"Got the following delta keys {list(delta.keys())}.") try: incident_type = find_incident_type(remote_incident_id) demisto.debug(f"Successfully identified incident type: {incident_type} for remote incident id: {remote_incident_id}") if parsed_args.incident_changed: if incident_type in (IncidentType.ON_DEMAND, IncidentType.LEGACY_ENDPOINT_DETECTION): result = update_remote_detection(delta, parsed_args.inc_status, remote_incident_id) if result: demisto.debug(f"Detection updated successfully. Result: {result}") elif incident_type in ( IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION, IncidentType.NGSIEM_DETECTION, IncidentType.THIRD_PARTY, IncidentType.NGSIEM_AUTOMATED_LEAD, ): result = update_remote_for_multiple_detection_types(delta, parsed_args.inc_status, remote_incident_id) if result: demisto.debug(f"IDP/Mobile/NGSIEM/Third Party Detection updated successfully. Result: {result}") elif incident_type == IncidentType.NGSIEM_CASE: result = update_remote_ngsiem_case(delta, parsed_args.inc_status, remote_incident_id) if result: demisto.debug(f"NGSIEM case updated successfully. Result: {result}") elif incident_type == IncidentType.RECON: result = update_remote_recon_notification(delta, parsed_args.inc_status, remote_incident_id) if result: demisto.debug(f"Recon-Log Recon notification updated successfully. Result: {result}") elif incident_type is None and remote_incident_id.startswith("inc:"): demisto.debug( f"Skipping update-remote-system for deprecated Endpoint Incident {remote_incident_id}. " "Endpoint Incident mirroring is no longer supported." ) else: raise Exception(f"Executed update-remote-system command with undefined id: {remote_incident_id}") else: demisto.debug(f"Skipping updating remote incident or detection {remote_incident_id} as it didn't change.") except Exception as e: demisto.error( f"Error in CrowdStrike Falcon outgoing mirror for incident or detection {remote_incident_id}. " f"Error message: {e!s}" ) return remote_incident_id def close_in_cs_falcon(delta: dict[str, Any]) -> bool: """ Closing in the remote system should happen only when both: 1. The user asked for it 2. One of the closing fields appears in the delta The second is mandatory so we will not send a closing request at all of the mirroring requests that happen after closing an incident (in case where the incident is updated so there is a delta, but it is not the status that was changed). """ closing_fields = {"closeReason", "closingUserId", "closeNotes"} return demisto.params().get("close_in_cs_falcon") and any(field in delta for field in closing_fields) def update_remote_detection(delta, inc_status: IncidentStatus, detection_id: str) -> str: if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta): demisto.debug(f"Closing detection with remote ID {detection_id} in remote system.") return str(update_detection_request([detection_id], "closed")) # status field in CS Falcon is mapped to State field in XSOAR elif "status" in delta: demisto.debug(f'Detection with remote ID {detection_id} status will change to "{delta.get("status")}" in remote system.') return str(update_detection_request([detection_id], delta.get("status"))) return "" def update_remote_ngsiem_case(delta, inc_status: IncidentStatus, ngsiem_case_id: str) -> str: """ Sends the request to update the relevant NGSIEM case entity. :type delta: ``dict`` :param delta: The modified fields. :type inc_status: ``IncidentStatus`` :param inc_status: The NGSIEM case status. :type ngsiem_case_id: ``str`` :param ngsiem_case_id: The NGSIEM case ID to update. :return: The response. :rtype ``str`` """ remote_id = ngsiem_case_id.replace(f"{IncidentType.NGSIEM_CASE.value}:", "", 1) if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta): demisto.debug(f"Closing case with remote ID {remote_id} in remote system.") return str(patch_remote_entity(remote_id, status="closed")) elif "status" in delta: return str(patch_remote_entity(remote_id, status=delta.get("status"))) return "" def update_remote_for_multiple_detection_types(delta, inc_status: IncidentStatus, detection_id: str) -> str: """ Sends the request to update the relevant IDP/Mobile/NGSIEM/Third Party/Recon Notifications entity. :type delta: ``dict`` :param delta: The modified fields. :type inc_status: ``IncidentStatus`` :param inc_status: The IDP/Mobile/NGSIEM/Third Party/Recon Notifications status. :type detection_id: ``str`` :param detection_id: The IDP/Mobile/NGSIEM/Third Party/Recon Notifications ID to update. """ if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta): demisto.debug( f"Closing IDP/Mobile/NGSIEM/Third Party/Recon Notifications with remote ID {detection_id} in remote system." ) return str(update_request_for_multiple_detection_types([detection_id], "closed")) # status field in CS Falcon is mapped to State field in XSOAR elif "status" in delta: demisto.debug(f'Detection with remote ID {detection_id} status will change to "{delta.get("status")}" in remote system.') return str(update_request_for_multiple_detection_types([detection_id], delta.get("status"))) return "" def get_mapping_fields_command() -> GetMappingFieldsResponse: """ Returns the list of fields to map in outgoing mirroring, for incidents and detections. """ mapping_response = GetMappingFieldsResponse() # Supported only in the new version (Raptor) and not in the legacy version detection_types = [ "CrowdStrike Falcon Detection", "CrowdStrike Falcon OFP Detection", "CrowdStrike Falcon On-Demand Scans Detection", ] for detection_type in detection_types: detection_type_scheme = SchemeTypeMapping(type_name=detection_type) for argument, description in CS_FALCON_DETECTION_OUTGOING_ARGS.items(): detection_type_scheme.add_field(name=argument, description=description) mapping_response.add_scheme_type(detection_type_scheme) return mapping_response """ COMMANDS FUNCTIONS """ def migrate_last_run(last_run: dict[str, str] | list[dict], is_fetch_events: bool = False) -> list[dict]: """This function migrated from old last run object to new last run object Args: last_run (dict[str, str]): Old last run object. Returns: list[dict]: New last run object. """ if isinstance(last_run, list): return last_run else: updated_last_run_detections: dict[str, str | None] = {} if (detection_time := last_run.get("first_behavior_detection_time")) and ( detection_time_date := dateparser.parse(detection_time) ): updated_last_run_detections["time"] = detection_time_date.strftime(DATE_FORMAT) updated_last_run_incidents: dict[str, str | None] = {} if (incident_time := last_run.get("first_behavior_incident_time")) and ( incident_time_date := dateparser.parse(incident_time) ): updated_last_run_incidents["time"] = incident_time_date.strftime(DATE_FORMAT) last_run_length = TOTAL_FETCH_TYPE_XSIAM if is_fetch_events else TOTAL_FETCH_TYPE_XSOAR result = [updated_last_run_detections, updated_last_run_incidents] current_length = len(result) result.extend([{} for _ in range(last_run_length - current_length)]) return result def fetch_endpoint_detections(current_fetch_info_detections, look_back, is_fetch_events): """ Fetch detections from CrowdStrike Falcon api. Args: current_fetch_info_detections (dict): The last_run for detection fetch type, Contains information about the last fetch run look_back (int): Number of days to look back for detection is_fetch_events: Flag to determine whether it's for fetch-events command (XSIAM) or fetch-incidents command (XSOAR). Returns: tuple: A tuple containing a list of detections and the updated fetch information dictionary. """ detections = [] # The configured per-run limit (10000 for XSIAM, "Max incidents per fetch" for XSOAR). base_fetch_limit = MAX_FETCH_DETECTION_PER_API_CALL if is_fetch_events else INCIDENTS_PER_FETCH detections_offset: int = current_fetch_info_detections.get("offset") or 0 start_fetch_time, end_fetch_time = get_fetch_run_time_range( last_run=current_fetch_info_detections, first_fetch=FETCH_TIME, look_back=look_back, date_format=DETECTION_DATE_FORMAT ) fetch_limit = current_fetch_info_detections.get("limit") or base_fetch_limit incident_type = "detection" # The API rejects requests where offset + limit exceeds MAX_FETCH_SIZE. With look_back, fetch_limit can grow # past that bound, so cap the value sent to the API while keeping fetch_limit for dedup and last_run bookkeeping. api_limit = min(fetch_limit, MAX_FETCH_SIZE - detections_offset) fetch_query = demisto.params().get("fetch_query") if fetch_query: fetch_query = f"(created_timestamp:>'{start_fetch_time}')+({fetch_query})" response = get_fetch_detections(filter_arg=fetch_query, limit=api_limit, offset=detections_offset) else: response = get_fetch_detections(last_created_timestamp=start_fetch_time, limit=api_limit, offset=detections_offset) detections_ids: list[dict] = demisto.get(response, "resources", []) total_detections = demisto.get(response, "meta.pagination.total") detections_offset = calculate_new_offset(detections_offset, len(detections_ids), total_detections) if detections_offset: if detections_offset + fetch_limit > MAX_FETCH_SIZE: demisto.debug( f"CrowdStrikeFalconMsg: The new offset: {detections_offset} + limit: {fetch_limit} reached " f"{MAX_FETCH_SIZE}, resetting the offset to 0" ) detections_offset = 0 demisto.debug(f"CrowdStrikeFalconMsg: The new detections offset is {detections_offset}") raw_res = get_detections_entities(detections_ids) if raw_res is not None and "resources" in raw_res: full_detections = demisto.get(raw_res, "resources") # detection_id is for the old version of the API, composite_id is for the new version (Raptor) for detection in full_detections: detection_id = detection.get("composite_id") detection["incident_type"] = incident_type demisto.debug( f"CrowdStrikeFalconMsg: Detection {detection_id} " f"was fetched which was created in {detection['created_timestamp']}" ) incident = detection_to_incident(detection, is_fetch_events=is_fetch_events) detections.append(incident) detections = filter_incidents_by_duplicates_and_limit( incidents_res=detections, last_run=current_fetch_info_detections, fetch_limit=fetch_limit, id_field="name" ) for detection in detections: occurred = dateparser.parse(detection["occurred"]) if occurred: detection["occurred"] = occurred.strftime(DETECTION_DATE_FORMAT) demisto.debug(f"CrowdStrikeFalconMsg: Detection {detection['name']} occurred at {detection['occurred']}") current_fetch_info_detections = update_last_run_object( last_run=current_fetch_info_detections, incidents=detections, fetch_limit=base_fetch_limit, start_fetch_time=start_fetch_time, end_fetch_time=end_fetch_time, look_back=look_back, created_time_field="occurred", id_field="name", date_format=DETECTION_DATE_FORMAT, new_offset=detections_offset, ) demisto.debug(f"CrowdStrikeFalconMsg: Ending fetch endpoint_detections. Fetched {len(detections) if detections else 0}") return detections, current_fetch_info_detections def fetch_iom_incidents(iom_last_run): demisto.debug("Fetching Indicator of Misconfiguration incidents") demisto.debug(f"{iom_last_run=}") fetch_query = demisto.params().get("iom_fetch_query", "") validate_iom_fetch_query(iom_fetch_query=fetch_query) last_resource_ids, iom_next_token, last_scan_time, first_fetch_timestamp = get_current_fetch_data( last_run_object=iom_last_run, date_format=IOM_DATE_FORMAT, last_date_key="last_scan_time", next_token_key="iom_next_token", last_fetched_ids_key="last_resource_ids", ) filter = create_iom_filter( is_paginating=bool(iom_next_token), last_fetch_filter=iom_last_run.get("last_fetch_filter", ""), last_scan_time=last_scan_time, first_fetch_timestamp=first_fetch_timestamp, configured_fetch_query=fetch_query, ) demisto.debug(f"IOM {filter=}") iom_resource_ids, iom_new_next_token = iom_ids_pagination( filter=filter, iom_next_token=iom_next_token, fetch_limit=INCIDENTS_PER_FETCH, api_limit=500 ) demisto.debug(f'Fetched the following IOM resource IDS: {", ".join(iom_resource_ids)}') iom_incidents, fetched_resource_ids, new_scan_time = parse_ioa_iom_incidents( fetched_data=get_iom_resources(iom_resource_ids=iom_resource_ids), last_date=last_scan_time, last_fetched_ids=last_resource_ids, date_key="scan_time", id_key="id", date_format=IOM_DATE_FORMAT, is_paginating=bool(iom_new_next_token or iom_next_token), to_incident_context=iom_resource_to_incident, incident_type="iom_configurations", ) iom_last_run = { "iom_next_token": iom_new_next_token, "last_scan_time": new_scan_time, "last_fetch_filter": filter, "last_resource_ids": fetched_resource_ids or last_resource_ids, } return iom_incidents, iom_last_run def set_last_run_per_type(last_run: list, index: LastRunIndex, data: dict, is_fetch_events=False) -> None: """ Safely set the specific sub last_run dictionary in the main last_run list. Args: last_run: The last_run list of dictionaries, where each index represents a last_run object for different fetch type. fetch_type: The fetch type index enum data: The data to set for the specified fetch type is_fetch_events: Flag to determine whether it's for fetch-events command (XSIAM) or fetch-incidents command (XSOAR). Returns: The updated last_run list. """ demisto.debug(f"CrowdStrikeFalconMsg: set_last_run_per_type with {index=}") if not isinstance(data, dict): return_error(f"Invalid data type : last_run is a list of dictionary, expected dictionary, got {type(data).__name__}") last_run_length = TOTAL_FETCH_TYPE_XSIAM if is_fetch_events else TOTAL_FETCH_TYPE_XSOAR if index >= last_run_length: return_error(f"Invalid last_run index {index}, cannot exceed {last_run_length - 1}") if index < 0: return_error(f"Invalid last_run index {index}, index cannot be negative") # Extend the list if necessary to accommodate the fetch_type index while len(last_run) <= index: last_run.append({}) last_run[index] = data demisto.debug(f"CrowdStrikeFalconMsg: {last_run}") def get_last_run_per_type(last_run: list, fetch_type: LastRunIndex) -> dict: """ Safely get the specific sub last_run dictionary from the main last_run list. Args: last_run: The last_run list of dictionaries, where each index represents a last_run object for different fetch type. fetch_type: The fetch type index enum default: Default value if not present (defaults to empty dict) Returns: The last_run dict from the list for the requested type. """ if len(last_run) <= fetch_type: return {} return last_run[fetch_type] def fetch_items(command="fetch-incidents"): """ Fetch incidents or events from CrowdStrike Falcon based on configuration. Args: command (str): Either 'fetch-incidents' or 'fetch-events' Returns: tuple: (last_run, items) where last_run is the updated state and items are the fetched incidents/events """ is_fetch_events = command == "fetch-events" items = [] params = demisto.params() # Initialize and migrate last_run last_run = demisto.getLastRun() last_run_length = TOTAL_FETCH_TYPE_XSIAM if is_fetch_events else TOTAL_FETCH_TYPE_XSOAR if not last_run: last_run = [{} for _ in range(last_run_length)] last_run = migrate_last_run(last_run, is_fetch_events) # last_run objects - common for fetch_incident and fetch_events detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.DETECTIONS) idp_detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.IDP_DETECTIONS) mobile_detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.MOBILE_DETECTIONS) on_demand_detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.ON_DEMAND_DETECTIONS) ofp_detection_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.OFP_DETECTION) # last_run objects - fetch types only for fetch-incidents iom_last_run: dict[str, Any] = {} ioa_last_run: dict[str, Any] = {} third_party_detection_last_run: dict[str, Any] = {} ngsiem_detection_last_run: dict[str, Any] = {} recon_last_run: dict[str, Any] = {} ngsiem_incident_last_run: dict[str, Any] = {} ngsiem_automated_lead_last_run: dict[str, Any] = {} ngsiem_case_last_run: dict[str, Any] = {} if is_fetch_events: fetch_incidents_or_detections = params.get("fetch_events_or_detections", "") look_back = int(params.get("look_back_xsiam") or 2) else: fetch_incidents_or_detections = params.get("fetch_incidents_or_detections", "") look_back = int(params.get("look_back") or 2) # last_run object - Only for fetch_incident iom_last_run = get_last_run_per_type(last_run, LastRunIndex.IOM) ioa_last_run = get_last_run_per_type(last_run, LastRunIndex.IOA) third_party_detection_last_run = get_last_run_per_type(last_run, LastRunIndex.THIRD_PARTY_DETECTIONS) ngsiem_detection_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_DETECTIONS) recon_last_run = get_last_run_per_type(last_run, LastRunIndex.RECON_NOTIFICATIONS) ngsiem_incident_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_INCIDENTS) ngsiem_automated_lead_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_AUTOMATED_LEADS) ngsiem_case_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_CASES) demisto.debug(f"CrowdstrikeFalconMsg: Selected fetch types: {fetch_incidents_or_detections}") # Fetch Endpoint Detections if is_detection_fetch_type_selected(selected_types=fetch_incidents_or_detections): # if "Detections" in fetch_incidents_or_detections or "Endpoint Detection" in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch Detections") demisto.debug(f"CrowdStrikeFalconMsg: Current detections_last_run object: {detections_last_run}") fetched_detections, detections_last_run = fetch_endpoint_detections(detections_last_run, look_back, is_fetch_events) items.extend(fetched_detections) # Fetch IDP Detections if IDP_DETECTION_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch IDP Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current IDP Detection last_run object: {idp_detections_last_run}") fetched_idp_detections, idp_detections_last_run = fetch_detections_by_product_type( idp_detections_last_run, look_back=look_back, fetch_query=params.get("idp_detections_fetch_query", ""), detections_type=IDP_DETECTION, product_type="idp", detection_name_prefix=IDP_DETECTION_FETCH_TYPE, start_time_key="created_timestamp", is_fetch_events=is_fetch_events, ) items.extend(fetched_idp_detections) # Fetch Mobile Detections if MOBILE_DETECTION_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch Mobile Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current Mobile Detection last_run object: {mobile_detections_last_run}") fetched_mobile_detections, mobile_detections_last_run = fetch_detections_by_product_type( mobile_detections_last_run, look_back=look_back, fetch_query=params.get("mobile_detections_fetch_query", ""), detections_type=MOBILE_DETECTION, product_type="mobile", detection_name_prefix=MOBILE_DETECTION_FETCH_TYPE, start_time_key="timestamp", is_fetch_events=is_fetch_events, ) items.extend(fetched_mobile_detections) # Fetch On-Demand Scan Detections if ON_DEMAND_SCANS_DETECTION_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch ODS Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current ODS Detection last_run object: {on_demand_detections_last_run}") fetched_on_demand_detections, on_demand_detections_last_run = fetch_detections_by_product_type( on_demand_detections_last_run, look_back=look_back, fetch_query=params.get("on_demand_fetch_query", ""), detections_type=ON_DEMAND_SCANS_DETECTION, product_type="ods", detection_name_prefix=ON_DEMAND_SCANS_DETECTION_TYPE, start_time_key="created_timestamp", is_fetch_events=is_fetch_events, ) items.extend(fetched_on_demand_detections) # Fetch OFP Detections if OFP_DETECTION_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch OFP Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current OFP Detection last_run object: {ofp_detection_last_run}") fetched_ofp_detections, ofp_detection_last_run = fetch_detections_by_product_type( ofp_detection_last_run, look_back=look_back, fetch_query=params.get("ofp_detection_fetch_query", ""), detections_type=OFP_DETECTION, product_type="ofp", detection_name_prefix=OFP_DETECTION_TYPE, start_time_key="created_timestamp", is_fetch_events=is_fetch_events, ) items.extend(fetched_ofp_detections) if NGSIEM_INCIDENT_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdstrikeFalconMsg: Start fetch NGSIEM Incident Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Incident last_run_object: {ngsiem_incident_last_run}") fetched_ngsiem_incidents, ngsiem_incident_last_run = fetch_detections_by_product_type( ngsiem_incident_last_run, look_back=look_back, fetch_query=params.get("ngsiem_incidents_fetch_query", ""), detections_type=NGSIEM_INCIDENT, product_type="xdr", detection_name_prefix=NGSIEM_INCIDENT_FETCH_TYPE, start_time_key="created_timestamp", is_fetch_events=False, ) items.extend(fetched_ngsiem_incidents) if NGSIEM_AUTOMATED_LEADS_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdstrikeFalconMsg: Start fetch NGSIEM Automated Lead") demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Automated Lead last_run_object: {ngsiem_automated_lead_last_run}") fetched_ngsiem_automated_leads, ngsiem_automated_lead_last_run = fetch_detections_by_product_type( ngsiem_automated_lead_last_run, look_back=look_back, fetch_query=params.get("automated_leads_fetch_query", ""), detections_type=NGSIEM_AUTOMATED_LEAD, product_type="automated-lead", detection_name_prefix=NGSIEM_AUTOMATED_LEADS_FETCH_TYPE, start_time_key="created_timestamp", is_fetch_events=False, ) demisto.debug(f"Extending items with Automated Leads: {fetched_ngsiem_automated_leads}") items.extend(fetched_ngsiem_automated_leads) if NGSIEM_CASES_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdstrikeFalconMsg: Start fetch NGSIEM Cases") demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Cases last_run_object: {ngsiem_case_last_run}") fetched_ngsiem_cases, ngsiem_case_last_run = fetch_ngsiem_cases( ngsiem_case_last_run, look_back, params.get("ngsiem_cases_fetch_query", "") ) items.extend(fetched_ngsiem_cases) # Fetch Indicators of Misconfiguration (IOM) - supported for fetch-incidents command only. if not is_fetch_events and IOM_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch IOM") demisto.debug(f"CrowdStrikeFalconMsg: Current IOM last_run object: {iom_last_run}") fetched_iom_incidents, iom_last_run = fetch_iom_incidents(iom_last_run) items.extend(fetched_iom_incidents) # Fetch Indicators of Attack (IOA) - supported for fetch-incidents command only. if not is_fetch_events and IOA_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch IOA") demisto.debug(f"CrowdStrikeFalconMsg: Current IOA last_run object: {ioa_last_run}") fetched_ioa_incidents, ioa_last_run = fetch_detections_by_product_type( ioa_last_run, look_back=look_back, fetch_query=params.get("ioa_fetch_query", ""), detections_type=IOA_DETECTION, product_type=IncidentType.IOA_TYPE_TAG.value, detection_name_prefix=IOA_FETCH_TYPE, start_time_key="created_timestamp", is_fetch_events=False, ) items.extend(fetched_ioa_incidents) if not is_fetch_events and NGSIEM_DETECTION_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch NGSIEM Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Detection last_run object: {ngsiem_detection_last_run}") fetched_ngsiem_detections, ngsiem_detection_last_run = fetch_detections_by_product_type( ngsiem_detection_last_run, look_back=look_back, fetch_query=params.get("ngsiem_detection_fetch_query", ""), detections_type=NGSIEM_DETECTION, product_type="ngsiem", detection_name_prefix=NGSIEM_DETECTION_FETCH_TYPE, start_time_key="created_timestamp", is_fetch_events=is_fetch_events, ) items.extend(fetched_ngsiem_detections) if not is_fetch_events and THIRD_PARTY_DETECTION_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("CrowdStrikeFalconMsg: Start fetch THIRD PARTY Detection") demisto.debug(f"CrowdStrikeFalconMsg: Current THIRD PARTY Detection last_run object: {third_party_detection_last_run}") fetched_third_party_detections, third_party_detection_last_run = fetch_detections_by_product_type( third_party_detection_last_run, look_back=look_back, fetch_query=params.get("third_party_detection_fetch_query", ""), detections_type=THIRD_PARTY_DETECTION, product_type="thirdparty", detection_name_prefix=THIRD_PARTY_DETECTION_FETCH_TYPE, start_time_key="created_timestamp", is_fetch_events=is_fetch_events, ) items.extend(fetched_third_party_detections) if not is_fetch_events and RECON_FETCH_TYPE in fetch_incidents_or_detections: demisto.debug("Recon-Log CrowdStrikeFalconMsg: Start fetch Recon Notifications") demisto.debug(f"Recon-Log CrowdStrikeFalconMsg: Current Recon Notifications last_run object: {recon_last_run}") fetched_recon_notifications, recon_last_run = fetch_recon_incidents(recon_last_run) demisto.debug(f"Recon-Log Recon updated_run: {recon_last_run}") items.extend(fetched_recon_notifications) # Assign each sub last_run info per type at its proper index set_last_run_per_type(last_run, index=LastRunIndex.DETECTIONS, data=detections_last_run, is_fetch_events=is_fetch_events) set_last_run_per_type( last_run, index=LastRunIndex.IDP_DETECTIONS, data=idp_detections_last_run, is_fetch_events=is_fetch_events ) set_last_run_per_type( last_run, index=LastRunIndex.MOBILE_DETECTIONS, data=mobile_detections_last_run, is_fetch_events=is_fetch_events ) set_last_run_per_type( last_run, index=LastRunIndex.ON_DEMAND_DETECTIONS, data=on_demand_detections_last_run, is_fetch_events=is_fetch_events ) set_last_run_per_type( last_run, index=LastRunIndex.OFP_DETECTION, data=ofp_detection_last_run, is_fetch_events=is_fetch_events ) if not is_fetch_events: set_last_run_per_type(last_run, index=LastRunIndex.IOM, data=iom_last_run, is_fetch_events=is_fetch_events) set_last_run_per_type(last_run, index=LastRunIndex.IOA, data=ioa_last_run, is_fetch_events=is_fetch_events) set_last_run_per_type( last_run, index=LastRunIndex.THIRD_PARTY_DETECTIONS, data=third_party_detection_last_run, is_fetch_events=is_fetch_events, ) set_last_run_per_type( last_run, index=LastRunIndex.NGSIEM_DETECTIONS, data=ngsiem_detection_last_run, is_fetch_events=is_fetch_events ) set_last_run_per_type( last_run, index=LastRunIndex.RECON_NOTIFICATIONS, data=recon_last_run, is_fetch_events=is_fetch_events ) set_last_run_per_type(last_run, index=LastRunIndex.NGSIEM_INCIDENTS, data=ngsiem_incident_last_run, is_fetch_events=False) set_last_run_per_type( last_run, index=LastRunIndex.NGSIEM_AUTOMATED_LEADS, data=ngsiem_automated_lead_last_run, is_fetch_events=False ) set_last_run_per_type(last_run, index=LastRunIndex.NGSIEM_CASES, data=ngsiem_case_last_run, is_fetch_events=False) demisto.setLastRun(last_run) demisto.debug(f"CrowdStrikeFalconMsg: Updated last_run object after fetch: {last_run}") return last_run, items def list_cnapp_alerts_command(args: dict[str, Any]) -> CommandResults: filter = args.get("filter", "") response = preform_get_cnapp_alerts_request(filter=filter) alerts = response.get("resources", []) return CommandResults( outputs_prefix="CrowdStrike.CnappAlert", outputs_key_field="detection_name", outputs=alerts, readable_output=tableToMarkdown( name="CrowdStrike CNAPP alerts", t=alerts, sort_headers=False, ), raw_response=alerts, ) def preform_get_cnapp_alerts_request(offset=0, filter=""): """Preforms request to get CNAPP alerts Args: offset (int, optional): The offset for pagination filter (str, optional): A filter to use if given. Returns: the response. """ limit = 100 endpoint = "/container-security/combined/container-alerts/v1" params = {"offset": offset, "limit": limit} if filter: params["filter"] = filter demisto.info(f"Preforming a reuest to get cnapp alerts. Calling {endpoint=} with {params=}") return http_request("GET", endpoint, params) def get_cnapp_assets(): last_run = demisto.getAssetsLastRun() demisto.debug(f"Starting a new cnapp fetch assets execution with {last_run=}") snapshot_id = last_run.get("snapshot_id", str(round(time.time() * 1000))) offset = int(last_run.get("offset", 0)) total_fetched_until_now = int(last_run.get("total_fetched_until_now", 0)) new_last_run = {} response = preform_get_cnapp_alerts_request(offset=offset) cnapp_alerts = response.get("resources", []) total_detections = demisto.get(response, "meta.pagination.total") total_fetched_until_now += len(cnapp_alerts) demisto.debug(f"Fetched {len(cnapp_alerts)} CNAPP assets, reulsting a toal of {total_fetched_until_now}.") if total_detections > total_fetched_until_now: # type: ignore demisto.debug( f"Fetch {total_fetched_until_now} assets out of expected {total_detections} so far, setting NextTrigger to 0." ) offset += len(cnapp_alerts) items_count = 1 new_last_run = { "offset": offset, "total_fetched_until_now": total_fetched_until_now, "snapshot_id": snapshot_id, "nextTrigger": "0", "type": 1, } else: demisto.debug(f"Fetched all expected assets ({total_detections}), closing the snapshot.") offset = 0 items_count = total_fetched_until_now new_last_run = {"offset": offset, "total_fetched_until_now": 0} return new_last_run, cnapp_alerts, items_count, snapshot_id def save_spotlight_state(context_store: ContentClientContextStore, spotlight_state: ContentClientState) -> None: """ Save Spotlight state to integration context without breaking other keys. Args: context_store: Context store for writing integration context spotlight_state: Spotlight state object to save """ # Update only the spotlight_assets key, preserving all other context integration_context = context_store.read() integration_context["spotlight_assets"] = spotlight_state.to_dict() context_store.write(integration_context) spotlight_data = integration_context.get("spotlight_assets", {}) log_falcon_assets(f"Saved Spotlight state: metadata={spotlight_data.get('metadata', {})}") class AssetsDeviceHandler: """ Handler for enriching and ingesting device assets asynchronously. Buffers unique AIDs from vulnerability batches, enriches them via the Devices API, and sends enriched data to XSIAM using the async fire-and-forget pattern. Maintains separate batch tracking from vulnerability chain to prevent out-of-order context saves. """ def __init__( self, client: ContentClient, context_store: ContentClientContextStore, spotlight_state: ContentClientState, snapshot_id: str, processed_aids: set, batch_limit: int = MAX_FETCH_SPOTLIGHT_ASSETS, ): """ Initialize the AssetsDeviceHandler. Args: client: ContentClient instance for API calls context_store: Context store for thread-safe state persistence spotlight_state: Spotlight state object for metadata updates snapshot_id: Snapshot ID for asset collection tracking processed_aids: Set of already processed AIDs (for deduplication) batch_limit: Number of AIDs to accumulate before triggering enrichment """ self.client = client self.context_store = context_store self.spotlight_state = spotlight_state self.snapshot_id = snapshot_id self.processed_aids = processed_aids self.pending_buffer: set[str] = set() self.batch_limit = batch_limit # SEPARATE batch tracking for assets chain (independent from vulnerability chain) self.asset_batch_counter = 0 self.asset_last_saved_batch_number = 0 self.running_tasks: set[asyncio.Task] = set() async def receive_new_aids(self, new_aids: set[str]) -> None: """ Receive new AIDs and trigger enrichment when buffer reaches batch_limit. Keeps at least 1 item in the buffer to ensure we can send the final count with the last batch. Args: new_aids: Set of AIDs extracted from vulnerability batch """ # Deduplicate against already processed AIDs unique_new = new_aids - self.processed_aids self.pending_buffer.update(unique_new) log_falcon_assets(f"AssetsDeviceHandler: Received {len(unique_new)} new AIDs, buffer size: {len(self.pending_buffer)}") # Trigger enrichment for full batches, but keep at least 1 item for the final flush # Threshold is batch_limit + 1 to ensure we always have leftovers for flush_remaining threshold = self.batch_limit + 1 while len(self.pending_buffer) >= threshold: full_list = list(self.pending_buffer) batch = full_list[: self.batch_limit] self.pending_buffer = set(full_list[self.batch_limit :]) log_falcon_assets(f"AssetsDeviceHandler: Buffer full, triggering enrichment for {len(batch)} AIDs") # Create async enrichment task task = asyncio.create_task(self.enrich_and_ingest_batch(batch)) self.running_tasks.add(task) task.add_done_callback(self.running_tasks.discard) async def enrich_and_ingest_batch(self, aid_batch: list[str], final_items_count: int = 1) -> None: """ Enrich a batch of AIDs via Devices API and send to XSIAM. Args: aid_batch: List of AIDs to enrich final_items_count: Total items count to send to XSIAM (1 for intermediate batches, actual total for final batch) """ # Increment ASSET batch counter (separate from vulnerability chain) self.asset_batch_counter += 1 current_batch_number = self.asset_batch_counter log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Enriching {len(aid_batch)} AIDs") try: # 1. Enrich the AID batch via ContentClient. # /devices/entities/devices/v2 returns HTTP 400 on partial success (valid devices in # "resources", rejected IDs in "errors"). Accept 400 (ok_codes) to ingest the resolved # devices instead of discarding the whole batch and raising on the full response body. response = await self.client._request( method="POST", url_suffix="/devices/entities/devices/v2", json_data={"ids": aid_batch}, ok_codes=(200, 400), ) log_falcon_assets( f"AssetsDeviceHandler: [Batch {current_batch_number}] CrowdStrike response status={response.status_code}" ) # Parse response response_data = response.json() devices = response_data.get("resources", []) # Log any invalid device IDs returned in the partial-success "errors" array. errors = response_data.get("errors") or [] if errors: log_falcon_assets( f"AssetsDeviceHandler: [Batch {current_batch_number}] CrowdStrike returned " f"{len(errors)} invalid device ID(s); skipping them. First error: {errors[0].get('message')}", "warning", ) # Mark the entire batch processed (including invalid IDs) regardless of whether any # devices resolved, so permanently-invalid IDs are not retried indefinitely on every fetch. self.processed_aids.update(aid_batch) self.spotlight_state.metadata["processed_aids_count"] = len(self.processed_aids) if not devices: log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] No devices returned from API") return log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Enriched {len(devices)} devices") devices = self._filter_asset_fields(devices) # 2. Send to XSIAM using existing generic function (fire-and-forget) send_task = create_task_send_batch_to_xsiam_and_save_context( data=devices, product=SPOTLIGHT_ASSETS_PRODUCT, snapshot_id=self.snapshot_id, items_count=final_items_count, batch_number=current_batch_number, last_saved_batch_number=self.asset_last_saved_batch_number, context_store=self.context_store, state=self.spotlight_state, save_state_callback=save_spotlight_state, data_type="assets", ) # Track task with callback to update last_saved_batch_number def update_last_saved(future): # 'self' is accessible from enclosing method scope - no nonlocal needed try: saved_batch_num = future.result() if saved_batch_num > self.asset_last_saved_batch_number: self.asset_last_saved_batch_number = saved_batch_num log_falcon_assets(f"AssetsDeviceHandler: Updated asset_last_saved_batch_number to {saved_batch_num}") except asyncio.CancelledError: log_falcon_assets( f"AssetsDeviceHandler: [Batch {current_batch_number}] Send task was cancelled (script exiting)." ) except Exception as e: log_falcon_assets(f"AssetsDeviceHandler: Enrichment task failed: {e}", "error") finally: self.running_tasks.discard(future) # Track the send task self.running_tasks.add(send_task) send_task.add_done_callback(update_last_saved) log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Created send task") except Exception as e: log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Error enriching assets: {e}", "error") raise async def flush_remaining(self, total_items_count: int) -> None: """ Flush remaining AIDs in buffer and wait for all enrichment tasks. This is the FINAL batch, so we send the actual total_items_count. Args: total_items_count: The final count of unique assets to report to XSIAM. """ # Handle leftover AIDs that didn't reach batch_limit if self.pending_buffer: log_falcon_assets( f"AssetsDeviceHandler: Flushing {len(self.pending_buffer)} remaining AIDs with final count {total_items_count}", "info", ) # Create task for remaining batch (fire-and-forget) task = asyncio.create_task( self.enrich_and_ingest_batch(list(self.pending_buffer), final_items_count=total_items_count) ) self.running_tasks.add(task) task.add_done_callback(self.running_tasks.discard) self.pending_buffer.clear() # Wait for all enrichment and send tasks to complete while self.running_tasks: log_falcon_assets("AssetsDeviceHandler: Starting flush of remaining assets.", "info") # Create a snapshot of the current tasks current_batch = list(self.running_tasks) if not current_batch: break count = len(current_batch) log_falcon_assets(f"AssetsDeviceHandler: Waiting for {count} background tasks to complete...", "info") # Wait for this specific batch. await asyncio.gather(*current_batch, return_exceptions=True) self.running_tasks.difference_update(current_batch) log_falcon_assets("AssetsDeviceHandler: All enrichment/send tasks completed successfully", "info") @staticmethod def _filter_asset_fields(assets: list[Dict]) -> list[Dict]: """ Filters a list of asset dictionaries to retain only specific keys. """ # Filtering assets key according to UVEM request allowed_keys = { "device_id", "cid", "external_ip", "mac_address", "hostname", "first_seen", "last_login_timestamp", "last_seen", "local_ip", "machine_domain", "os_version", "os_build", "serial_number", "status", "os_product_name", "connection_mac_address", "tags", } return [{k: asset.get(k, [] if k == "tags" else "") for k in allowed_keys} for asset in assets] async def xsiam_api_call_async( xsiam_url: str, zipped_data: bytes, headers: dict, num_of_attempts: int, data_type: str = "assets" ) -> aiohttp.ClientResponse | None: """ Send data to XSIAM asynchronously with retry logic. Generic function for sending any type of data to XSIAM. Args: xsiam_url: XSIAM API endpoint URL (e.g., "https://api-{domain}") zipped_data: Gzip-compressed data bytes to send headers: HTTP headers including authorization token, format, vendor, product, etc. num_of_attempts: Maximum number of retry attempts for failed requests data_type: Type of data being sent (e.g., "assets", "events"). Used for logging. Defaults to "assets" Returns: aiohttp.ClientResponse: The HTTP response object from the XSIAM API Raises: DemistoException: If all retry attempts fail or non-retryable error occurs """ status_code = None attempt_num = 1 response = None while status_code != 200 and attempt_num < num_of_attempts + 1: log_falcon_assets(f"Sending {data_type} to XSIAM, attempt {attempt_num}/{num_of_attempts}") ok_codes = (200, 429) if attempt_num < num_of_attempts else None async with aiohttp.ClientSession() as session: # noqa: SIM117 async with session.post(urljoin(xsiam_url, "/logs/v1/xsiam"), data=zipped_data, headers=headers) as response: try: response.raise_for_status() status_code = response.status except aiohttp.ClientResponseError as e: if ok_codes and e.status in ok_codes: status_code = e.status if e.status == 429: await asyncio.sleep(1) attempt_num += 1 continue else: header_msg = f"Error sending {data_type} to XSIAM: {e.message}" log_falcon_assets(header_msg, "error") demisto.updateModuleHealth(header_msg + e.message, is_error=True) log_falcon_assets(f"received status code: {status_code}") if status_code == 429: await asyncio.sleep(1) attempt_num += 1 return response def send_data_to_xsiam_async( data: Union[str, list], vendor: str, product: str, data_format: str = "json", url_key: str = "url", num_of_attempts: int = 3, chunk_size: int = XSIAM_EVENT_CHUNK_SIZE, data_type: str = "assets", snapshot_id: str = "", items_count: int = 1, ) -> list: """ Send data to XSIAM asynchronously by creating async tasks for each data chunk. Generic function for sending any type of data (assets, events, etc.) to XSIAM. Adapted from Rapid7_Nexpose.py lines 7631-7672. Args: data: List of data objects to send (e.g., vulnerabilities, alerts, events) vendor: Vendor name for XSIAM headers (e.g., "CrowdStrike") product: Product name for XSIAM headers data_format: Format of the data being sent. Defaults to "json" url_key: Parameter key to retrieve the final reporting device URL from params. Defaults to "url" num_of_attempts: Maximum retry attempts for failed requests. Defaults to 3 chunk_size: Maximum size in bytes for each data chunk. Defaults to 1 MiB (2**20) data_type: Type of data being sent for XSIAM collector-type header. Defaults to "assets" snapshot_id: Snapshot ID for asset collection tracking. Required for assets, empty for events items_count: Total items count - final count when complete, 1 when in-progress. Defaults to 1 Returns: list: List of asyncio.Task objects for each data chunk being sent Note: - Data is automatically converted to newline-separated JSON strings - Data is compressed with gzip before sending - Data is split into chunks based on chunk_size parameter - Each chunk is sent as a separate async task """ params = demisto.params() calling_context = demisto.callingContext.get("context", {}) instance_name = calling_context.get("IntegrationInstance", "") collector_name = calling_context.get("IntegrationBrand", "") # We only return early if data is empty AND it's NOT an asset snapshot update. # If it is assets, we might be sending the "Final Seal" (empty data + count header). if not data and data_type != "assets": log_falcon_assets(f"No {data_type} to send to XSIAM") return [] # Convert data to a newline-separated JSON string data_str = _normalize_data_to_str(data, data_type) if data_str is None: return [] # Get XSIAM credentials xsiam_api_token = demisto.getLicenseCustomField("Http_Connector.token") xsiam_domain = demisto.getLicenseCustomField("Http_Connector.url") xsiam_url = f"https://api-{xsiam_domain}" # Build headers headers = remove_empty_elements( { "authorization": xsiam_api_token, "format": data_format, "product": product, "vendor": vendor, "content-encoding": "gzip", "collector-name": collector_name, "instance-name": instance_name, "final-reporting-device": params.get(url_key, ""), "collector-type": "assets" if data_type == "assets" else "events", } ) # Adapt headers to asset data if data_type == "assets": if not snapshot_id: snapshot_id = str(round(time.time() * 1000)) headers["snapshot-id"] = snapshot_id + instance_name + product headers["total-items-count"] = str(items_count) # If data_str is empty (the seal), we force a list with one empty string [""] to ensure the task is created if not data_str and data_type == "assets": data_chunks = [""] log_falcon_assets("Preparing empty 'Seal' batch to close snapshot.") else: data_chunks = list(split_data_to_chunks(data_str, chunk_size)) # Free the intermediate JSON string — chunks now hold the only references del data_str # Compress chunks synchronously to free raw string data before creating async tasks. # This reduces memory fragmentation by allowing Python to reuse arenas for the next batch. compressed_chunks: list[tuple[bytes, int]] = [] total_raw_bytes = 0 total_compressed_bytes = 0 for chunk in data_chunks: chunk_size_val = len(chunk) if isinstance(chunk, list) else 1 chunk_str = "\n".join(chunk) if isinstance(chunk, list) else chunk raw_bytes = chunk_str.encode("utf-8") total_raw_bytes += len(raw_bytes) zipped_data = gzip.compress(raw_bytes) total_compressed_bytes += len(zipped_data) del raw_bytes # Free the encoded string immediately compressed_chunks.append((zipped_data, chunk_size_val)) # Free the uncompressed chunks — only compressed bytes remain del data_chunks if total_raw_bytes > 0: ratio = total_compressed_bytes / total_raw_bytes * 100 log_falcon_assets( f"Compressed {len(compressed_chunks)} chunks: " f"{total_raw_bytes / 1024:.1f} KB → {total_compressed_bytes / 1024:.1f} KB ({ratio:.1f}%)" ) async def send_compressed_async(zipped_data: bytes, chunk_size_val: int) -> int: await xsiam_api_call_async( xsiam_url=xsiam_url, zipped_data=zipped_data, headers=headers, num_of_attempts=num_of_attempts, data_type=data_type ) return chunk_size_val tasks = [asyncio.create_task(send_compressed_async(zipped, size)) for zipped, size in compressed_chunks] return tasks async def send_batch_to_xsiam_and_save_context( data: list, vendor: str, product: str, snapshot_id: str, items_count: int, batch_number: int, last_saved_batch_number: int, context_store: ContentClientContextStore, state: ContentClientState, save_state_callback: Callable[[ContentClientContextStore, ContentClientState], None], data_type: str = "assets", ) -> int: """ Send batch to XSIAM asynchronously, then save context ONLY if send succeeds AND this is the latest batch. Generic function implementing the async fire-and-forget pattern for sending any type of data to XSIAM while managing state persistence. Prevents out-of-order context saves by only saving when batch_number > last_saved_batch_number. Args: data: List of data objects to send (e.g., vulnerabilities, alerts, events) vendor: Vendor name for XSIAM headers (e.g., "CrowdStrike") product: Product name for XSIAM headers (e.g., "Falcon_Spotlight", "Falcon_CNAPP") snapshot_id: Snapshot ID for asset collection tracking items_count: Total items count - use final count when complete, 1 when in-progress batch_number: Current batch number being processed last_saved_batch_number: Highest batch number that has successfully saved context context_store: ContentClientContextStore instance for thread-safe context operations state: ContentClientState object containing cursor and metadata save_state_callback: Callback function to save state with signature: (ContentClientContextStore, dict, ContentClientState) -> None Example: save_spotlight_state, save_cnapp_state, etc. data_type: Type of data being sent for XSIAM collector-type header. Defaults to "assets" Returns: int: batch_number if context was saved, else last_saved_batch_number """ log_falcon_assets(f"[Batch {batch_number}] Sending {len(data)} {data_type} to XSIAM") try: # 1. Send to XSIAM (compresses data synchronously, returns async tasks for HTTP only) tasks = send_data_to_xsiam_async( data=data, vendor=vendor, product=product, data_format="json", url_key="url", num_of_attempts=3, chunk_size=XSIAM_EVENT_CHUNK_SIZE, data_type=data_type, snapshot_id=snapshot_id, items_count=items_count, ) # Release raw data — compression already done synchronously, async tasks hold only compressed bytes del data # 2. Wait for all chunks to complete await asyncio.gather(*tasks) log_falcon_assets(f"[Batch {batch_number}] for {product=} Successfully sent to XSIAM") # 3. Save context ONLY if this is the latest batch using the provided callback if batch_number > last_saved_batch_number: save_state_callback(context_store, state) log_falcon_assets(f"[Batch {batch_number}] Context saved") return batch_number else: log_falcon_assets( f"[Batch {batch_number}] for {product=} Skipped save (batch {last_saved_batch_number} already saved)" ) return last_saved_batch_number except Exception as e: log_falcon_assets(f"[Batch {batch_number}] Failed: {str(e)}", "error") raise def create_task_send_batch_to_xsiam_and_save_context( data, product, snapshot_id, items_count, batch_number, last_saved_batch_number, context_store, state, save_state_callback, data_type, ): """ Create an async task to send vulnerability batch to XSIAM and save context. Parameters now match the order and names of the internal async function. Args: data: List of data items to send product: The product name snapshot_id: Snapshot ID for tracking items_count: Total items count - use final count when complete, 1 when in-progress batch_number: Current batch number being processed last_saved_batch_number: Highest batch number that has successfully saved context context_store: ContentClientContextStore instance for thread-safe context operations state: ContentClientState object containing cursor and metadata save_state_callback: Callback function to save state with signature: (ContentClientContextStore, dict, ContentClientState) -> None Example: save_spotlight_state, save_cnapp_state, etc. data_type: Type of data being sent for XSIAM collector-type header. Defaults to "assets" Returns: asyncio.Task: The created async task """ task = asyncio.create_task( send_batch_to_xsiam_and_save_context( data=data, vendor=VENDOR, product=product, snapshot_id=snapshot_id, items_count=items_count, batch_number=batch_number, last_saved_batch_number=last_saved_batch_number, context_store=context_store, state=state, save_state_callback=save_state_callback, data_type=data_type, ) ) return task def create_spotlight_client(context_store: ContentClientContextStore) -> ContentClient: """ Create and configure ContentClient for Spotlight API with OAuth2 authentication. Args: context_store: Context store for token and state persistence Returns: Configured ContentClient instance """ return ContentClient( base_url=SERVER, verify=USE_SSL, proxy=PROXY, # OAuth2 authentication with token persistence auth_handler=OAuth2ClientCredentialsHandler( token_url=f"{SERVER}/oauth2/token", client_id=CLIENT_ID, client_secret=SECRET, context_store=context_store ), # diagnostic_mode retains a history of every request/response (full parsed response bodies), # which on large tenants grows memory linearly with the number of vulnerabilities and leads # to an out-of-memory failure. Keep it disabled so memory stays bounded. diagnostic_mode=False, client_name="FalconSpotlightAssetCollector", ) def extract_unique_aids(vulnerabilities: list, existing_unique_aids: set) -> None: """ Extract unique AIDs (Host IDs) from vulnerabilities and merge with existing set. Equivalent to JavaScript: const u_aid = [...new Set(aids)] Update the set of unique AIDs in place. Args: vulnerabilities: List of vulnerability objects existing_unique_aids: Existing set of unique AIDs """ # Extract AIDs from this batch batch_aids = {vuln.get("aid") for vuln in vulnerabilities if vuln.get("aid")} # Merge with existing existing_unique_aids.update(batch_aids) log_falcon_assets(f"Batch AIDs: {len(batch_aids)}, Total unique AIDs: {len(existing_unique_aids)}") def load_spotlight_state( context_store: ContentClientContextStore, ) -> tuple[ContentClientState, str, int, set, set, list[str], list[dict]]: """ Load Spotlight state from integration context. Args: context_store: Context store for reading integration context Returns: Tuple of (state_object, snapshot_id, total_fetched, unique_aids, processed_aids, completed_severities, withheld_records). """ # Read entire integration context (preserves all existing keys) integration_context = context_store.read() log_falcon_assets(f"Loaded integration context with keys: {list(integration_context.keys())}") # Get Spotlight-specific state spotlight_state_dict = integration_context.get("spotlight_assets", {}) spotlight_state = ContentClientState.from_dict(spotlight_state_dict) # Extract state variables snapshot_id = spotlight_state.metadata.get("snapshot_id") or str(round(time.time() * 1000)) total_fetched = spotlight_state.metadata.get("total_fetched_until_now", 0) # AIDs are no longer stored in context (only counts) to reduce memory/serialization overhead. # Backward compat: read old "unique_aids"/"processed_aids" lists if present, otherwise use counts. unique_aids_count = spotlight_state.metadata.get("unique_aids_count", len(spotlight_state.metadata.get("unique_aids", []))) processed_aids_count = spotlight_state.metadata.get( "processed_aids_count", len(spotlight_state.metadata.get("processed_aids", [])) ) completed_severities = spotlight_state.metadata.get("completed_severities", []) # Records withheld for the seal in previous cycles, persisted across resume cycles. withheld_records = spotlight_state.metadata.get("withheld_records", []) log_falcon_assets( f"Loaded Spotlight state: {snapshot_id=}, {total_fetched=}, " f"{unique_aids_count=}, {processed_aids_count=}, " f"completed_severities={completed_severities}, " f"withheld_records_count={len(withheld_records)}, " f"after_token={spotlight_state.cursor}" ) return ( spotlight_state, snapshot_id, total_fetched, unique_aids_count, processed_aids_count, completed_severities, withheld_records, ) def update_spotlight_state_and_metadata( spotlight_state: ContentClientState, cursor: str | None, snapshot_id: str, total_fetched: int, unique_aids: set, processed_aids: set, completed_severities: list[str] | None = None, withheld_records: list[dict] | None = None, ) -> None: """ Update Spotlight state with cursor and metadata. Centralizes the repetitive state update logic. Args: spotlight_state: State object to update cursor: Pagination cursor/token snapshot_id: Snapshot ID for tracking total_fetched: Total vulnerabilities fetched unique_aids: Set of unique AIDs processed_aids: Set of processed AIDs completed_severities: List of severities that have completed successfully (optional) withheld_records: Records withheld for the final sealing batch, persisted across resume cycles. At most one record per severity (optional) """ spotlight_state.cursor = cursor # Preserve existing completed_severities if not explicitly provided if completed_severities is None and isinstance(spotlight_state.metadata, dict): completed_severities = spotlight_state.metadata.get("completed_severities", []) elif completed_severities is None: completed_severities = [] # Preserve existing withheld_records if not explicitly provided if withheld_records is None and isinstance(spotlight_state.metadata, dict): withheld_records = spotlight_state.metadata.get("withheld_records", []) elif withheld_records is None: withheld_records = [] spotlight_state.metadata = { "snapshot_id": snapshot_id, "total_fetched_until_now": total_fetched, "unique_aids_count": len(unique_aids), "processed_aids_count": len(processed_aids), "completed_severities": completed_severities, "withheld_records": withheld_records, } async def fetch_spotlight_vulnerabilities_page( client: ContentClient, after_token: str | None, filter_query: str ) -> tuple[list, dict]: """ Fetch a single page of Spotlight vulnerabilities with custom filter. Args: client: ContentClient instance after_token: Pagination token (None for first request) filter_query: FQL filter query (e.g., "status:['open','reopen']" or "status:['open','reopen']+cve.severity:['CRITICAL']") Returns: Tuple of (vulnerabilities_list, response_data) """ # Build request parameters params = {"limit": MAX_FETCH_SPOTLIGHT_ASSETS, "filter": filter_query, "facet": ["host_info", "cve"]} # Add pagination token if provided if after_token: params["after"] = after_token log_falcon_assets( f"Fetching Spotlight page with limit={MAX_FETCH_SPOTLIGHT_ASSETS}, after_token={'present' if after_token else 'none'}" ) # Make ASYNC API request response = await client._request(method="GET", url_suffix="/spotlight/combined/vulnerabilities/v1", params=params) # Parse JSON response response_data = response.json() vulnerabilities = response_data.get("resources", []) log_falcon_assets(f"Fetched {len(vulnerabilities)} vulnerabilities in this page") return vulnerabilities, response_data async def wait_for_background_tasks(pending_tasks: set[asyncio.Task], task_description: str = "background") -> None: """Wait for all pending async tasks to complete and raise on first failure. Args: pending_tasks: Set of asyncio.Task objects to await. task_description: Human-readable label for log messages (e.g. "vulnerability send"). Raises: Exception: Re-raises the first exception encountered from a failed task. """ if not pending_tasks: return log_falcon_assets(f"Waiting for {len(pending_tasks)} {task_description} tasks to complete", "info") results = await asyncio.gather(*pending_tasks, return_exceptions=True) for res in results: if isinstance(res, Exception): log_falcon_assets(f"Background {task_description} task failed: {res}", "error") raise res log_falcon_assets(f"All {task_description} tasks completed successfully", "info") async def fetch_vulnerabilities_by_severity( client: ContentClient, severity: str, context_store: ContentClientContextStore, spotlight_state: ContentClientState, snapshot_id: str, asset_handler: AssetsDeviceHandler, ) -> tuple[int, set, set[asyncio.Task], list[dict]]: """Fetch all vulnerabilities for a single severity level with pagination. This function handles continuous pagination for one severity, avoiding cursor expiration by fetching all pages sequentially without delays. Args: client: ContentClient instance for API calls severity: Severity level to filter (CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN) context_store: Context store for state persistence spotlight_state: Current Spotlight state object snapshot_id: Snapshot ID for asset collection tracking asset_handler: AssetsDeviceHandler for AID enrichment Returns: Tuple of (total_vulnerabilities_fetched, unique_aids, pending_tasks, withheld_records) """ log_falcon_assets(f"[{severity}] Starting vulnerability fetch for severity: {severity}", "info") total_fetched = 0 unique_aids: set = set() pending_tasks: set[asyncio.Task] = set() after_token: str | None = None batch_counter = 0 last_saved_batch_number = 0 # The first fetched record is withheld from the data batches to be sent in the seal. withheld_records: list[dict] = [] try: while True: # BACKPRESSURE: before fetching next page, wait if too many send tasks are pending. # This prevents unbounded memory growth from fire-and-forget vulnerability batches. while len(pending_tasks) >= MAX_PENDING_TASKS_PER_SEVERITY: log_falcon_assets( f"[{severity}] Backpressure: {len(pending_tasks)} pending tasks >= limit {MAX_PENDING_TASKS_PER_SEVERITY}, " f"waiting for at least one to complete (RSS: {_get_process_memory_mb()})" ) done, pending_tasks_updated = await asyncio.wait(pending_tasks, return_when=asyncio.FIRST_COMPLETED) pending_tasks = pending_tasks_updated # Process completed tasks to update last_saved_batch_number for completed_task in done: try: result = completed_task.result() if isinstance(result, int) and result > last_saved_batch_number: last_saved_batch_number = result except Exception as e: log_falcon_assets(f"[{severity}] Background send task failed: {e}", "error") log_falcon_assets( f"[{severity}] Backpressure released: {len(done)} tasks completed, " f"{len(pending_tasks)} still pending" ) # Build filter query with severity and a lookback window. # Only fetch vulnerabilities updated within the last SPOTLIGHT_LOOKBACK_DAYS days to bound # the dataset size for very large tenants. Uses FQL relative time syntax. filter_query = ( f"status:['open','reopen']+cve.severity:['{severity}']" f"+updated_timestamp:>'now-{SPOTLIGHT_LOOKBACK_DAYS}d'" ) log_falcon_assets( f"[{severity}] Fetching batch {batch_counter + 1} with limit={MAX_FETCH_SPOTLIGHT_ASSETS}, " f"after_token={'present' if after_token else 'none'}" ) vulnerabilities, response_data = await fetch_spotlight_vulnerabilities_page( client=client, after_token=after_token, filter_query=filter_query ) log_falcon_assets(f"[{severity}] Fetched {len(vulnerabilities)} vulnerabilities in batch {batch_counter + 1}") # Extract unique AIDs from this batch (covers the withheld record too). extract_unique_aids(vulnerabilities, unique_aids) # Send AIDs to asset handler for enrichment (async fire-and-forget) batch_aids = {vuln.get("aid") for vuln in vulnerabilities if vuln.get("aid")} await asset_handler.receive_new_aids(batch_aids) # Count every fetched record, including the withheld one, so the count stays exact. total_fetched += len(vulnerabilities) batch_counter += 1 # Withhold the first record of this severity from the data batches; it is sent later # in the sealing batch. It is already counted and AID-enriched above, so it is still # sent exactly once. records_to_send = vulnerabilities if not withheld_records and vulnerabilities: withheld_records.append(vulnerabilities[0]) records_to_send = vulnerabilities[1:] log_falcon_assets( f"[{severity}] Withholding first record for the sealing batch " f"(id={vulnerabilities[0].get('id')}); sending {len(records_to_send)} records in this batch.", "info", ) # Get next pagination token new_after_token = response_data.get("meta", {}).get("pagination", {}).get("after") # Determine if this is the last batch for this severity is_last_batch = not new_after_token # For severity-based fetching, we use items_count=1 for all batches # The final sealing happens in the orchestrator after all severities complete items_count = 1 # Create task to send batch to XSIAM (without the withheld first record) task = create_task_send_batch_to_xsiam_and_save_context( data=records_to_send, product=SPOTLIGHT_VULN_PRODUCT, snapshot_id=snapshot_id, items_count=items_count, batch_number=batch_counter, last_saved_batch_number=last_saved_batch_number, context_store=context_store, state=spotlight_state, save_state_callback=save_spotlight_state, data_type="assets", ) # Track task and update last_saved_batch_number when task completes def update_last_saved(future, _pending=pending_tasks): nonlocal last_saved_batch_number try: last_saved_batch_number = future.result() except Exception as e: log_falcon_assets(f"[{severity}] Background vulnerability task failed: {e}", "error") finally: _pending.discard(future) pending_tasks.add(task) task.add_done_callback(update_last_saved) log_falcon_assets( f"[{severity}] Created send task for batch {batch_counter} " f"(pending: {len(pending_tasks)}/{MAX_PENDING_TASKS_PER_SEVERITY})" ) # Log memory stats every 10 batches if batch_counter % 10 == 0: log_falcon_assets( f"[{severity}] Memory checkpoint: batch={batch_counter}, total_fetched={total_fetched}, " f"unique_aids={len(unique_aids)}, pending_tasks={len(pending_tasks)}, " f"RSS: {_get_process_memory_mb()}", "info", ) # Check if more pages exist if is_last_batch: log_falcon_assets( f"[{severity}] Completed fetching vulnerabilities. Total: {total_fetched}, Unique hosts: {len(unique_aids)}, " f"pending_tasks: {len(pending_tasks)}, RSS: {_get_process_memory_mb()}", "info", ) break # More pages exist - continue to next batch log_falcon_assets(f"[{severity}] More pages available. Fetched so far: {total_fetched}") after_token = new_after_token except ContentClientError as e: # Check if this is an authentication error (HTTP 401) # Authentication errors are not transient and should fail immediately if e.response and e.response.status_code == 401: error_msg = ( f"Authentication failed (HTTP 401) while fetching {severity} severity vulnerabilities. " f"Invalid or expired credentials. Please verify the API credentials and try again. " f"Error: {e}" ) log_falcon_assets(f"[{severity}] {error_msg}", "error") raise ContentClientError(error_msg) from e # Check for "Unauthorized" in error message as fallback error_str = str(e) if "Unauthorized" in error_str or "401" in error_str: error_msg = ( f"Authentication failed while fetching {severity} severity vulnerabilities. " f"Invalid or expired credentials. Please verify the API credentials and try again. " f"Error: {e}" ) log_falcon_assets(f"[{severity}] {error_msg}", "error") raise ContentClientError(error_msg) from e # Check if this is an expired cursor error if "Search context expired" in error_str or ('"code": 404' in error_str and "after" in error_str): log_falcon_assets( f"[{severity}] Pagination cursor expired. This should not happen with continuous fetching. " f"Progress ({total_fetched} vulnerabilities) will be lost.", "error", ) log_falcon_assets(f"[{severity}] Error during fetch: {e}", "error") raise except Exception as e: log_falcon_assets(f"[{severity}] Unexpected error during fetch: {e}", "error") raise return total_fetched, unique_aids, pending_tasks, withheld_records async def await_and_aggregate_severity_results( severity_tasks: list[tuple[str, asyncio.Task]], current_completed_severities: list[str], context_store: ContentClientContextStore, spotlight_state: ContentClientState, snapshot_id: str, prior_withheld_records: list[dict] | None = None, ) -> tuple[int, set, set[asyncio.Task], list[str], list[dict]]: """Wait for all severity tasks and aggregate their results. Args: severity_tasks: List of (severity, task) tuples to await current_completed_severities: List of severities already completed context_store: Context store for state persistence spotlight_state: Current Spotlight state object snapshot_id: Snapshot ID for asset collection tracking prior_withheld_records: Records withheld by severities completed in previous cycles. New per-severity withheld records are appended so the seal covers all severities. Returns: Tuple of (total_vulnerabilities, all_unique_aids, all_pending_tasks, updated_completed_severities, withheld_records). ``withheld_records`` holds the records withheld across all completed severities (this cycle + prior cycles). """ total_vulnerabilities = 0 all_unique_aids: set = set() all_pending_tasks: set[asyncio.Task] = set() # Seed with records withheld in previous cycles so the seal isn't missing earlier severities. all_withheld_records: list[dict] = list(prior_withheld_records or []) for severity, task in severity_tasks: try: log_falcon_assets(f"Waiting for {severity} severity task to complete...", "info") severity_total, severity_aids, severity_tasks_result, severity_withheld = await task total_vulnerabilities += severity_total all_unique_aids.update(severity_aids) all_pending_tasks.update(severity_tasks_result) all_withheld_records.extend(severity_withheld) log_falcon_assets( f"[{severity}] Completed: {severity_total} vulnerabilities, {len(severity_aids)} unique hosts", "info" ) # Mark this severity as completed if severity not in current_completed_severities: current_completed_severities.append(severity) log_falcon_assets(f"[{severity}] Marked as completed. Total completed: {current_completed_severities}", "info") # Persist completed severities and the accumulated withheld records after each # severity completes, so a resumed run does not lose earlier severities' records. update_spotlight_state_and_metadata( spotlight_state=spotlight_state, cursor=None, # No cursor needed for severity-based fetching snapshot_id=snapshot_id, total_fetched=0, # Reset for next cycle unique_aids=set(), # Reset for next cycle processed_aids=set(), # Reset for next cycle completed_severities=current_completed_severities, withheld_records=all_withheld_records, ) save_spotlight_state(context_store, spotlight_state) log_falcon_assets( f"[{severity}] Saved completion state to context (withheld_records so far: {len(all_withheld_records)})", "info", ) except Exception as e: log_falcon_assets(f"[{severity}] Failed with error: {e}", "error") # Don't mark as completed if it failed - will retry next cycle continue log_falcon_assets( f"All severity queries completed. Total vulnerabilities: {total_vulnerabilities}, " f"Total unique hosts: {len(all_unique_aids)}", "info", ) return total_vulnerabilities, all_unique_aids, all_pending_tasks, current_completed_severities, all_withheld_records async def finalize_severity_fetch( all_pending_tasks: set[asyncio.Task], current_completed_severities: list[str], total_vulnerabilities: int, all_unique_aids: set, asset_handler: AssetsDeviceHandler, context_store: ContentClientContextStore, spotlight_state: ContentClientState, snapshot_id: str, withheld_records: list[dict] | None = None, ) -> None: """Finalize the severity fetch by waiting for background tasks and sealing snapshot if complete. Args: all_pending_tasks: Set of background tasks to wait for current_completed_severities: List of severities completed in this cycle total_vulnerabilities: Total number of vulnerabilities fetched all_unique_aids: Set of all unique asset IDs asset_handler: Asset handler for enrichment context_store: Context store for state persistence spotlight_state: Current Spotlight state object snapshot_id: Snapshot ID for asset collection tracking withheld_records: Records withheld during fetching to send as the sealing batch. Each record is sent exactly once (only here), so the count stays exact. """ withheld_records = withheld_records or [] # Wait for all background vulnerability send tasks to complete log_falcon_assets(f"Waiting for {len(all_pending_tasks)} background vulnerability send tasks...", "info") await wait_for_background_tasks(all_pending_tasks, "vulnerability send") # Check if ALL severities have completed (including previously completed ones) all_severities_completed = set(current_completed_severities) == set(SPOTLIGHT_SEVERITIES) if all_severities_completed: if not withheld_records: # Grand total is zero: there is no real record to seal with. Emitting an empty # request would not create a BQ row anyway (the original bug), so skip sealing. # This is a legitimately empty snapshot. log_falcon_assets("All severities completed but no records were fetched. Skipping seal (empty snapshot).", "info") else: # Send the final sealing batch with the withheld records and the actual total count. log_falcon_assets( f"All severities completed successfully. Sending final sealing batch for " f"snapshot_id={snapshot_id} with {len(withheld_records)} withheld record(s) and " f"total-items-count={total_vulnerabilities}", "info", ) final_task = create_task_send_batch_to_xsiam_and_save_context( data=withheld_records, # Real data rows so the count lands in BigQuery product=SPOTLIGHT_VULN_PRODUCT, snapshot_id=snapshot_id, items_count=total_vulnerabilities, # Final total count batch_number=999999, # High number to ensure it's processed last last_saved_batch_number=0, context_store=context_store, state=spotlight_state, save_state_callback=save_spotlight_state, data_type="assets", ) await final_task log_falcon_assets( f"Final sealing batch sent successfully for snapshot_id={snapshot_id} " f"(total-items-count={total_vulnerabilities})", "info", ) # Flush remaining AIDs and wait for all asset enrichment tasks total_assets_count = len(all_unique_aids) log_falcon_assets( f"Flushing remaining AIDs and waiting for asset enrichment tasks. Total assets: {total_assets_count}", "info" ) await asset_handler.flush_remaining(total_items_count=total_assets_count) log_falcon_assets( f"Parallel severity fetch completed. Total vulnerabilities: {total_vulnerabilities}, " f"Total unique hosts: {len(all_unique_aids)}, Enriched assets: {len(asset_handler.processed_aids)}", "info", ) # State will be cleared by fetch_spotlight_assets() after this function returns log_falcon_assets("All severities completed successfully.", "info") else: log_falcon_assets( f"Not all severities completed yet. Snapshot NOT sealed. Completed: {current_completed_severities}, " f"Remaining: {[s for s in SPOTLIGHT_SEVERITIES if s not in current_completed_severities]}", "warning", ) log_falcon_assets( f"Partial fetch completed. Total vulnerabilities in this cycle: {total_vulnerabilities}, " f"Total unique hosts: {len(all_unique_aids)}. Will retry incomplete severities in next fetch.", "info", ) async def fetch_spotlight_by_severity_parallel( client: ContentClient, context_store: ContentClientContextStore, spotlight_state: ContentClientState, snapshot_id: str, completed_severities: list[str], prior_withheld_records: list[dict] | None = None, ) -> tuple[int, set]: """Orchestrate parallel vulnerability fetching across all severity levels. Runs 6 parallel queries (one per severity) to avoid cursor expiration issues. Each severity query maintains its own cursor and fetches continuously. Skips severities that have already completed in previous fetch cycles. Args: client: ContentClient instance for API calls context_store: Context store for state persistence spotlight_state: Current Spotlight state object snapshot_id: Snapshot ID for asset collection tracking completed_severities: List of severities already completed in previous cycles prior_withheld_records: Records withheld for the seal by severities completed in previous cycles, carried forward so the seal includes them. Returns: Tuple of (total_vulnerabilities, unique_aids) """ log_falcon_assets("Starting parallel vulnerability fetch by severity", "info") log_falcon_assets(f"All severities: {SPOTLIGHT_SEVERITIES}", "info") log_falcon_assets(f"Previously completed severities: {completed_severities}", "info") # Filter out already completed severities severities_to_fetch = [s for s in SPOTLIGHT_SEVERITIES if s not in completed_severities] if not severities_to_fetch: log_falcon_assets("All severities already completed. Nothing to fetch.", "info") return 0, set() log_falcon_assets(f"Severities to fetch in this cycle: {severities_to_fetch}", "info") # Track completed severities in this cycle (start with previously completed) current_completed_severities = completed_severities.copy() # Create asset handler for enrichment asset_handler = AssetsDeviceHandler( client=client, context_store=context_store, spotlight_state=spotlight_state, snapshot_id=snapshot_id, processed_aids=set(), # Start fresh for this fetch batch_limit=MAX_FETCH_SPOTLIGHT_ASSETS, ) # Create parallel tasks for each severity that needs fetching severity_tasks = [] for severity in severities_to_fetch: task = asyncio.create_task( fetch_vulnerabilities_by_severity( client=client, severity=severity, context_store=context_store, spotlight_state=spotlight_state, snapshot_id=snapshot_id, asset_handler=asset_handler, ) ) severity_tasks.append((severity, task)) log_falcon_assets(f"Created {len(severity_tasks)} parallel severity fetch tasks", "info") # Wait for all severity tasks and aggregate results ( total_vulnerabilities, all_unique_aids, all_pending_tasks, current_completed_severities, withheld_records, ) = await await_and_aggregate_severity_results( severity_tasks=severity_tasks, current_completed_severities=current_completed_severities, context_store=context_store, spotlight_state=spotlight_state, snapshot_id=snapshot_id, prior_withheld_records=prior_withheld_records, ) await finalize_severity_fetch( all_pending_tasks=all_pending_tasks, current_completed_severities=current_completed_severities, total_vulnerabilities=total_vulnerabilities, all_unique_aids=all_unique_aids, asset_handler=asset_handler, context_store=context_store, spotlight_state=spotlight_state, snapshot_id=snapshot_id, withheld_records=withheld_records, ) return total_vulnerabilities, all_unique_aids async def fetch_spotlight_assets(): """Fetch Spotlight vulnerabilities using severity-based parallel approach. IMPLEMENTATION (Severity-Based Parallel): 1. Split vulnerability fetching by severity: CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN 2. Run 6 parallel queries, each with independent cursor 3. Each query fetches continuously (no cursor expiration within query) 4. Aggregate results from all severities 5. Extract unique AIDs and enrich assets 6. Send vulnerabilities and assets to XSIAM with proper snapshot sealing This approach solves the pagination cursor TTL issue for customers with 6M+ vulnerabilities by parallelizing across severity levels. Largest query (LOW, ~2.4M vulns) completes in ~136 minutes. Total time = max(all queries) = ~2.3 hours. No cursor expiration, no duplication. """ log_falcon_assets("Starting Spotlight assets fetch execution (severity-based parallel approach).", "info") fetch_start_time = time.monotonic() # Start tracemalloc to track Python allocations vs OS RSS (quantifies arena fragmentation) import tracemalloc if not tracemalloc.is_tracing(): tracemalloc.start() log_falcon_assets("tracemalloc started for memory diagnostics") context_store = ContentClientContextStore(namespace="SpotlightAssets") ( spotlight_state, snapshot_id, _total_fetched, _unique_aids, _processed_aids, completed_severities, prior_withheld_records, ) = load_spotlight_state(context_store) # Note: total_fetched, unique_aids, processed_aids not used in severity-based approach # Each severity starts fresh. Only completed_severities and prior_withheld_records are used. client = create_spotlight_client(context_store) try: # Fetch vulnerabilities in parallel by severity total_vulnerabilities, all_unique_aids = await fetch_spotlight_by_severity_parallel( client=client, context_store=context_store, spotlight_state=spotlight_state, snapshot_id=snapshot_id, completed_severities=completed_severities, prior_withheld_records=prior_withheld_records, ) # Reset state after successful fetch (completed_severities already cleared in parallel function if all done). # Also clear the persisted withheld_records so they do not leak into the next snapshot. log_falcon_assets("Resetting Spotlight state after successful complete fetch") update_spotlight_state_and_metadata( spotlight_state=spotlight_state, cursor=None, snapshot_id="", total_fetched=0, unique_aids=set(), processed_aids=set(), completed_severities=[], # Ensure it's cleared withheld_records=[], # Clear persisted seal records for the next snapshot ) save_spotlight_state(context_store, spotlight_state) fetch_elapsed = time.monotonic() - fetch_start_time fetch_minutes = fetch_elapsed / 60 log_falcon_assets( f"Finished Spotlight assets fetch in {fetch_minutes:.1f} minutes ({fetch_elapsed:.0f}s). " f"Total vulnerabilities: {total_vulnerabilities}, " f"Total unique hosts: {len(all_unique_aids)}, " f"RSS: {_get_process_memory_mb()}", "info", ) except (ContentClientError, Exception) as e: log_falcon_assets(f"Error during Spotlight fetch: {e}", "error") if isinstance(e, ContentClientError): diagnosis = client.diagnose_error(e) log_falcon_assets(f"Issue: {diagnosis['issue']}, Solution: {diagnosis['solution']}", "error") raise finally: await client.aclose() def fetch_cnapp_assets(): log_falcon_assets("Starting fetch assets execution.", "info", asset="CNAPP Alerts") new_last_run, detections, items_count, snapshot_id = get_cnapp_assets() log_falcon_assets( f"Sending a batch of {len(detections)} assets to xsiam with {snapshot_id=}", log_type="debug", asset="CNAPP Alerts" ) send_data_to_xsiam( data=detections, vendor=VENDOR, product=CNAPP_PRODUCT, data_type="assets", snapshot_id=snapshot_id, items_count=items_count, should_update_health_module=False, ) log_falcon_assets("Finished sending a batch of assets.", log_type="debug", asset="CNAPP Alerts") log_falcon_assets(f"Preparing to save assets last run with {new_last_run=}.", log_type="debug", asset="CNAPP Alerts") demisto.setAssetsLastRun(new_last_run) log_falcon_assets("Assets last run was saved succesfuly.", log_type="debug", asset="CNAPP Alerts") demisto.updateModuleHealth({"assetsPulled": len(detections)}) log_falcon_assets("Finished fetch assets exeuction.", log_type="info", asset="CNAPP Alerts") def fetch_assets_command(): log_falcon_assets("Starting fetch assets execution.", "info", asset="") params = demisto.params() fetch_assets_types = params.get("fetch_assets_type", "") if "CNAPP Alerts" in fetch_assets_types: fetch_cnapp_assets() if "Spotlight" in fetch_assets_types: asyncio.run(fetch_spotlight_assets()) def fetch_detections_by_product_type( current_fetch_info: dict, look_back: int, product_type: str, fetch_query: str, detections_type: str, detection_name_prefix: str, start_time_key: str, is_fetch_events: bool = False, ) -> tuple[List, dict]: """The fetch logic for idp, ods and mobile detections. Args: current_fetch_info (dict): The last run object. look_back (int): The number of minutes to lookback. product_type (str): The product_type, used for debug & query. fetch_query (str): The user's query param. detections_type (str): The detection type, used for debugging and context save. detection_name_prefix (str): The name prefix for the fetched incidents. start_time_key (str): The key to save as the incident occurred time. Returns: tuple[List, dict]: The list of the fetched incidents and the updated last object. """ detections: List = [] # The configured per-run limit (10000 for XSIAM, "Max incidents per fetch" for XSOAR). base_fetch_limit = MAX_FETCH_DETECTION_PER_API_CALL if is_fetch_events else INCIDENTS_PER_FETCH offset: int = current_fetch_info.get("offset") or 0 start_fetch_time, end_fetch_time = get_fetch_run_time_range( last_run=current_fetch_info, first_fetch=FETCH_TIME, look_back=look_back, date_format=DETECTION_DATE_FORMAT ) fetch_limit = current_fetch_info.get("limit") or base_fetch_limit # Build the base product/type filter clauses. # Most product types (e.g. idp, mobile, ngsiem, xdr, automated-lead, thirdparty) map to a single # `product:'<value>'` clause. ON_DEMAND ('ods') and OFP ('ofp') need `type:'<value>'` instead. # IOA needs a compound `product:'fcs'+type:'cloud-ioa'` selector. product_type_to_clauses: dict[str, tuple[str | None, str | None]] = { IncidentType.ON_DEMAND.value: (None, IncidentType.ON_DEMAND.value), IncidentType.OFP.value: (None, IncidentType.OFP.value), IncidentType.IOA_TYPE_TAG.value: ("fcs", IncidentType.IOA_TYPE_TAG.value), } product_clause_value, type_clause_value = product_type_to_clauses.get(product_type, (product_type, None)) filter_clauses: list[str] = [] if product_clause_value: filter_clauses.append(f"product:'{product_clause_value}'") if type_clause_value: filter_clauses.append(f"type:'{type_clause_value}'") filter_clauses.append(f"created_timestamp:>'{start_fetch_time}'") filter = "+".join(filter_clauses) if fetch_query: filter = f"({filter})+({fetch_query})" # The API rejects requests where offset + limit exceeds MAX_FETCH_SIZE. With look_back, fetch_limit can grow # past that bound, so cap the value sent to the API while keeping fetch_limit for dedup and last_run bookkeeping. api_limit = min(fetch_limit, MAX_FETCH_SIZE - offset) response = get_detections_ids(filter_arg=filter, limit=api_limit, offset=offset, product_type=product_type) detections_ids: list[dict] = demisto.get(response, "resources", []) demisto.debug(f"CrowdStrikeFalconMsg: Total fetched detections: {len(detections_ids)}") total_detections = demisto.get(response, "meta.pagination.total") offset = calculate_new_offset(offset, len(detections_ids), total_detections) if offset: if offset + fetch_limit > MAX_FETCH_SIZE: demisto.debug( f"CrowdStrikeFalconMsg: The new offset: {offset} + limit: {fetch_limit} reached " f"{MAX_FETCH_SIZE}, resetting the offset to 0" ) offset = 0 demisto.debug(f"CrowdStrikeFalconMsg: The new {detections_type} offset is {offset}") if detections_ids: raw_res = get_detection_entities(detections_ids) if "resources" in raw_res: full_detections = demisto.get(raw_res, "resources") for detection in full_detections: detection["incident_type"] = detections_type detection_to_context = detection_to_incident_context( detection, detection_name_prefix, start_time_key, is_fetch_events=is_fetch_events ) detections.append(detection_to_context) detections = ( truncate_long_time_str(detections, "occurred") if product_type in {IncidentType.ON_DEMAND.value, IncidentType.OFP.value, IncidentType.NGSIEM_DETECTION, IncidentType.THIRD_PARTY} else detections ) detections = filter_incidents_by_duplicates_and_limit( incidents_res=detections, last_run=current_fetch_info, fetch_limit=fetch_limit, id_field="name" ) demisto.debug(f"CrowdstrikeFalconMsg: last_run before update: {current_fetch_info}") current_fetch_info = update_last_run_object( last_run=current_fetch_info, incidents=detections, fetch_limit=base_fetch_limit, start_fetch_time=start_fetch_time, end_fetch_time=end_fetch_time, look_back=look_back, created_time_field="occurred", id_field="name", date_format=DETECTION_DATE_FORMAT, new_offset=offset, ) demisto.debug(f"CrowdstrikeFalconMsg: last_run after update: {current_fetch_info}") demisto.debug(f"CrowdstrikeFalconMsg: Ending fetch {detections_type}. Fetched {len(detections)}") return detections, current_fetch_info def fetch_ngsiem_cases(last_run: dict, look_back: int, fetch_query: str): """ Fetches NGSIEM cases from CrowdStrikeFalcon :param last_run: The last run object :param look_back: The look back time in minutes :param fetch_query: The fetch query :return: A tuple containing a list of cases and the updated last run object """ cases = [] offset = last_run.get("offset", 0) fetch_limit = last_run.get("limit", INCIDENTS_PER_FETCH) start_fetch_time, end_fetch_time = get_fetch_run_time_range( last_run=last_run, first_fetch=FETCH_TIME, look_back=look_back, date_format=DETECTION_DATE_FORMAT ) # build query and fetch cases data filter = f"created_timestamp:>'{start_fetch_time}'" if fetch_query: filter += f"+{fetch_query}" demisto.debug(f"CrowdStrikeFalconMsg: fetching NGSIEM case ids with: {filter=}, {fetch_limit=}, {offset=}") total_cases, ids = get_cases_data(filter, fetch_limit, offset) demisto.debug(f"CrowdStrikeFalconMsg: fetched a total of {len(ids)} NGSIEM case ids") # calculate new offset offset = calculate_new_offset(offset, len(ids), total_cases) if offset and offset + fetch_limit > MAX_FETCH_SIZE: demisto.debug( f"CrowdStrikeFalconMsg: The new offset: {offset} + limit: {fetch_limit} reached " f"{MAX_FETCH_SIZE}, resetting the offset to 0" ) offset = 0 demisto.debug(f"CrowdStrikeFalconMsg: The new ngsiem cases offset is {offset}") # fetch cases details if ids exist if ids: cases_details = get_cases_details(ids) # add incident type and append to list demisto.debug(f"CrowdStrikeFalconMsg: fetched cases details: {json.dumps(cases_details)=}") for case in cases_details: add_mirroring_fields(case) case["incident_type"] = NGSIEM_CASE fix_time_field(case, "created_timestamp") case_context = { "name": f"{NGSIEM_CASE} ID: {case.get('id')}", "occurred": case.get("created_timestamp"), "severity": case.get("severity"), "rawJSON": json.dumps(case), } cases.append(case_context) demisto.debug(f"cases before filter: {cases=}") cases = filter_incidents_by_duplicates_and_limit( incidents_res=cases, last_run=last_run, fetch_limit=fetch_limit, id_field="name" ) demisto.debug(f"CrowdstrikeFalconMsg: cases last_run before update: {last_run}") last_run = update_last_run_object( last_run=last_run, incidents=cases, fetch_limit=fetch_limit, start_fetch_time=start_fetch_time, end_fetch_time=end_fetch_time, look_back=look_back, created_time_field="occurred", id_field="name", date_format=DETECTION_DATE_FORMAT, new_offset=offset, ) demisto.debug(f"CrowdstrikeFalconMsg: cases last_run after update: {last_run}") demisto.debug(f"CrowdstrikeFalconMsg: Ending NGSIEM Cases fetch. Fetched {len(cases)}") demisto.debug(f"CrowdstrikeFalconMsg: Ending NGSIEM Cases fetch. {cases=}") return cases, last_run def parse_ioa_iom_incidents( fetched_data: list[dict[str, Any]], last_date: str, last_fetched_ids: list[str], date_key: str, id_key: str, date_format: str, is_paginating: bool, to_incident_context: Callable[[dict[str, Any], str], dict[str, Any]], incident_type: str, ) -> tuple[list[dict[str, Any]], list[str], str]: """This function is in charge of parsing IOA, and IOM data from their respective API, to create incidents from them. Args: fetched_data (list[dict[str, Any]]): The fetched data. last_date (str): The last date saved in the last run object. last_fetched_ids (list[str]): The last fetched IDs. date_key (str): The key of the value that holds the date in the API. id_key (str): The key of the value that holds the ID in the API. date_format (str): The date format. is_paginating (bool): Whether we are doing pagination or not. When false, the previously fetched IDs will NOT be considered for duplicates removal. new_next_token (str | None): The next token that will be used in the next run. next_token (str | None): The next token that was used in the current round. to_incident_context (Callable[[dict[str, Any], str], dict[str, Any]]): The function that is used to convert data from the API to an incident. incident_type (str): The incident type. Returns: tuple[list[dict[str, Any]], list[str], str]: The fetched incidents, the fetched ids, the largest date found withing the fetched incidents. """ incidents: list[dict[str, Any]] = [] fetched_ids: list[str] = [] # Hold the date_time_since of all fetched incidents, to acquire the largest date fetched_dates: list[datetime] = [safe_strptime(last_date, date_format)] for data in fetched_data: data_id = data.get(id_key, "") if data_id not in last_fetched_ids: demisto.debug(f"Creating an incident for CrowdStrike CSPM ID: {data_id}") fetched_ids.append(data_id) incident_context = to_incident_context(data, incident_type) incidents.append(incident_context) event_created = reformat_timestamp(demisto.get(data, date_key, ""), date_format) # type: ignore fetched_dates.append(safe_strptime(event_created, date_format)) else: demisto.debug(f"Ignoring CSPM incident with {data_id=} - was already fetched in the previous run") new_last_date = max(fetched_dates).strftime(date_format) if is_paginating: demisto.debug(f"Current run did pagination, or next one will, keeping {len(last_fetched_ids)} IDs from last fetch") # If the next run will do pagination, or the current run did pagination, we should keep the ids from the last fetch # until progress is made, so we exclude them in the next fetch. fetched_ids.extend(last_fetched_ids) return incidents, fetched_ids, new_last_date def get_recon_notification_ids_for_fetch( filter: str, recon_offset: Optional[int], limit: int = INCIDENTS_PER_FETCH, sort: str = "created_date|asc", query: str = "" ) -> tuple[list[str], int, int]: """ Get the Recon notification IDs for fetch. :param filter: The filter to use. :param recon_offset: The offset to start from. :param limit: The limit of the results. :param sort: The sort order. :param query: The query to use. :return: A tuple containing the IDs, the offset, and the total number of results. """ params = assign_params(filter=filter, limit=limit, offset=recon_offset, sort=sort, q=query) demisto.debug(f"Recon-Log Recon notifications query params: {params=}") # The API limit of this request(limit + offset) is 10K raw = http_request("GET", "/recon/queries/notifications/v1", params=params) ids = raw.get("resources", []) pagination = dict_safe_get(raw, ["meta", "pagination"]) or {} total = pagination.get("total", 0) offset = pagination.get("offset", 0) demisto.debug(f"Recon-Log Recon notifications pagination object: {pagination=}") return ids, offset, total def get_recon_notifications_detailed(notification_ids: list[str]) -> list[dict[str, Any]]: """ Get the Recon notification entities with pagination support. Args: notification_ids (list[str]): The Recon notification IDs. Returns: list[dict[str, Any]]: A list of the Recon notification entities. """ if not notification_ids: return [] demisto.debug(f"Recon-Log get_recon_notifications_detailed: {notification_ids=}") all_resources: list[dict[str, Any]] = [] offset = 0 total = offset + 1 while offset < total: query_params = {"ids": notification_ids, "offset": offset} demisto.debug(f"Recon-Log Recon notifications detailed request params: {query_params=}") raw = http_request(method="GET", url_suffix="/recon/entities/notifications-detailed/v1", params=query_params) all_resources.extend(raw.get("resources", [])) pagination = dict_safe_get(raw, ["meta", "pagination"], {}) total = pagination.get("total", 0) limit = pagination.get("limit", 1) offset = pagination.get("offset", 0) + limit demisto.debug(f"Recon-Log pagination info: {offset=}, {total=}, {limit=} for detailed notifications") return all_resources def recon_notifications_pagination( filter: str, recon_offset: Optional[int], api_limit: int = MAX_FETCH_SIZE, fetch_limit: int = INCIDENTS_PER_FETCH, is_fetch: bool = True, ) -> tuple[list[str], list[dict[str, Any]], int | None]: """ Paginates through Recon notifications based on a filter and fetch limits. It first fetches notification IDs using pagination, and optionally retrieves the detailed notification data for incident creation. Args: filter: The query filter string to apply to the notifications API. api_limit: The maximum number of items to request per single API call. recon_offset: The offset (page token) for the current pagination request. fetch_limit: The maximum number of total incidents to collect in this run. Defaults to INCIDENTS_PER_FETCH. is_fetch: If True, detailed notification data is fetched. If False, only IDs are collected. Defaults to True (used during incident fetching). Returns: A tuple containing: 1. collected_ids: A list of all fetched notification IDs (str). 2. collected_notifications_detailed: A list of dictionaries containing detailed notification information (only if is_fetch is True). 3. next_offset: The offset for the next pagination request (int) or None if all results were fetched in this iteration or if is_fetch is False. """ fetch_query = demisto.params().get("recon_fetch_query", "") demisto.debug(f"Recon-Log Doing Recon pagination with: {filter=}, {recon_offset=}, {api_limit=}, {fetch_limit=}") ids, offset, remote_total = get_recon_notification_ids_for_fetch( filter=filter, recon_offset=recon_offset, limit=min(api_limit, fetch_limit), query=fetch_query ) demisto.debug(f"Recon-Log Pagination results: {len(ids)=}, {offset=}") full_notifications_deta = [] if is_fetch: full_notifications_deta = get_recon_notifications_detailed(notification_ids=ids) next_offset = offset + len(ids) if offset + len(ids) < remote_total else 0 if not is_fetch: return ids, [], None return ids, full_notifications_deta, next_offset def recon_notification_to_incident(recon_notification: dict[str, Any], incident_type: str) -> dict[str, Any]: """Create an incident from a Recon notification entity. Args: recon_notification (dict[str, Any]): A Recon notification entity. incident_type (str): The incident type. Returns: dict[str, Any]: An incident from a Recon notification entity. """ incident_metadata = assign_params( mirror_direction=MIRROR_DIRECTION, mirror_instance=INTEGRATION_INSTANCE, incident_type=incident_type ) severity_map = { "low": IncidentSeverity.LOW, "medium": IncidentSeverity.MEDIUM, "high": IncidentSeverity.HIGH, "critical": IncidentSeverity.CRITICAL, "unknown": IncidentSeverity.UNKNOWN, } raw_severity = dict_safe_get(recon_notification, ["notification", "rule_priority"], "unknown") incident_context = { "name": recon_notification.get("id"), "occurred": dict_safe_get(recon_notification, ["notification", "created_date"], ""), "severity": severity_map.get(str(raw_severity).lower()), "rawJSON": json.dumps(recon_notification | incident_metadata), } return incident_context def create_recon_filter(is_paginating: bool, last_fetch_filter: str, last_created_date: str, first_fetch_timestamp: str) -> str: """Retrieve the Recon filter that will be used in the current fetch round. Args: is_paginating (bool): Whether we are doing pagination or not. last_fetch_filter (str): The last fetch filter that was used in the previous round. last_created_date (str): The last created timestamp. first_fetch_timestamp (str): The first fetch timestamp. Raises: DemistoException: If paginating and last filter is an empty string. Returns: str: The Recon filter that will be used in the current fetch. """ filter = "created_date:" if is_paginating: if not last_fetch_filter: raise DemistoException("Last fetch filter must not be empty when doing pagination") # Doing pagination, we need to use the same fetch query as the previous round filter = last_fetch_filter demisto.debug(f"Recon-Log Doing pagination, using the same query as the previous round. Filter is {filter}") else: if last_created_date == first_fetch_timestamp: # First fetch, filter = f"{filter}>='{last_created_date}'" demisto.debug(f"Recon-Log First fetch, looking for created_date >= {last_created_date=}. Filter is {filter}") else: # Not first fetch, filter = f"{filter}>'{last_created_date}'" demisto.debug(f"Recon-Log Not first fetch, looking for created_date > {last_created_date=}. Filter is {filter}") return filter def fetch_recon_incidents(recon_last_run: Dict[str, Any]) -> tuple[List[Dict], Dict[str, Any]]: """ Fetches Recon notifications and converts them into XSOAR incidents. Args: recon_last_run: A dictionary containing the last run object for Recon, including offset, last fetched timestamp, and IDs. Returns: A tuple containing: 1. A list of incident dictionaries to be created in XSOAR. 2. A dictionary representing the updated last run object for the next fetch. """ demisto.debug(f"Recon-Log {recon_last_run=}") last_ids, recon_offset, last_created, first_fetch_ts = get_current_fetch_data( last_run_object=recon_last_run, date_format=DATE_FORMAT, last_date_key="last_created_date", next_token_key="recon_offset", last_fetched_ids_key="last_resource_ids", ) demisto.debug(f"Recon-Log Recon fetch current last run: {last_ids=},{recon_offset=},{last_created=},{first_fetch_ts=}") # Validate if offset + limit exceeds the 10,000 record limit offset_int = arg_to_number(recon_offset) or 0 if offset_int + min(MAX_FETCH_SIZE, INCIDENTS_PER_FETCH) > 10000: demisto.debug(f"Recon-Log: Offset {offset_int} exceeds limit. Resetting offset.") return [], { "recon_offset": 0, "last_created_date": last_created, "last_resource_ids": last_ids, } filter = create_recon_filter( is_paginating=bool(recon_offset), last_fetch_filter=recon_last_run.get("last_fetch_filter", ""), last_created_date=last_created, first_fetch_timestamp=first_fetch_ts, ) demisto.debug(f"Recon-Log Recon fetch filter: {filter=}") ids, notifications_detailed, new_offset = recon_notifications_pagination( filter=filter, recon_offset=arg_to_number(recon_offset) ) demisto.debug(f"Recon-Log Fetched the following Recon notification IDs: [{', '.join(ids)}]") recon_incidents, fetched_ids, new_created_ts = parse_ioa_iom_incidents( fetched_data=notifications_detailed, last_date=last_created, last_fetched_ids=last_ids, date_key="notification.created_date", id_key="id", date_format=RECON_DATE_FORMAT, is_paginating=bool(new_offset), to_incident_context=recon_notification_to_incident, incident_type=RECON_NOTIFICATION, ) updated_run = { "recon_offset": new_offset, "last_created_date": new_created_ts, "last_fetch_filter": filter, "last_resource_ids": fetched_ids or last_ids, } return recon_incidents, updated_run def get_current_fetch_data( last_run_object: dict[str, Any], date_format: str, last_date_key: str, next_token_key: str, last_fetched_ids_key: str, ) -> tuple[list[str], str | None, str, str]: """Returns the last fetched ids, next token that will be used in current round, last date found in the last run object, and the first fetch timestamp. Args: last_run_object (dict[str, Any]): The last run object. date_format (str): The date format. last_date_key (str): The key of the value that holds the date in the last run object. next_token_key (str): The key of the value that holds the next token in the last run object. last_fetched_ids_key (str): The key of the value that holds the last fetched ids in the last run object. Returns: tuple[list[str], str | None | int, str, str]: The last fetched IDs, the next token/offset that will be used in the current fetch round, the last date saved in the last run object, and the first fetch timestamp. """ first_fetch_timestamp = reformat_timestamp( time=FETCH_TIME, date_format=date_format, dateparser_settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True} ) last_date = last_run_object.get(last_date_key, first_fetch_timestamp) # The next token is used when not all the results have been returned from the API, therefore, # we would need to do pagination using the next token query parameter next_token = last_run_object.get(next_token_key) # In order to deal with duplicates, we retrieve the last resource ids of the last run, so we can # compare them with the newly fetched ids, and ignore any duplicates last_fetched_ids: list[str] = last_run_object.get(last_fetched_ids_key, []) return last_fetched_ids, next_token, last_date, first_fetch_timestamp def create_iom_filter( is_paginating: bool, last_fetch_filter: str, last_scan_time: str, first_fetch_timestamp: str, configured_fetch_query: str ) -> str: """Retrieve the IOM filter that will be used in the current fetch round. Args: is_paginating (bool): Whether we are doing pagination or not. last_fetch_filter (str): The last fetch filter that was used in the previous round. last_scan_time (str): The last scan time. first_fetch_timestamp (str): The first fetch timestamp. configured_fetch_query (str): The fetched query configured by the user. Raises: DemistoException: If paginating and last filter is an empty string. Returns: str: The IOM filter that will be used in the current fetch. """ filter = "scan_time:" if is_paginating: if not last_fetch_filter: raise DemistoException("Last fetch filter must not be empty when doing pagination") # Doing pagination, we need to use the same fetch query as the previous round filter = last_fetch_filter demisto.debug(f"Doing pagination, using the same query as the previous round. Filter is {filter}") else: # If entered here, that means we aren't doing pagination if last_scan_time == first_fetch_timestamp: # First fetch, we want to include resources with a scan time # EQUAL or GREATER than the first fetch timestamp filter = f"{filter} >='{last_scan_time}'" demisto.debug(f"First fetch, looking for scan time >= {last_scan_time=}. Filter is {filter}") else: # Not first fetch, we only want to include resources with a scan time # GREATER than the last configured scan time, to prevent duplicates. filter = f"{filter} >'{last_scan_time}'" demisto.debug(f"Not first fetch, only looking for scan time > {last_scan_time=}. Filter is {filter}") if configured_fetch_query and not is_paginating: # If the user entered a fetch query, then append it to the filter demisto.debug("User entered fetch query, appending to filter") filter = f"{filter}+{configured_fetch_query}" return filter def validate_iom_fetch_query(iom_fetch_query: str) -> None: if "scan_time" in iom_fetch_query: raise DemistoException("scan_time is not allowed as part of the IOM fetch query.") def add_seconds_to_date(date: str, seconds_to_add: int, date_format: str) -> str: """Takes in a date in string format, and adds seconds to it according to seconds_to_add. Args: date (str): The date we want to add seconds to it. seconds_to_add (int): The amount of seconds to add to the date. date_format (str): The date format. Returns: str: The date with an increase in seconds. """ added_datetime = safe_strptime(date, date_format) + timedelta(seconds=seconds_to_add) return added_datetime.strftime(date_format) def reformat_timestamp(time: str, date_format: str, dateparser_settings: Any | None = None) -> str: """Format the given time according to the supplied date format. Args: time (str): The time to format. date_format (str): The date format. Raises: DemistoException: If the time is not a proper date string. Returns: str: The time in the supplied format. """ if parsed_scan_time := dateparser.parse(time, settings=dateparser_settings): return parsed_scan_time.strftime(date_format) else: raise DemistoException(f"{time=} is not a proper date string") def iom_resource_to_incident(iom_resource: dict[str, Any], incident_type: str) -> dict[str, Any]: """Create an incident from an IOM entity. Args: iom_resource (dict[str, Any]): An IOM entity. incident_type (str): The incident type. Returns: dict[str, Any]: An incident from an IOM entity. """ incident_metadata = assign_params( mirror_direction=MIRROR_DIRECTION, mirror_instance=INTEGRATION_INSTANCE, incident_type=incident_type ) incident_context = { "name": f'IOM Event ID: {iom_resource.get("id")}', "rawJSON": json.dumps(iom_resource | incident_metadata), } return incident_context def iom_ids_pagination( filter: str, api_limit: int, iom_next_token: str | None, fetch_limit: int = INCIDENTS_PER_FETCH ) -> tuple[list[str], str | None]: """This is in charge of doing the pagination process in a single fetch run, since the fetch limit can be greater than the api limit, in such a case, we do multiple API calls until we reach the fetch limit, or no more results are found by the API. Args: filter (str): The IOM filter query parameter. api_limit (int): The API limit iom_next_token (str | None): The IOM next token to start the pagination from. fetch_limit (int, optional): The fetch limit. Defaults to INCIDENTS_PER_FETCH. Returns: tuple[list[dict[str, Any]], str | None]: A tuple where the first element is the fetched resources, and the second is the next token that will be used in the next fetch run. """ total_incidents_count = 0 iom_new_next_token = iom_next_token fetched_iom_events: list[str] = [] continue_pagination = True while continue_pagination: demisto.debug(f"Doing IOM pagination with the arguments: {filter=}, {api_limit=}, {iom_new_next_token=},{fetch_limit=}") iom_resource_ids, iom_new_next_token = get_iom_ids_for_fetch( filter=filter, iom_next_token=iom_new_next_token, limit=min(api_limit, fetch_limit - total_incidents_count) ) fetched_iom_events.extend(iom_resource_ids) total_incidents_count += len(iom_resource_ids) demisto.debug(f"Results of IOM pagination: {total_incidents_count=}, {iom_new_next_token=}") if total_incidents_count >= fetch_limit or iom_new_next_token is None: # If the number of fetched incidents reaches the fetching limit, or there are no more results to be fetched # (by checking the next token variable), then we should stop the pagination process continue_pagination = False return fetched_iom_events, iom_new_next_token def get_iom_ids_for_fetch( filter: str, iom_next_token: str | None = None, limit: int = INCIDENTS_PER_FETCH ) -> tuple[list[str], str | None]: """Do a single API call to receive IOM resource ids. Args: filter (str | None): The filter to use when fetching IOM events. iom_next_token (int | None): The next token to be used as part of the pagination process. limit (int, optional): The maximum amount to fetch IOA events. Defaults to INCIDENTS_PER_FETCH. Returns: tuple[list[dict[str, Any]], str | None]: A tuple where the first element is the returned events, and the second is the next token that will be used in the next API call. """ query_params = assign_params(filter=filter, limit=limit, next_token=iom_next_token) demisto.debug(f"IOM {query_params=}") raw_response = http_request(method="GET", url_suffix="/detects/queries/iom/v2", params=query_params) resource_ids = raw_response.get("resources", []) pagination_obj = demisto.get(raw_response, "meta.pagination", {}) demisto.debug(f"{pagination_obj=}") next_token = pagination_obj.get("next_token") if next_token: # If next_token has a value, that means more pagination is needed, and the next run should use it return resource_ids, next_token else: # If it is None, that means no more pagination is required, therefore, # the next token for the next run should be None return resource_ids, None def get_iom_resources(iom_resource_ids: list[str]) -> list[dict[str, Any]]: """Get the IOM entities/details that were fetched. Args: iom_resource_ids (list[str]): The IOM resource IDs. Returns: list[dict[str, Any]]: A list of the IOM entities. """ if iom_resource_ids: query_params = "&".join(f"ids={resource_id}" for resource_id in iom_resource_ids) raw_response = http_request("GET", "/detects/entities/iom/v2", params=query_params) return raw_response.get("resources", []) else: return [] def upload_ioc_command( ioc_type=None, value=None, policy=None, expiration_days=None, share_level=None, description=None, source=None ): """ :param ioc_type: The type of the indicator: :param policy :The policy to enact when the value is detected on a host. :param share_level: The level at which the indicator will be shared. :param expiration_days: This represents the days the indicator should be valid for. :param source: The source where this indicator originated. :param description: A meaningful description of the indicator. :param value: The string representation of the indicator. """ raw_res = upload_ioc(ioc_type, value, policy, expiration_days, share_level, description, source) handle_response_errors(raw_res) iocs = search_iocs(ids=f"{ioc_type}:{value}").get("resources") if not iocs: raise DemistoException("Failed to create IOC. Please try again.") ec = [get_trasnformed_dict(iocs[0], IOC_KEY_MAP)] enrich_ioc_dict_with_ids(ec) return create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Custom IOC was created successfully", ec), ) def update_ioc_command( ioc_type=None, value=None, policy=None, expiration_days=None, share_level=None, description=None, source=None ): """ :param ioc_type: The type of the indicator: :param policy :The policy to enact when the value is detected on a host. :param share_level: The level at which the indicator will be shared. :param expiration_days: This represents the days the indicator should be valid for. :param source: The source where this indicator originated. :param description: A meaningful description of the indicator. :param value: The string representation of the indicator. """ raw_res = update_ioc(ioc_type, value, policy, expiration_days, share_level, description, source) handle_response_errors(raw_res) iocs = search_iocs(ids=f"{ioc_type}:{value}").get("resources") ec = [get_trasnformed_dict(iocs[0], IOC_KEY_MAP)] enrich_ioc_dict_with_ids(ec) return create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Custom IOC was created successfully", ec), ) def search_iocs_command( types=None, values=None, policies=None, sources=None, from_expiration_date=None, to_expiration_date=None, share_levels=None, limit=None, sort=None, offset=None, ): """ :param types: A list of indicator types. Separate multiple types by comma. :param values: Comma-separated list of indicator values :param policies: Comma-separated list of indicator policies :param sources: Comma-separated list of IOC sources :param from_expiration_date: Start of date range to search (YYYY-MM-DD format). :param to_expiration_date: End of date range to search (YYYY-MM-DD format). :param share_levels: A list of share levels. Only red is supported. :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100. :param sort: The order of the results. Format :param offset: The offset to begin the list from """ raw_res = search_iocs( types=types, values=values, policies=policies, sources=sources, sort=sort, offset=offset, expiration_from=from_expiration_date, expiration_to=to_expiration_date, share_levels=share_levels, limit=limit, ) if not raw_res: return create_entry_object(hr="Could not find any Indicators of Compromise.") handle_response_errors(raw_res) iocs = raw_res.get("resources") ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs] enrich_ioc_dict_with_ids(ec) return create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Indicators of Compromise", ec) ) def get_ioc_command(ioc_type: str, value: str): """ :param ioc_type: The type of the indicator :param value: The IOC value to retrieve """ raw_res = search_iocs(ids=f"{ioc_type}:{value}") handle_response_errors(raw_res, "Could not find any Indicators of Compromise.") iocs = raw_res.get("resources") ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs] enrich_ioc_dict_with_ids(ec) return create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Indicator of Compromise", ec) ) def delete_ioc_command(ioc_type, value): """ :param ioc_type: The type of the indicator :param value: The IOC value to delete """ raw_res = delete_ioc(ioc_type, value) handle_response_errors(raw_res, "The server has not confirmed deletion, please manually confirm deletion.") ids = f"{ioc_type}:{value}" return create_entry_object(contents=raw_res, hr=f"Custom IOC {ids} was successfully deleted.") def search_custom_iocs_command( types: list | str | None = None, values: list | str | None = None, sources: list | str | None = None, expiration: str | None = None, limit: str = "50", sort: str | None = None, offset: str | None = None, next_page_token: str | None = None, ) -> list[dict]: """ :param types: A list of indicator types. Separate multiple types by comma. :param values: Comma-separated list of indicator values :param sources: Comma-separated list of IOC sources :param expiration: The date on which the indicator will become inactive. (YYYY-MM-DD format). :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100. :param sort: The order of the results. Format :param offset: The offset to begin the list from :param next_page_token: A pagination token used with the limit parameter to manage pagination of results. On your first request, don't provide an 'after' token. On subsequent requests, provide the 'after' token from the previous response to continue from that place in the results. To access more than 10k indicators, use the 'after' parameter instead of 'offset'. """ raw_res = search_custom_iocs( types=argToList(types), values=argToList(values), sources=argToList(sources), sort=sort, offset=offset, expiration=expiration, limit=limit, after=next_page_token, ) iocs = raw_res.get("resources") meta = raw_res.get("meta") pagination_token = meta["pagination"].get("after") if meta else None if not iocs: return create_entry_object(hr="Could not find any Indicators of Compromise.") handle_response_errors(raw_res) entry_objects_list = [] ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs] entry_objects_list.append( create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Indicators of Compromise", ec, headers=IOC_HEADERS), ) ) entry_objects_list.append( create_entry_object( contents=raw_res, ec={"CrowdStrike.NextPageToken": pagination_token}, hr=tableToMarkdown("Pagination Info", pagination_token, headers=["Next Page Token"]), ) ) return entry_objects_list def get_custom_ioc_command( ioc_type: str | None = None, value: str | None = None, ioc_id: str | None = None, ) -> dict: """ :param ioc_type: IOC type :param value: IOC value :param ioc_id: IOC ID """ if not ioc_id and not (ioc_type and value): raise ValueError("Either ioc_id or ioc_type and value must be provided.") raw_res = get_custom_ioc(ioc_id) if ioc_id else search_custom_iocs(types=argToList(ioc_type), values=argToList(value)) iocs = raw_res.get("resources") handle_response_errors(raw_res) if not iocs: return create_entry_object(hr="Could not find any Indicators of Compromise.") ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs] return create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Indicator of Compromise", ec, headers=IOC_HEADERS), ) def upload_custom_ioc_command( ioc_type: str, value: str, action: str, platforms: str, severity: str | None = None, source: str | None = None, description: str | None = None, expiration: str | None = None, applied_globally: bool | None = None, host_groups: list[str] | None = None, tags: list[str] | None = None, file_name: str | None = None, mobile_action: str | None = None, ) -> list[dict]: """ :param ioc_type: The type of the indicator. :param value: The string representation of the indicator. :param action: Action to take when a host observes the custom IOC. :param platforms: The platforms that the indicator applies to. :param severity: The severity level to apply to this indicator. :param source: The source where this indicator originated. :param description: A meaningful description of the indicator. :param expiration: The date on which the indicator will become inactive. :param applied_globally: Whether the indicator is applied globally. :param host_groups: List of host group IDs that the indicator applies to. :param tags: List of tags to apply to the indicator. :param mobile_action: Action to take on mobile when a host observes the custom IOC. """ if action in {"prevent", "detect"} and not severity: raise ValueError(f"Severity is required for action {action}.") values: list[str] = argToList(value) applied_globally = argToBoolean(applied_globally) if applied_globally else None host_groups: list[str] = argToList(host_groups) tags = argToList(tags) platforms_list = argToList(platforms) if mobile_action and ("android" not in platforms_list and "ios" not in platforms_list): raise ValueError("mobile_action requires a mobile platform (android or ios) in the platforms argument.") iocs_json_batch = create_json_iocs_list( ioc_type, values, action, platforms_list, severity, source, description, expiration, applied_globally, host_groups, tags, file_name, mobile_action, ) raw_res = upload_batch_custom_ioc(ioc_batch=iocs_json_batch) handle_response_errors(raw_res) iocs = raw_res.get("resources", []) entry_objects_list = [] for ioc in iocs: ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP)] ec[0]["Filename"] = ioc.get("metadata", {}).get("filename") entry_objects_list.append( create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown(f"Custom IOC {ioc['value']} was created successfully", ec), ) ) return entry_objects_list def update_custom_ioc_command( ioc_id: str, action: str | None = None, platforms: str | None = None, severity: str | None = None, source: str | None = None, description: str | None = None, expiration: str | None = None, file_name: str | None = None, mobile_action: str | None = None, ) -> dict: """ :param ioc_id: The ID of the indicator to update. :param action: Action to take when a host observes the custom IOC. :param platforms: The platforms that the indicator applies to. :param severity: The severity level to apply to this indicator. :param source: The source where this indicator originated. :param description: A meaningful description of the indicator. :param expiration: The date on which the indicator will become inactive. :param file_name: The file name associated with the indicator. :param mobile_action: Action to take on mobile when a host observes the custom IOC. """ raw_res = update_custom_ioc( ioc_id, action, argToList(platforms), severity, source, description, expiration, file_name, mobile_action, ) handle_response_errors(raw_res) iocs = raw_res.get("resources", []) ec = [get_trasnformed_dict(iocs[0], IOC_KEY_MAP)] ec[0]["Filename"] = iocs[0].get("metadata", {}).get("filename") return create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Custom IOC was updated successfully", ec), ) def delete_custom_ioc_command(ioc_id: str) -> dict: """ :param ioc_id: The ID of indicator to delete. """ raw_res = delete_custom_ioc(ioc_id) handle_response_errors(raw_res, "The server has not confirmed deletion, please manually confirm deletion.") return create_entry_object(contents=raw_res, hr=f"Custom IOC {ioc_id} was successfully deleted.") def get_ioc_device_count_command(ioc_type: str, value: str): """ :param ioc_type: The type of the indicator :param value: The IOC value """ raw_res = get_ioc_device_count(ioc_type, value) if "No results found for" in raw_res: return raw_res else: handle_response_errors(raw_res) device_count_res = raw_res.get("resources") ioc_id = f"{ioc_type}:{value}" if not device_count_res: return create_entry_object(raw_res, hr=f"Could not find any devices the IOC **{ioc_id}** was detected in.") device_count = device_count_res[0].get("device_count") if argToBoolean(device_count_res[0].get("limit_exceeded", False)): demisto.debug(f"limit exceeded for {ioc_id}, trying to count by run_indicator_device_id_request") # rate limit exceeded, so we will get the count by running the run_indicator_device_id_request function # see https://falcon.crowdstrike.com/documentation/page/ed1b4a95/detection-and-prevention-policy-apis device_count = 0 params = assign_params(type=ioc_type, value=value) while True: device_ids_raw = run_indicator_device_id_request(params) device_count += len(device_ids_raw.get("resources", [])) offset = demisto.get(device_ids_raw, "meta.pagination.offset") if not offset: break params["offset"] = offset device_count_res[0]["device_count"] = device_count context = [get_trasnformed_dict(device_count, IOC_DEVICE_COUNT_MAP) for device_count in device_count_res] hr = f"Indicator of Compromise **{ioc_id}** device count: **{device_count}**" return create_entry_object(contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": context}, hr=hr) def get_process_details_command(ids: str): """ :param ids: proccess ids """ ids = argToList(ids) raw_res = get_process_details(ids) handle_response_errors(raw_res) proc = raw_res.get("resources") if not proc: return create_entry_object(raw_res, hr="Could not find any searched processes.") proc_hr_ids = str(ids)[1:-1].replace("'", "") title = f"Details for process{'es' if len(ids) > 1 else ''}: {proc_hr_ids}." return create_entry_object( contents=raw_res, hr=tableToMarkdown(title, proc), ec={"CrowdStrike.Process(val.process_id === obj.process_id)": proc} ) def get_proccesses_ran_on_command(ioc_type, value, device_id): """ :param device_id: Device id the IOC ran on :param ioc_type: The type of the indicator :param value: The IOC value """ raw_res = get_proccesses_ran_on(ioc_type, value, device_id) handle_response_errors(raw_res) proc_ids = raw_res.get("resources") ioc_id = f"{ioc_type}:{value}" if not proc_ids: return create_entry_object(raw_res, hr=f"Could not find any processes associated with the IOC **{ioc_id}**.") context = {"ID": ioc_id, "Type": ioc_type, "Value": value, "Process": {"DeviceID": device_id, "ID": proc_ids}} hr = tableToMarkdown(f"Processes with custom IOC {ioc_id} on device {device_id}.", proc_ids, headers="Process ID") return create_entry_object(contents=raw_res, hr=hr, ec={"CrowdStrike.IOC(val.ID === obj.ID)": context}) def search_device_command(): """ Searches for a device :return: EntryObject of search device command """ raw_res = search_device() device_ids = [] if not raw_res: return create_entry_object(hr="Could not find any devices.") devices = raw_res.get("resources") extended_data = argToBoolean(demisto.args().get("extended_data", False)) for device in devices: device_id = device.get("device_id") device_ids.append(device_id) state_data = get_status(device_ids) command_results = [] for single_device in devices: endpoint = generate_endpoint_by_contex_standard(single_device, state_data) if not extended_data: entry = get_trasnformed_dict(single_device, SEARCH_DEVICE_KEY_MAP) headers = ["ID", "Hostname", "OS", "MacAddress", "LocalIP", "ExternalIP", "FirstSeen", "LastSeen", "Status"] else: if device_groups := single_device.get("groups"): single_device.update({"group_names": list(enrich_groups(device_groups).values())}) entry = get_trasnformed_dict(single_device, SEARCH_DEVICE_VERBOSE_KEY_MAP) headers = list(SEARCH_DEVICE_VERBOSE_KEY_MAP.values()) command_results.append( CommandResults( outputs_prefix="CrowdStrike.Device", outputs_key_field="ID", outputs=entry, readable_output=tableToMarkdown("Devices", entry, headers=headers, headerTransform=pascalToSpace), raw_response=raw_res, indicator=endpoint, ) ) return command_results def search_device_by_ip(raw_res, ip_address): devices = raw_res.get("resources") filtered_devices = [] for single_device in devices: if single_device.get("local_ip") == ip_address: filtered_devices.append(single_device) if filtered_devices: raw_res["resources"] = filtered_devices else: raw_res = None return raw_res def enrich_groups(all_group_ids) -> dict[str, Any]: """ Receives a list of group_ids Returns a dict {group_id: group_name} """ result = {} params = {"ids": all_group_ids} response_json = http_request("GET", "/devices/entities/host-groups/v1", params, status_code=404) for resource in response_json["resources"] or []: try: result[resource["id"]] = resource["name"] except KeyError: demisto.debug(f"Could not retrieve group name for {resource=}") return result def get_status(device_ids): """ Get the online status for one or more hosts by specifying each host’s unique ID (up to 100 max). The status can be online, offline, or unknown. Args: device_ids: list of device ids. Returns: dictionary contains the id:state """ state_data = {} batch_size = 100 for i in range(0, len(device_ids), batch_size): batch = device_ids[i : i + batch_size] raw_res = http_request("GET", "/devices/entities/online-state/v1", params={"ids": batch}) for res in raw_res.get("resources"): state = res.get("state", "") device_id = res.get("id", "") if state == "unknown": demisto.debug( f"Device with id: {device_id} returned an unknown state, which indicates that the host has not" f" been seen recently and we are not confident about its current state" ) state_data[device_id] = HOST_STATUS_DICT[state] return state_data def get_isolation_status(endpoint_status): is_isolated = "" if endpoint_status == "containment_pending": is_isolated = "Pending isolation" elif endpoint_status == "contained": is_isolated = "Yes" elif endpoint_status == "lift_containment_pending": is_isolated = "Pending unisolation" elif endpoint_status.lower() != "normal": raise DemistoException(f"Error: Unknown endpoint status was given: {endpoint_status}") return is_isolated def generate_endpoint_by_contex_standard(single_device, state_data): device_id = single_device.get("device_id") endpoint = Common.Endpoint( id=device_id, hostname=single_device.get("hostname"), ip_address=single_device.get("local_ip"), os=single_device.get("platform_name"), os_version=single_device.get("os_version"), status=state_data.get(device_id), is_isolated=get_isolation_status(single_device.get("status")), mac_address=single_device.get("mac_address"), vendor=INTEGRATION_NAME, ) return endpoint def get_endpoint_command(): args = demisto.args() if "id" in args: args["ids"] = args.get("id", "") if not args.get("ip") and not args.get("id") and not args.get("hostname"): # in order not to return all the devices return create_entry_object(hr="Please add a filter argument - ip, hostname or id.") # use OR operator between filters (https://github.com/demisto/etc/issues/46353) raw_res = search_device(filter_operator="OR") if not raw_res: return create_entry_object(hr="Could not find any devices.") devices = raw_res.get("resources") device_ids = [] for device in devices: device_id = device.get("device_id") device_ids.append(device_id) state_data = get_status(device_ids) # filter hostnames that will match the exact hostnames including case-sensitive if hostnames := argToList(args.get("hostname")): lowercase_hostnames = {hostname.lower() for hostname in hostnames} devices = [device for device in devices if (device.get("hostname") or "").lower() in lowercase_hostnames] standard_endpoints = [] for single_device in devices: standard_endpoints.append(generate_endpoint_by_contex_standard(single_device, state_data)) command_results = [] for endpoint in standard_endpoints: endpoint_context = endpoint.to_context().get(Common.Endpoint.CONTEXT_PATH) hr = tableToMarkdown("CrowdStrike Falcon Endpoint", endpoint_context) command_results.append(CommandResults(readable_output=hr, raw_response=raw_res, indicator=endpoint)) return command_results def get_behavior_command(): """ Gets a behavior by ID :return: EntryObject of get behavior command """ behavior_id = demisto.args().get("behavior_id") detections_ids = demisto.get(get_detections(behavior_id=behavior_id), "resources") raw_res = get_detections_entities(detections_ids) entries = [] if "resources" in raw_res: for resource in demisto.get(raw_res, "resources"): for behavior in demisto.get(resource, "behaviors"): entries.append(behavior_to_entry_context(behavior)) hr = tableToMarkdown(f"Behavior ID: {behavior_id}", entries, headerTransform=pascalToSpace) # no dt since behavior vary by more than their ID ec = {"CrowdStrike.Behavior": entries} return create_entry_object(contents=raw_res, ec=ec, hr=hr) def search_detections_command(): """ Searches for a detection :return: EntryObject of search detections command """ d_args = demisto.args() detections_ids = argToList(d_args.get("ids")) extended_data = argToBoolean(d_args.get("extended_data", False)) if not detections_ids: # This value is similar to the default value of the filter argument in the YAML file filter_arg = d_args.get("filter", "product:'epp'+type:'ldt'") detections_ids = get_detections(filter_arg=filter_arg).get("resources") raw_res = get_detections_entities(detections_ids) entries = [] headers = ["ID", "Status", "System", "ProcessStartTime", "CustomerID", "MaxSeverity"] if "resources" in raw_res: for detection in demisto.get(raw_res, "resources"): detection_entry = {} detection = modify_detection_outputs(detection) for path, new_key in DETECTIONS_BASE_KEY_MAP.items(): detection_entry[new_key] = demisto.get(detection, path) behaviors = [] for behavior in demisto.get(detection, "behaviors"): behaviors.append(behavior_to_entry_context(behavior)) detection_entry["Behavior"] = behaviors if extended_data: detection_entry["Device"] = demisto.get(detection, "device") entries.append(detection_entry) hr = tableToMarkdown("Detections Found:", entries, headers=headers, removeNull=True, headerTransform=pascalToSpace) return CommandResults( readable_output=hr, outputs=entries, outputs_key_field="ID", outputs_prefix="CrowdStrike.Detection", raw_response=raw_res ) def resolve_detection_command(): """ Resolves single or multiple detections :return: EntryObject of resolve detection command """ args = demisto.args() ids = argToList(args.get("ids")) username = args.get("username") assigned_to_uuid = args.get("assigned_to_uuid") comment = args.get("comment") if username and assigned_to_uuid: raise ValueError("Only one of the arguments assigned_to_uuid or username should be provided, not both.") status = args.get("status") if status in ["true_positive", "false_positive", "ignored"]: raise ValueError( f"The status chosen: {status} is deprecated due to the deprecation of the Legacy API. Choose a different one from the available options." # noqa: E501 ) # noqa: E501 tag = args.get("tag") show_in_ui = args.get("show_in_ui") if not (username or assigned_to_uuid or comment or status or show_in_ui or tag): raise DemistoException("Please provide at least one argument to resolve the detection with.") raw_res = resolve_detection(ids, status, assigned_to_uuid, username, show_in_ui, comment, tag) args.pop("ids") hr = f"Detection {str(ids)[1:-1]} updated\n" hr += "With the following values:\n" for k, arg in args.items(): hr += f"\t{k}:{arg}\n" return create_entry_object(contents=raw_res, hr=hr) def contain_host_command(): """ Contains hosts with user arg ids :return: EntryObject of contain host command """ ids = argToList(demisto.args().get("ids")) raw_res = contain_host(ids) hr = f"Host {str(ids)[1:-1]} contained" return create_entry_object(contents=raw_res, hr=hr) def lift_host_containment_command(): """ Lifts containment off a host :return: EntryObject of lift host containment """ ids = argToList(demisto.args().get("ids")) lift_filesystem = argToBoolean(demisto.args().get("lift_filesystem_containment_all", "false")) action_name = "lift_filesystem_containment_all" if lift_filesystem else "lift_containment" raw_res = lift_host_containment(ids, action_name=action_name) if lift_filesystem: hr = f"Filesystem containment has been lifted off host {str(ids)[1:-1]}" else: hr = f"Containment has been lifted off host {str(ids)[1:-1]}" return create_entry_object(contents=raw_res, hr=hr) def run_command(): args = demisto.args() host_ids = argToList(args.get("host_ids")) command_type = args.get("command_type") full_command = args.get("full_command") scope = args.get("scope", "read") target = args.get("target", "batch") timeout = int(args.get("timeout", 180)) offline = argToBoolean(args.get("queue_offline", False)) output = [] if target == "batch": batch_id = args.get("batch_id", None) if args.get("batch_id", None) else init_rtr_batch_session(host_ids, offline) demisto.debug(f"{args.get('batch_id', None)=} , {batch_id=}") timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id}) timer.start() try: if scope == "read": response = run_batch_read_cmd(batch_id, command_type, full_command, timeout=timeout) elif scope == "write": response = run_batch_write_cmd(batch_id, command_type, full_command, timeout=timeout) else: # scope = admin response = run_batch_admin_cmd(batch_id, command_type, full_command, timeout=timeout) finally: timer.cancel() resources: dict = response.get("combined", {}).get("resources", {}) for _, resource in resources.items(): errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not run command\n{errors}" return_error(error_message) output.append( { "HostID": resource.get("aid"), "SessionID": resource.get("session_id"), "Stdout": resource.get("stdout"), "Stderr": resource.get("stderr"), "BaseCommand": resource.get("base_command"), "Command": full_command, "BatchID": batch_id, } ) human_readable = tableToMarkdown(f"Command {full_command} results", output, removeNull=True) entry_context_batch = {"CrowdStrike": {"Command": output}} return create_entry_object(contents=response, ec=entry_context_batch, hr=human_readable) else: # target = 'single' responses = [] for host_id in host_ids: if scope == "read": response1 = run_single_read_cmd(host_id, command_type, full_command, offline, timeout=timeout) elif scope == "write": response1 = run_single_write_cmd(host_id, command_type, full_command, offline, timeout=timeout) else: # scope = admin response1 = run_single_admin_cmd(host_id, command_type, full_command, offline, timeout=timeout) responses.append(response1) for resource in response1.get("resources", []): errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not run command\n{errors}" return_error(error_message) output.append( { "HostID": host_id, "TaskID": resource.get("cloud_request_id"), "SessionID": resource.get("session_id"), "BaseCommand": command_type, "Command": full_command, "Complete": False, "NextSequenceID": 0, } ) human_readable = tableToMarkdown(f"Command {full_command} results", output, removeNull=True) entry_context_single = {"CrowdStrike.Command(val.TaskID === obj.TaskID)": output} return create_entry_object(contents=responses, ec=entry_context_single, hr=human_readable) def upload_script_command(): args = demisto.args() name = args.get("name") permission_type = args.get("permission_type", "private") content = args.get("content") entry_id = args.get("entry_id") if content and entry_id: raise ValueError("Only one of the arguments entry_id or content should be provided, not both.") elif not content and not entry_id: raise ValueError("One of the arguments entry_id or content must be provided, none given.") response = upload_script(name, permission_type, content, entry_id) return create_entry_object(contents=response, hr="The script was uploaded successfully") def get_script_command(): script_id = argToList(demisto.args().get("script_id")) response = get_script(script_id) resources: list = response.get("resources", []) if resources and isinstance(resources, list): resource = resources[0] script = { "ID": resource.get("id"), "CreatedBy": resource.get("created_by"), "CreatedTime": resource.get("created_timestamp"), "Description": resource.get("description"), "ModifiedBy": resource.get("modified_by"), "ModifiedTime": resource.get("modified_timestamp"), "Name": resource.get("name"), "Permission": resource.get("permission_type"), "SHA256": resource.get("sha256"), "RunAttemptCount": resource.get("run_attempt_count"), "RunSuccessCount": resource.get("run_success_count"), "WriteAccess": resource.get("write_access"), } human_readable = tableToMarkdown(f"CrowdStrike Falcon script {script_id}", script) entry_context = {"CrowdStrike.Script(val.ID === obj.ID)": script} script_content = resource.get("content") if script_content: demisto.results(fileResult(f"{resource.get('name', 'script')}.ps1", script_content)) return create_entry_object(contents=response, ec=entry_context, hr=human_readable) else: return "No script found." def delete_script_command(): script_id = demisto.args().get("script_id") response = delete_script(script_id) return create_entry_object(contents=response, hr=f"Script {script_id} was deleted successfully") def list_scripts_command(): response = list_scripts() resources: list = response.get("resources", []) scripts = [] for resource in resources: scripts.append( { "ID": resource.get("id"), "CreatedBy": resource.get("created_by"), "CreatedTime": resource.get("created_timestamp"), "Description": resource.get("description"), "ModifiedBy": resource.get("modified_by"), "ModifiedTime": resource.get("modified_timestamp"), "Name": resource.get("name"), "Permission": resource.get("permission_type"), "SHA256": resource.get("sha256"), "RunAttemptCount": resource.get("run_attempt_count"), "RunSuccessCount": resource.get("run_success_count"), "Platform": resource.get("platform"), "WriteAccess": resource.get("write_access"), } ) human_readable = tableToMarkdown("CrowdStrike Falcon scripts", scripts) entry_context = {"CrowdStrike.Script(val.ID === obj.ID)": scripts} return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def upload_file_command(): entry_id = demisto.args().get("entry_id") description = demisto.args().get("description", "File uploaded from Demisto") response, file_name = upload_file(entry_id, description) return create_entry_object(contents=response, hr="File was uploaded successfully") def delete_file_command(): """ This command deletes a file by either file_id or file_name. If file_name is provided without file_id, it will first list all files to find the corresponding file_id. Args: file_id (str, optional): The ID of the file to delete file_name (str, optional): The name of the file to delete Returns: dict: Entry object with deletion confirmation message Raises: ValueError: If neither file_name nor file_id is provided, or if file with given name is not found """ file_id = demisto.args().get("file_id") file_name = demisto.args().get("file_name") if not file_name and not file_id: raise ValueError("Either file_name or file_id must be provided.") if not file_id: file_id = get_file_id_by_name(file_name) if not file_id: raise ValueError(f"File with name '{file_name}' not found.") response = delete_file(file_id) return create_entry_object(contents=response, hr=f"File {file_id} was deleted successfully") def get_file_command(): file_id = argToList(demisto.args().get("file_id")) response = get_file(file_id) resources: list = response.get("resources", []) if resources and isinstance(resources, list): # will always be a list of one resource resource = resources[0] file_ = { "ID": resource.get("id"), "CreatedBy": resource.get("created_by"), "CreatedTime": resource.get("created_timestamp"), "Description": resource.get("description"), "Type": resource.get("file_type"), "ModifiedBy": resource.get("modified_by"), "ModifiedTime": resource.get("modified_timestamp"), "Name": resource.get("name"), "Permission": resource.get("permission_type"), "SHA256": resource.get("sha256"), } file_standard_context = { "Type": resource.get("file_type"), "Name": resource.get("name"), "SHA256": resource.get("sha256"), "Size": resource.get("size"), } human_readable = tableToMarkdown(f"CrowdStrike Falcon file {file_id}", file_) entry_context = {"CrowdStrike.File(val.ID === obj.ID)": file_, outputPaths["file"]: file_standard_context} file_content = resource.get("content") if file_content: demisto.results(fileResult(resource.get("name"), file_content)) return create_entry_object(contents=response, ec=entry_context, hr=human_readable) else: return "No file found." def list_files_command(): response = list_files() resources: list = response.get("resources", []) files_output = [] file_standard_context = [] for resource in resources: files_output.append( { "ID": resource.get("id"), "CreatedBy": resource.get("created_by"), "CreatedTime": resource.get("created_timestamp"), "Description": resource.get("description"), "Type": resource.get("file_type"), "ModifiedBy": resource.get("modified_by"), "ModifiedTime": resource.get("modified_timestamp"), "Name": resource.get("name"), "Permission": resource.get("permission_type"), "SHA256": resource.get("sha256"), } ) file_standard_context.append( { "Type": resource.get("file_type"), "Name": resource.get("name"), "SHA256": resource.get("sha256"), "Size": resource.get("size"), } ) human_readable = tableToMarkdown("CrowdStrike Falcon files", files_output) entry_context = {"CrowdStrike.File(val.ID === obj.ID)": files_output, outputPaths["file"]: file_standard_context} return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def run_script_command(): args = demisto.args() script_name = args.get("script_name") raw = args.get("raw") host_ids = argToList(args.get("host_ids")) offline = argToBoolean(args.get("queue_offline", False)) full_command = "" try: timeout = int(args.get("timeout", 30)) except ValueError as e: demisto.error(str(e)) raise ValueError("Timeout argument should be an integer, for example: 30") full_command = "" # initialized variable here to avoid pylint errors if script_name and raw: raise ValueError("Only one of the arguments script_name or raw should be provided, not both.") elif not script_name and not raw: raise ValueError("One of the arguments script_name or raw must be provided, none given.") elif script_name: full_command = f"runscript -CloudFile={script_name}" elif raw: full_command = f"runscript -Raw=```{raw}```" full_command += f" -Timeout={timeout}" command_type = "runscript" batch_id = init_rtr_batch_session(host_ids, offline) timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id}) timer.start() try: response = run_batch_admin_cmd(batch_id, command_type, full_command, timeout) finally: timer.cancel() resources: dict = response.get("combined", {}).get("resources", {}) output = [] for _, resource in resources.items(): errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not run command\n{errors}" return_error(error_message) full_command = full_command.replace("`", "") stderr = resource.get("stderr") output.append( { "HostID": resource.get("aid"), "SessionID": resource.get("session_id"), "Stdout": resource.get("stdout"), "Stderr": stderr, "BaseCommand": resource.get("base_command"), "Command": full_command, } ) if stderr: raise DemistoException(f"cs-falcon-run-script command failed with the following error: {stderr}") human_readable = tableToMarkdown(f"Command {full_command} results", output) entry_context = {"CrowdStrike": {"Command": output}} return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def run_get_command(is_polling=False, offline=False): request_ids_for_polling = [] args = demisto.args() host_ids = argToList(args.get("host_ids")) file_path = args.get("file_path") optional_hosts = argToList(args.get("optional_hosts")) timeout = args.get("timeout") timeout_duration = args.get("timeout_duration") timeout = timeout and int(timeout) response = run_batch_get_cmd(host_ids, file_path, optional_hosts, timeout, timeout_duration, offline) resources: dict = response.get("combined", {}).get("resources", {}) output = [] for _, resource in resources.items(): errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not get command\n{errors}" return_error(error_message) output.append( { "HostID": resource.get("aid"), "Stdout": resource.get("stdout"), "Stderr": resource.get("stderr"), "BaseCommand": resource.get("base_command"), "TaskID": resource.get("task_id"), "GetRequestID": response.get("batch_get_cmd_req_id"), "Complete": resource.get("complete") or False, "FilePath": file_path, } ) request_ids_for_polling.append( { "RequestID": response.get("batch_get_cmd_req_id"), "HostID": resource.get("aid"), } ) if is_polling: return request_ids_for_polling human_readable = tableToMarkdown(f"Get command has requested for a file {file_path}", output) entry_context = {"CrowdStrike.Command(val.TaskID === obj.TaskID)": output} return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def status_get_command(args, is_polling=False): request_ids_for_polling = {} request_ids = argToList(args.get("request_ids")) timeout = args.get("timeout") timeout_duration = args.get("timeout_duration") timeout = timeout and int(timeout) responses = [] files_output = [] file_standard_context = [] sha256 = "" # Used for the polling. When this isn't empty it indicates that the status is "ready". for request_id in request_ids: response = status_get_cmd(request_id, timeout, timeout_duration) responses.append(response) resources: dict = response.get("resources", {}) for host_id, resource in resources.items(): errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not get command\n{errors}" return_error(error_message) files_output.append( { "ID": resource.get("id"), "TaskID": resource.get("cloud_request_id"), "CreatedAt": resource.get("created_at"), "DeletedAt": resource.get("deleted_at"), "UpdatedAt": resource.get("updated_at"), "Name": resource.get("name"), "Size": resource.get("size"), "SHA256": resource.get("sha256"), } ) file_standard_context.append( { "Name": resource.get("name"), "SHA256": resource.get("sha256"), "Size": resource.get("size"), } ) sha256 = resource.get("sha256", "") request_ids_for_polling[host_id] = {"SHA256": sha256} if is_polling: args["SHA256"] = sha256 return request_ids_for_polling, args human_readable = tableToMarkdown("CrowdStrike Falcon files", files_output) entry_context = { "CrowdStrike.File(val.ID === obj.ID || val.TaskID === obj.TaskID)": files_output, outputPaths["file"]: file_standard_context, } if len(responses) == 1: return create_entry_object(contents=responses[0], ec=entry_context, hr=human_readable) else: return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def status_command(): args = demisto.args() request_id = args.get("request_id") sequence_id = args.get("sequence_id") scope = args.get("scope", "read") sequence_id = None if sequence_id is None else int(sequence_id) if scope == "read": response = status_read_cmd(request_id, sequence_id) elif scope == "write": response = status_write_cmd(request_id, sequence_id) else: # scope = admin response = status_admin_cmd(request_id, sequence_id) resources: list = response.get("resources", []) output = [] for resource in resources: errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not run command\n{errors}" return_error(error_message) sequence_id = int(resource.get("sequence_id", 0)) output.append( { "Complete": resource.get("complete") or False, "Stdout": resource.get("stdout"), "Stderr": resource.get("stderr"), "BaseCommand": resource.get("base_command"), "TaskID": resource.get("task_id"), "SequenceID": sequence_id, "NextSequenceID": sequence_id + 1, } ) human_readable = tableToMarkdown("Command status results", output, removeNull=True) entry_context = {"CrowdStrike.Command(val.TaskID === obj.TaskID)": output} return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def get_extracted_file_command(args): host_id = args.get("host_id") sha256 = args.get("sha256") filename = args.get("filename") response = get_extracted_file(host_id, sha256, filename) # save an extracted file content_type = response.headers.get("Content-Type", "").lower() if content_type == "application/x-7z-compressed": content_disposition = response.headers.get("Content-Disposition", "").lower() if content_disposition: filename = email.message_from_string(f"Content-Disposition: {content_disposition}\n\n").get_filename() if not filename: sha256 = sha256 or hashlib.sha256(response.content).hexdigest() filename = sha256.lower() + ".7z" return fileResult(filename, response.content) return_error("An extracted file is missing in the response") return None def list_host_files_command(): args = demisto.args() host_id = args.get("host_id") session_id = args.get("session_id") response = list_host_files(host_id, session_id) resources: list = response.get("resources", []) files_output = [] file_standard_context = [] command_output = [] for resource in resources: errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not run command\n{errors}" return_error(error_message) command_output.append( {"HostID": host_id, "TaskID": resource.get("cloud_request_id"), "SessionID": resource.get("session_id")} ) files_output.append( { "ID": resource.get("id"), "CreatedAt": resource.get("created_at"), "DeletedAt": resource.get("deleted_at"), "UpdatedAt": resource.get("updated_at"), "Name": resource.get("name"), "SHA256": resource.get("sha256"), "Size": resource.get("size"), "Stdout": resource.get("stdout"), "Stderr": resource.get("stderr"), } ) file_standard_context.append( { "Name": resource.get("name"), "SHA256": resource.get("sha256"), "Size": resource.get("size"), } ) human_readable = tableToMarkdown("CrowdStrike Falcon files", files_output) if files_output else "No result found" entry_context = { "CrowdStrike.Command(val.TaskID === obj.TaskID)": command_output, "CrowdStrike.File(val.ID === obj.ID)": files_output, outputPaths["file"]: file_standard_context, } return create_entry_object(contents=response, ec=entry_context, hr=human_readable) def refresh_session_command(): args = demisto.args() host_id = args.get("host_id") response = refresh_session(host_id) resources: list = response.get("resources", []) session_id = None for resource in resources: errors = resource.get("errors", []) if errors: error_message = errors[0].get("message", "") if not error_message: error_message = f"Could not run command\n{errors}" return_error(error_message) session_id = resource.get("session_id") return create_entry_object(contents=response, hr=f"CrowdStrike Session Refreshed: {session_id}") def build_error_message(raw_res): if raw_res.get("errors"): error_data = raw_res.get("errors")[0] else: error_data = {"code": "None", "message": "something got wrong, please try again"} error_code = error_data.get("code") error_message = error_data.get("message") return f"Error: error code: {error_code}, error_message: {error_message}." def validate_response(raw_res): return "resources" in raw_res def run_indicator_device_id_request(params): return http_request("GET", "/indicators/queries/devices/v1", params=params, status_code=404) def get_indicator_device_id(): args = demisto.args() ioc_type = args.get("type") ioc_value = args.get("value") params = assign_params(type=ioc_type, value=ioc_value) raw_res = run_indicator_device_id_request(params=params) errors = raw_res.get("errors", []) for error in errors: if error.get("code") == 404: return f"No results found for {ioc_type} - {ioc_value}" devices_response = [] if validate_response(raw_res): devices_response = raw_res.get("resources") else: error_message = build_error_message(raw_res) return_error(error_message) ioc_id = f"{ioc_type}:{ioc_value}" readable_output = tableToMarkdown(f"Devices that encountered the IOC {ioc_id}", devices_response, headers="Device ID") outputs = { "DeviceID": devices_response, "DeviceIOC": { "Type": ioc_type, "Value": ioc_value, "ID": ioc_id, "DeviceID": devices_response, }, } return CommandResults( readable_output=readable_output, outputs_prefix="CrowdStrike", outputs_key_field="DeviceIOC.ID", outputs=outputs, raw_response=raw_res, ) def detections_to_human_readable(detections): detections_readable_outputs = [] for detection in detections: readable_output = assign_params( status=detection.get("status"), max_severity=detection.get("severity_name"), detection_id=detection.get("detection_id"), created_time=detection.get("created_timestamp"), ) detections_readable_outputs.append(readable_output) headers = ["detection_id", "created_time", "status", "max_severity"] human_readable = tableToMarkdown("CrowdStrike Detections", detections_readable_outputs, headers, removeNull=True) return human_readable def list_detection_summaries_command(): args = demisto.args() fetch_query = args.get("fetch_query") args_ids = args.get("ids") if args_ids: detections_ids = argToList(args_ids) elif fetch_query: fetch_query = f"{fetch_query}" detections_ids = demisto.get(get_fetch_detections(filter_arg=fetch_query), "resources") else: detections_ids = demisto.get(get_fetch_detections(), "resources") detections_response_data = get_detections_entities(detections_ids) detections = list(detections_response_data.get("resources")) if detections_response_data else [] # modify the new version (raptor) outputs to match the old format for backward compatibility detections = [modify_detection_summaries_outputs(detection) for detection in detections] detections_human_readable = detections_to_human_readable(detections) return CommandResults( readable_output=detections_human_readable, outputs_prefix="CrowdStrike.Detections", outputs_key_field="detection_id", outputs=detections, ) def cases_to_human_readable(cases): """ Converts a list of cases to a human-readable format. Args: cases: A list of cases. Returns: str: The human-readable string. """ cases_readable_outputs = [] for case in cases: readable_output = assign_params( case_id=case.get("id"), name=case.get("name"), created_time=case.get("created_timestamp"), status=case.get("status"), version=case.get("version"), description=case.get("description"), severity=case.get("severity"), assigned_to=case.get("assigned_to"), tags=case.get("tags"), ) demisto.debug(f"appending {readable_output=} to cases_readable_outputs") cases_readable_outputs.append(readable_output) headers = ["case_id", "name", "created_timestamp", "status", "version", "description", "severity", "assigned_to", "tags"] return tableToMarkdown( "CrowdStrike Cases", cases_readable_outputs, headers, removeNull=True, headerTransform=string_to_table_header ) def list_case_summaries_command(): """ Lists case summaries. """ args = demisto.args() ids = argToList(args.get("ids")) if not ids: _, ids = get_cases_data() demisto.debug(f"About to call get_cases_entities with {ids=}") cases = get_cases_entities(ids) demisto.debug(f"got {cases=}") cases_human_readable = cases_to_human_readable(cases) return CommandResults( readable_output=cases_human_readable, outputs_prefix="CrowdStrike.Case", outputs_key_field="id", outputs=cases, ) def get_evidence_for_case_command(args: dict[str, Any]) -> CommandResults: """ Get evidence for a specific case. Args: args: The arguments of the command. Returns: CommandResults: The command results object. """ case_id = args.get("id") if not case_id: raise ValueError("The 'id' argument is required.") cases = get_cases_entities([case_id]) if not cases: return CommandResults(readable_output=f"No case found with id {case_id}") case = cases[0] evidence = case.get("evidence", {}) # Prepare Human Readable output alerts = [record.get("selector", {}).get("id") for record in evidence.get("alerts", {}).get("records", [])] events = [record.get("selector", {}).get("id") for record in evidence.get("events", {}).get("records", [])] leads = [record.get("selector", {}).get("id") for record in evidence.get("leads", {}).get("records", [])] readable_output = [{"Case Id": case.get("id"), "Case Alerts": alerts, "Case Events": events, "Case Leads": leads}] markdown_output = tableToMarkdown( "Case Evidence", readable_output, headers=["Case Id", "Case Alerts", "Case Events", "Case Leads"], removeNull=True ) return CommandResults( outputs_prefix="CrowdStrike.CaseEvidence", outputs=evidence, readable_output=markdown_output, raw_response=case ) def add_case_tags_command(args: dict[str, Any]) -> CommandResults: """ Add tags to a case. Args: args: The arguments of the command. Returns: CommandResults: The command results object. """ case_id = args.get("id") tags = argToList(args.get("tags")) if not case_id: raise ValueError("The 'id' argument is required.") if not tags: raise ValueError("The 'tags' argument is required.") add_case_tags(case_id, tags) return CommandResults(readable_output="Tags were added successfully.") def delete_case_tags_command(args: dict[str, Any]) -> CommandResults: """ Delete a tag from a case. Args: args: The arguments of the command. Returns: CommandResults: The command results object. """ case_id = args.get("id") tag = args.get("tag") if not case_id: raise ValueError("The 'id' argument is required.") if not tag: raise ValueError("The 'tag' argument is required.") delete_case_tags(case_id, tag) return CommandResults(readable_output="Tags were deleted successfully.") def patch_remote_entity( case_id: str, status: str | None = None, name: str | None = None, assigned_to_uuid: str | None = None, description: str | None = None, remove_user_assignment: bool | None = None, severity: int | None = None, template_id: str | None = None, is_recon_type: bool | None = None, ) -> dict: """ Updates an incident (Case or Recon Notification) in the remote system via PATCH request. Args: case_id (str): The unique identifier of the incident/case. status (str | None): The status to set for the incident/case. assigned_to_uuid (str | None): A UUID of a user to assign the incident/case to. description (str | None): A new description for the incident/case. remove_user_assignment (bool): Whether to remove incident/case assignment from current user. severity (int | None): The new incident/case severity rating (10-100). template_id (str | None): The unique ID of the template to apply to the incident/case. is_recon_type (bool | None): Whether the incident is a recon type. Returns: dict: The response from the API. """ # Build fields dict with API field names, filtering out None values fields = { "status": status, "name": name, "assigned_to_user_uuid": assigned_to_uuid, "description": description, "severity": severity, "template": {"id": template_id} if template_id else None, "remove_user_assignment": remove_user_assignment if remove_user_assignment else None, } remove_nulls_from_dictionary(fields) if is_recon_type: url_suffix = "/recon/entities/notifications/v1" payload: dict | list = [{"id": case_id, **fields}] else: payload = {"id": case_id, "fields": fields} url_suffix = "/cases/entities/cases/v2" demisto.debug(f"Sending PATCH request to {url_suffix} for ID: {case_id}. Payload: {payload}") return http_request("PATCH", url_suffix, json=payload) def resolve_case_command(args: dict[str, Any]) -> CommandResults: """ Command function for cs-falcon-resolve-case. """ case_id = args.get("id") if not case_id: raise ValueError("The 'id' argument is required.") severity = arg_to_number(args.get("severity")) if severity is not None and not (10 <= severity <= 100): raise ValueError("Severity must be an integer between 10 and 100.") # We take care of that value seperatly so that it won't appear in the HR unless passed by the user remove_user_assignment_str_value = args.get("remove_user_assignment") # Collect changed fields for both API call and display changed_fields: dict[str, Any] = { "status": args.get("status"), "name": args.get("name"), "assigned_to_uuid": args.get("assigned_to_uuid"), "description": args.get("description"), "severity": severity, "template_id": args.get("template_id"), } patch_remote_entity( case_id=case_id, remove_user_assignment=argToBoolean(remove_user_assignment_str_value or False), **changed_fields ) readable_output = f"Case {case_id} was changed successfully" display_data = {"id": case_id, "remove_user_assignment": remove_user_assignment_str_value, **changed_fields} if argToBoolean(remove_user_assignment_str_value or False): display_data["assigned_to_uuid"] = "Unassigned" table = tableToMarkdown( "Edited Case", display_data, headers=list(changed_fields.keys()), headerTransform=string_to_table_header, removeNull=True, ) return CommandResults(readable_output=f"{readable_output}\n{table}") def create_host_group_command( name: str, group_type: str | None = None, description: str | None = None, assignment_rule: str | None = None ) -> CommandResults: response = change_host_group( is_post=True, name=name, group_type=group_type, description=description, assignment_rule=assignment_rule ) host_groups = response.get("resources") return CommandResults( outputs_prefix="CrowdStrike.HostGroup", outputs_key_field="id", outputs=host_groups, readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS), raw_response=response, ) def update_host_group_command( host_group_id: str, name: str | None = None, description: str | None = None, assignment_rule: str | None = None ) -> CommandResults: response = change_host_group( is_post=False, host_group_id=host_group_id, name=name, description=description, assignment_rule=assignment_rule ) host_groups = response.get("resources") return CommandResults( outputs_prefix="CrowdStrike.HostGroup", outputs_key_field="id", outputs=host_groups, readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS), raw_response=response, ) def list_host_group_members_command( host_group_id: str | None = None, filter: str | None = None, offset: str | None = None, limit: str | None = None, sort: str | None = None, ) -> CommandResults: response = host_group_members(filter, host_group_id, limit, offset, sort) devices = response.get("resources") if not devices: return CommandResults(readable_output="No hosts are found", raw_response=response) headers = list(SEARCH_DEVICE_KEY_MAP.values()) outputs = [get_trasnformed_dict(single_device, SEARCH_DEVICE_KEY_MAP) for single_device in devices] return CommandResults( outputs_prefix="CrowdStrike.Device", outputs_key_field="ID", outputs=outputs, readable_output=tableToMarkdown("Devices", outputs, headers=headers, headerTransform=pascalToSpace), raw_response=response, ) def add_host_group_members_command(host_group_id: str, host_ids: list[str]) -> CommandResults: response = change_host_group_members(action_name="add-hosts", host_group_id=host_group_id, host_ids=host_ids) host_groups = response.get("resources") return CommandResults( outputs_prefix="CrowdStrike.HostGroup", outputs_key_field="id", outputs=host_groups, readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS), raw_response=response, ) def remove_host_group_members_command(host_group_id: str, host_ids: list[str]) -> CommandResults: response = change_host_group_members(action_name="remove-hosts", host_group_id=host_group_id, host_ids=host_ids) host_groups = response.get("resources") return CommandResults( outputs_prefix="CrowdStrike.HostGroup", outputs_key_field="id", outputs=host_groups, readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS), raw_response=response, ) def list_host_groups_command(filter: str | None = None, offset: str | None = None, limit: str | None = None) -> CommandResults: response = list_host_groups(filter, limit, offset) host_groups = response.get("resources") return CommandResults( outputs_prefix="CrowdStrike.HostGroup", outputs_key_field="id", outputs=host_groups, readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS), raw_response=response, ) def delete_host_groups_command(host_group_ids: list[str]) -> CommandResults: response = delete_host_groups(host_group_ids) deleted_ids = response.get("resources") readable = ( "\n".join([f"Host groups {host_group_id} deleted successfully" for host_group_id in deleted_ids]) if deleted_ids else f"Host groups {host_group_ids} are not deleted" ) return CommandResults(readable_output=readable, raw_response=response) def upload_batch_custom_ioc_command( multiple_indicators_json: str | None = None, timeout: str = "180", ) -> list[dict]: """ :param multiple_indicators_json: A JSON object with list of CS Falcon indicators to upload. """ batch_json = safe_load_json(multiple_indicators_json) raw_res = upload_batch_custom_ioc(batch_json, timeout=float(timeout)) handle_response_errors(raw_res) iocs = raw_res.get("resources", []) entry_objects_list = [] for ioc in iocs: ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP)] entry_objects_list.append( create_entry_object( contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown(f"Custom IOC {ioc['value']} was created successfully", ec), ) ) return entry_objects_list # ============== NGSIEM Search Events Functions ============== def initiate_ngsiem_search_request(repository: str, body: dict) -> dict: """ Initiate an NGSIEM search query job. Args: repository: The repository to search (e.g., 'search-all'). body: The request body containing query parameters. Returns: dict: Response containing the job ID. """ demisto.debug(f"Initiating NGSIEM search with {repository=}, {body=}") return http_request( method="POST", url_suffix=f"/humio/api/v1/repositories/{repository}/queryjobs", json=body, ) def get_ngsiem_search_results_request(repository: str, job_id: str) -> dict: """ Get the results of an NGSIEM search query job. Args: repository: The repository that was searched. job_id: The job ID from the initiate search request. Returns: dict: Response containing the search results and status. """ demisto.debug(f"Getting NGSIEM search results for {repository=}, {job_id=}") return http_request( method="GET", url_suffix=f"/humio/api/v1/repositories/{repository}/queryjobs/{job_id}", ) def clean_ngsiem_rawstring_field(events: list[dict]) -> list[dict]: """ Clean the @rawstring field by replacing escaped '\\&' sequences with '&'. The NGSIEM API may return @rawstring values containing '\\&' (literal backslash + ampersand). This replacement ensures the string is clean before the whole event is later serialized with json.dumps. """ for event in events: raw = event.get("@rawstring") if isinstance(raw, str) and "\\&" in raw: event["@rawstring"] = raw.replace("\\&", "&") return events def build_ngsiem_query_with_limit(query: str, limit: int) -> str: """ Add tail() function to query if not already present to limit results. The default number of events returned for each API call is 200, unless the 'tail' function is used. Args: query: The original query string. limit: Maximum number of events to return. Returns: str: Query with tail() function appended if needed. """ if "tail(" not in query.lower(): return f"{query} | tail({limit})" return query def arg_to_timestamp(val: Any) -> Optional[int]: """Converts a value to an epoch-milliseconds timestamp using ``arg_to_datetime``. Returns ``None`` for empty/None values, otherwise an ``int`` (epoch ms). """ if not val: return None dt = arg_to_datetime(val) return int(dt.timestamp() * 1000) if dt else None def build_ngsiem_search_body(args: dict) -> dict: """ Build the request body for NGSIEM search. Args: args: Command arguments. Returns: dict: The request body. """ query = args.get("query", "") around_config = assign_params( eventId=args.get("around_event_id"), numberOfEventsBefore=arg_to_number(args.get("around_number_events_before")), numberOfEventsAfter=arg_to_number(args.get("around_number_events_after")), timestamp=arg_to_timestamp(args.get("around_timestamp")), ) if not around_config.get("numberOfEventsBefore") and not around_config.get("numberOfEventsAfter"): # If an "around" is used (around_number_events_before/after), adding `limit` would override/ignore the config, # so we only set `limit` when "around" is not used. limit = arg_to_number(args.get("limit")) or 50 query = build_ngsiem_query_with_limit(query, limit) body = assign_params( queryString=query, start=arg_to_timestamp(args.get("start")), end=arg_to_timestamp(args.get("end")), ingestStart=arg_to_timestamp(args.get("ingest_start")), ingestEnd=arg_to_timestamp(args.get("ingest_end")), useIngestTime=argToBoolean(args.get("use_ingest_time")) if args.get("use_ingest_time") else None, around=around_config, ) return body def build_ngsiem_hr_rows(events: list[dict], hr_keys: list[str]) -> list[dict]: """ Build human-readable table rows from NGSIEM events. For each desired key, resolves the value by trying the bare key first, then falling back to the '@' and '#' prefixed variants. Converts epoch-ms timestamp values to ISO 8601 date strings. Args: events: The raw event dicts (not modified). hr_keys: The unprefixed keys to extract for display. Returns: list[dict]: A list of dicts ready for tableToMarkdown (HR only). """ hr_rows: list[dict] = [] for event in events: row: dict[str, Any] = {} for key in hr_keys: val = event.get(key) or event.get(f"@{key}") or event.get(f"#{key}") if key == "timestamp" and val is not None: try: if isinstance(val, (int | float)): val = timestamp_to_datestring(val) elif isinstance(val, str) and val.isdigit(): val = timestamp_to_datestring(int(val)) except Exception: demisto.debug(f"Failed to convert timestamp {val} to date string") row[key] = val hr_rows.append(row) return hr_rows def process_ngsiem_search_completion(response: dict, args: dict) -> PollResult: """ Process the completion of an NGSIEM search job. Args: response: The response from the search job. args: Command arguments. Returns: PollResult: The result of the polling. """ args["wait_for_result"] = False events = response.get("events", []) warnings = response.get("warnings", []) if warnings: demisto.debug(f"NGSIEM search completed with warnings: {warnings}") if events: events = clean_ngsiem_rawstring_field(events) demisto.debug(f"Returned {len(events)} results from NGSIEM search") hr_keys = ["id", "event_simpleName", "user.name", "host.hostname", "timestamp"] def header_transform(header: str) -> str: return header.replace("_", " ").replace(".", " ").title() hr = tableToMarkdown( name=f"NGSIEM Events (Total: {len(events)})", t=build_ngsiem_hr_rows(events, hr_keys), headerTransform=header_transform, headers=hr_keys, removeNull=True, ) else: demisto.debug("No events found matching the query.") hr = "No events found matching the query." command_results = CommandResults( outputs_prefix="CrowdStrike.NGSiemEvent", outputs=events, readable_output=hr, raw_response=response, ) return PollResult(response=command_results, continue_to_poll=False) @polling_function( "cs-falcon-search-ngsiem-events", poll_message="Searching NGSIEM events:", polling_arg_name="wait_for_result", interval=arg_to_number(demisto.args().get("interval_in_seconds", DEFAULT_INTERVAL)), timeout=arg_to_number(demisto.args().get("timeout_in_seconds", DEFAULT_TIMEOUT_NGSIEM_SEARCH)), ) def cs_falcon_search_ngsiem_events_command(args: dict) -> PollResult: """ Search NGSIEM historical events using polling. This command initiates a search query job and polls for results until complete. Query jobs must continue to be polled until complete. If a query job is still in progress, the response will show done as false. Args: args: Command arguments including query, repository, time range, etc. Returns: PollResult: Contains the search results or indicates to continue polling. """ job_id = args.get("job_id") repository = args.get("repository", "search-all") if not job_id: # First call - initiate the search job body = build_ngsiem_search_body(args) response = initiate_ngsiem_search_request(repository=repository, body=body) job_id = response.get("id") if not job_id: raise DemistoException(f"Failed to initiate NGSIEM search. Response: {response}") demisto.debug(f"NGSIEM search job initiated with {job_id=}") args["job_id"] = job_id # Poll for results response = get_ngsiem_search_results_request(repository, job_id) is_done = response.get("done", False) is_cancelled = response.get("cancelled", False) demisto.debug(f"NGSIEM search job status: {is_done=}, {is_cancelled=}") if is_cancelled: raise DemistoException(f"NGSIEM search job {job_id} was cancelled.") if is_done: return process_ngsiem_search_completion(response, args) demisto.info(f"NGSIEM search job {job_id} still in progress, continuing to poll...") return PollResult( response=CommandResults(readable_output=f"NGSIEM search job {job_id} still in progress, continuing to poll..."), continue_to_poll=True, args_for_next_run=args, ) def module_test(): try: get_token(new_token=True) except (ValueError, DemistoException, requests.exceptions.RequestException) as e: demisto.debug(f"test-module failed to obtain a token: {e}\n{traceback.format_exc()}") return ( "Connection Error: Failed to reach the CrowdStrike Falcon server. Verify that the Server URL parameter is" " correct, that the API credentials are valid, and that the server is reachable from your host" " (check network connectivity, DNS, and proxy settings)." ) if demisto.params().get("isFetch"): try: fetch_items(command="fetch-incidents") except ValueError: return "Error: Something is wrong with the filters you entered for the fetch incident, please try again." return "ok" def rtr_kill_process_command(args: dict) -> CommandResults: host_id = args.get("host_id") process_ids = remove_duplicates_from_list_arg(args, "process_ids") command_type = "kill" raw_response = [] host_ids = [host_id] offline = argToBoolean(args.get("queue_offline", False)) batch_id = init_rtr_batch_session(host_ids, offline) timeout = arg_to_number(args.get("timeout")) outputs = [] for process_id in process_ids: full_command = f"{command_type} {process_id}" response = execute_run_batch_write_cmd_with_timer(batch_id, command_type, full_command, timeout=timeout) outputs.extend(parse_rtr_command_response(response, host_ids, process_id=process_id)) raw_response.append(response) human_readable = tableToMarkdown( f"{INTEGRATION_NAME} {command_type} command on host {host_id}:", outputs, headers=["ProcessID", "Error"] ) human_readable += get_human_readable_for_failed_command(outputs, process_ids, "ProcessID") return CommandResults( raw_response=raw_response, readable_output=human_readable, outputs=outputs, outputs_prefix="CrowdStrike.Command.kill", outputs_key_field="ProcessID", ) def get_human_readable_for_failed_command(outputs, required_elements, element_id): failed_elements = {} for output in outputs: if output.get("Error") != "Success": failed_elements[output.get(element_id)] = output.get("Error") return add_error_message(failed_hosts=failed_elements, all_requested_hosts=required_elements) def parse_rtr_command_response(response, host_ids, process_id=None) -> list: outputs = [] resources: dict = response.get("combined", {}).get("resources", {}) for host_id, host_data in resources.items(): current_error = "" errors = host_data.get("errors") # API errors stderr = host_data.get("stderr") # host command error (as path does not exist and more) command_failed_with_error = errors or stderr # API errors are "stronger" that host stderr if command_failed_with_error: if errors: current_error = errors[0].get("message", "") elif stderr: current_error = stderr outputs_data = { "HostID": host_id, "Error": current_error if current_error else "Success", } if process_id: outputs_data.update({"ProcessID": process_id}) outputs.append(outputs_data) found_host_ids = {host.get("HostID") for host in outputs} not_found_host_ids = set(host_ids) - found_host_ids for not_found_host in not_found_host_ids: outputs.append( { "HostID": not_found_host, "Error": "The host ID was not found.", } ) return outputs def match_remove_command_for_os(operating_system, file_path): if operating_system == "Windows": return f"rm '{file_path}' --force" elif operating_system == "Linux" or operating_system == "Mac": return f"rm '{file_path}' -r -d" else: return "" def rtr_remove_file_command(args: dict) -> CommandResults: file_path = args.get("file_path") host_ids = remove_duplicates_from_list_arg(args, "host_ids") offline = argToBoolean(args.get("queue_offline", False)) operating_system = args.get("os") timeout = arg_to_number(args.get("timeout")) full_command = match_remove_command_for_os(operating_system, file_path) command_type = "rm" batch_id = init_rtr_batch_session(host_ids, offline) response = execute_run_batch_write_cmd_with_timer(batch_id, command_type, full_command, host_ids, timeout) outputs = parse_rtr_command_response(response, host_ids) human_readable = tableToMarkdown( f"{INTEGRATION_NAME} {command_type} over the file: {file_path}", outputs, headers=["HostID", "Error"] ) human_readable += get_human_readable_for_failed_command(outputs, host_ids, "HostID") return CommandResults( raw_response=response, readable_output=human_readable, outputs=outputs, outputs_prefix="CrowdStrike.Command.rm", outputs_key_field="HostID", ) def execute_run_batch_write_cmd_with_timer(batch_id, command_type, full_command, host_ids=None, timeout=None): """ Executes a timer for keeping the session refreshed """ timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id}) timer.start() try: response = run_batch_write_cmd( batch_id, command_type=command_type, full_command=full_command, optional_hosts=host_ids, timeout=timeout ) finally: timer.cancel() return response def execute_run_batch_admin_cmd_with_timer(batch_id, command_type, full_command, host_ids=None, timeout=None): timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id}) timer.start() try: response = run_batch_admin_cmd( batch_id, command_type=command_type, full_command=full_command, optional_hosts=host_ids, timeout=timeout ) finally: timer.cancel() return response def rtr_general_command_on_hosts( host_ids: list, command: str, full_command: str, get_session_function: Callable, write_to_context=True, offline=False, timeout=None, ) -> list[CommandResults | dict]: # type:ignore """ General function to run RTR commands depending on the given command. """ batch_id = init_rtr_batch_session(host_ids, offline) response = get_session_function(batch_id, command_type=command, full_command=full_command, host_ids=host_ids, timeout=timeout) # type:ignore output, file, not_found_hosts = parse_rtr_stdout_response(host_ids, response, command) human_readable = tableToMarkdown(f"{INTEGRATION_NAME} {command} command on host {host_ids[0]}:", output, headers="Stdout") human_readable += add_error_message(not_found_hosts, host_ids) if write_to_context: outputs = {"Filename": file[0].get("File")} return [ CommandResults( raw_response=response, readable_output=human_readable, outputs=outputs, outputs_prefix=f"CrowdStrike.Command.{command}", outputs_key_field="Filename", ), file, ] return [CommandResults(raw_response=response, readable_output=human_readable), file] def parse_rtr_stdout_response(host_ids, response, command, file_name_suffix=""): resources: dict = response.get("combined", {}).get("resources", {}) outputs = [] files = [] for host_id, resource in resources.items(): current_error = "" errors = resource.get("errors") stderr = resource.get("stderr") command_failed_with_error = errors or stderr if command_failed_with_error: if errors: current_error = errors[0].get("message", "") elif stderr: current_error = stderr return_error(current_error) stdout = resource.get("stdout", "") file_name = f"{command}-{host_id}{file_name_suffix}" outputs.append({"Stdout": stdout, "FileName": file_name}) files.append(fileResult(file_name, stdout)) not_found_hosts = set(host_ids) - resources.keys() return outputs, files, not_found_hosts def rtr_read_registry_keys_command(args: dict): host_ids = remove_duplicates_from_list_arg(args, "host_ids") offline = argToBoolean(args.get("queue_offline", False)) registry_keys = remove_duplicates_from_list_arg(args, "registry_keys") timeout = arg_to_number(args.get("timeout")) command_type = "reg" raw_response = [] batch_id = init_rtr_batch_session(host_ids, offline) outputs = [] files = [] not_found_hosts = set() for registry_key in registry_keys: full_command = f"{command_type} query {registry_key}" response = execute_run_batch_write_cmd_with_timer( batch_id, command_type, full_command, host_ids=host_ids, timeout=timeout ) output, file, not_found_host = parse_rtr_stdout_response(host_ids, response, command_type, file_name_suffix=registry_key) not_found_hosts.update(not_found_host) outputs.extend(output) files.append(file) raw_response.append(response) human_readable = tableToMarkdown(f"{INTEGRATION_NAME} {command_type} command on hosts {host_ids}:", outputs) human_readable += add_error_message(not_found_hosts, host_ids) return [CommandResults(raw_response=raw_response, readable_output=human_readable), files] def add_error_message(failed_hosts, all_requested_hosts): human_readable = "" if failed_hosts: if len(all_requested_hosts) == len(failed_hosts): raise DemistoException(f"{INTEGRATION_NAME} The command was failed with the errors: {failed_hosts}") human_readable = "Note: you don't see the following IDs in the results as the request was failed for them. \n" for host_id in failed_hosts: human_readable += f"ID {host_id} failed as it was not found. \n" return human_readable def rtr_polling_retrieve_file_command(args: dict): """ This function is generically handling the polling flow. In this case, the polling flow is: 1. run the "cs-falcon-run-get-command" command to get the request id. 2. run the "cs-falcon-status-get-command" command to get the status of the first "get" command by the request id. 2.1 start polling - wait for the 2nd step to be finished (when we get at least sha256 one time). 3. run the "cs-falcon-get-extracted-file" command to get the extracted file. Args: args: the arguments required to the command being called, under cmd Returns: The return value is: 1. All the extracted files. 2. A list of dictionaries. Each dict includes a host id and a file name. """ cmd = "cs-falcon-rtr-retrieve-file" ScheduledCommand.raise_error_if_not_supported() interval_in_secs = int(args.get("interval_in_seconds", 60)) if "hosts_and_requests_ids" not in args: # this is the very first time we call the polling function. We don't wont to call this function more that # one time, so we store that arg between the different runs offline = argToBoolean(args.get("queue_offline", False)) # run the first command to retrieve file args["hosts_and_requests_ids"] = run_get_command(is_polling=True, offline=offline) # we are here after we ran the cs-falcon-run-get-command command at the current run or in previous if not args.get("SHA256"): # this means that we don't have status yet (i.e we didn't get sha256) hosts_and_requests_ids = args.pop("hosts_and_requests_ids") args["request_ids"] = [res.get("RequestID") for res in hosts_and_requests_ids] get_status_response, args = status_get_command(args, is_polling=True) if args.get("SHA256"): # the status is ready, we can get the extracted files args.pop("SHA256") return rtr_get_extracted_file(get_status_response, args.get("filename")) # type:ignore else: # we should call the polling on status, cause the status is not ready args["hosts_and_requests_ids"] = hosts_and_requests_ids args.pop("request_ids") args.pop("SHA256") polling_timeout = arg_to_number(args.get("polling_timeout", 600)) scheduled_command = ScheduledCommand( command=cmd, next_run_in_seconds=interval_in_secs, args=args, timeout_in_seconds=polling_timeout ) command_results = CommandResults( scheduled_command=scheduled_command, readable_output="Waiting for the polling execution" ) return command_results return None def rtr_get_extracted_file(args_to_get_files: dict, file_name: str): files = [] outputs_data = [] for host_id, values in args_to_get_files.items(): arg = {"host_id": host_id, "sha256": values.get("SHA256"), "filename": file_name} file = get_extracted_file_command(arg) files.append(file) outputs_data.append({"HostID": arg.get("host_id"), "FileName": file.get("File")}) return [ CommandResults(readable_output="CrowdStrike Falcon files", outputs=outputs_data, outputs_prefix="CrowdStrike.File"), files, ] def build_url_filter(values: list[str] | str | None): return "cve.id:['" + "','".join(argToList(values)) + "']" def cs_falcon_spotlight_search_vulnerability_request( aid: list[str] | None, cve_id: list[str] | None, cve_severity: list[str] | None, tags: list[str] | None, status: list[str] | None, platform_name: str | None, host_group: list[str] | None, host_type: list[str] | None, last_seen_within: str | None, is_suppressed: str | None, filter_: str, remediation: bool | None, evaluation_logic: bool | None, host_info: bool | None, limit: str | None, next_token: str | None = None, ) -> dict: input_arg_dict = { "aid": aid, "cve.id": cve_id, "host_info.tags": tags, "status": status, "host_info.groups": host_group, "last_seen_within": last_seen_within, "suppression_info.is_suppressed": is_suppressed, } input_arg_dict["cve.severity"] = [severity.upper() for severity in cve_severity] if cve_severity else None input_arg_dict["host_info.platform_name"] = platform_name.capitalize() if platform_name else None input_arg_dict["host_info.product_type_desc"] = [host_type_.capitalize() for host_type_ in host_type] if host_type else None remove_nulls_from_dictionary(input_arg_dict) # In Falcon Query Language, '+' (after decode '%2B) stands for AND and ',' for OR # (https://falcon.crowdstrike.com/documentation/45/falcon-query-language-fql) url_filter = filter_.replace("+", "%2B") if not any((input_arg_dict, url_filter)): raise DemistoException("Please add a at least one filter argument") for key, arg in input_arg_dict.items(): if url_filter: url_filter += "%2B" if isinstance(arg, list): url_filter += f"{key}:['" + "','".join(arg) + "']" else: url_filter += f"{key}:'{arg}'" # All args should be a list. this is a fallback url_facet = "&facet=cve" for argument, url_value in ( ("remediation", remediation), ("evaluation_logic", evaluation_logic), ("host_info", host_info), ): if argToBoolean(url_value): url_facet += f"&facet={argument}" # The url is hardcoded since facet is a parameter that can have serval values, therefore we can't use a dict suffix_url = f"/spotlight/combined/vulnerabilities/v1?filter={url_filter}{url_facet}&limit={limit}" if next_token: suffix_url += f"&after={urllib.parse.quote(next_token, safe='')}" return http_request("GET", suffix_url) def cs_falcon_spotlight_list_host_by_vulnerability_request(cve_ids: list[str] | None, limit: str) -> dict: url_filter = build_url_filter(cve_ids) params = {"filter": url_filter, "facet": "host_info", "limit": limit} return http_request("GET", "/spotlight/combined/vulnerabilities/v1", params=params) def cve_request(cve_id: list[str] | None) -> dict: url_filter = build_url_filter(cve_id) return http_request("GET", "/spotlight/combined/vulnerabilities/v1", params={"filter": url_filter, "facet": "cve"}) def cs_falcon_spotlight_search_vulnerability_command(args: dict) -> list[CommandResults]: """Search Spotlight vulnerabilities with cursor-based pagination via ``next_token``. The pagination cursor returned by CrowdStrike (``meta.pagination.after``) is always emitted to the ``CrowdStrike.VulnerabilityNextToken`` context output when present, and never rendered in the human-readable war-room output. Args: args: Command arguments (filter, limit, next_token, etc.). Returns: list[CommandResults]: Bulk-data entry, followed by a cursor entry when the API returned a non-empty ``after`` cursor. """ next_token = args.get("next_token") limit = arg_to_number(args.get("limit", 50)) if limit is not None and limit > MAX_SPOTLIGHT_VULNERABILITY_PAGE_SIZE: limit = MAX_SPOTLIGHT_VULNERABILITY_PAGE_SIZE try: vulnerability_response = cs_falcon_spotlight_search_vulnerability_request( argToList(args.get("aid")), argToList(args.get("cve_id")), argToList(args.get("cve_severity")), argToList(args.get("tags")), argToList(args.get("status")), args.get("platform_name"), argToList(args.get("host_group")), argToList(args.get("host_type")), args.get("last_seen_within"), args.get("is_suppressed"), args.get("filter", ""), args.get("display_remediation_info"), args.get("display_evaluation_logic_info"), args.get("display_host_info"), str(limit), next_token, ) except DemistoException as exc: # Narrow intercept: only the expired-cursor case (HTTP 404). # The generic 400 "Invalid pagination token" is already self-explanatory # and is intentionally NOT caught here. if "Search context expired" in str(exc): return_error( "CrowdStrike Spotlight pagination cursor has expired " "(these cursors are short-lived, typically a few minutes). " "Please rerun the command without the next_token argument to start a fresh pagination session." ) raise headers = ["ID", "Severity", "Status", "Base Score", "Published Date", "Impact Score", "Exploitability Score", "Vector"] outputs = [] for vulnerability in vulnerability_response.get("resources", {}): outputs.append( { "ID": vulnerability.get("cve", {}).get("id"), "Severity": vulnerability.get("cve", {}).get("severity"), "Status": vulnerability.get("status"), "Base Score": vulnerability.get("cve", {}).get("base_score"), "Published Date": vulnerability.get("cve", {}).get("published_date"), "Impact Score": vulnerability.get("cve", {}).get("impact_score"), "Exploitability Score": vulnerability.get("cve", {}).get("exploitability_score"), "Vector": vulnerability.get("cve", {}).get("vector"), } ) human_readable = tableToMarkdown("List Vulnerabilities", outputs, removeNull=True, headers=headers) raw_after = vulnerability_response.get("meta", {}).get("pagination", {}).get("after") results: list[CommandResults] = [ CommandResults( outputs_prefix="CrowdStrike.Vulnerability", outputs_key_field="id", outputs=vulnerability_response.get("resources"), readable_output=human_readable, raw_response=vulnerability_response, ) ] if raw_after: results.append( CommandResults( outputs_prefix="CrowdStrike.VulnerabilityNextToken", outputs=raw_after, readable_output="Token for next page was generated and can be found under CrowdStrike.VulnerabilityNextToken", replace_existing=True, ) ) return results def cs_falcon_spotlight_list_host_by_vulnerability_command(args: dict) -> CommandResults: """ Get a list of vulnerability by spotlight : args: filter which include params or filter param. : return: a list of vulnerabilities according to the user. """ cve_ids = args.get("cve_ids") limit = args.get("limit", "50") vulnerability_response = cs_falcon_spotlight_list_host_by_vulnerability_request(cve_ids, limit) headers = [ "CVE ID", "hostname", "os Version", "Product Type Desc", "Local IP", "ou", "Machine Domain", "Site Name", "CVE Exploitability Score", "CVE Vector", ] outputs = [] for vulnerability in vulnerability_response.get("resources", {}): outputs.append( { "CVE ID": vulnerability.get("cve", {}).get("id"), "hostname": vulnerability.get("host_info", {}).get("hostname"), "os Version": vulnerability.get("host_info", {}).get("os_version"), "Product Type Desc": vulnerability.get("host_info", {}).get("product_type_desc"), "Local IP": vulnerability.get("host_info", {}).get("local_ip"), "ou": vulnerability.get("host_info", {}).get("ou"), "Machine Domain": vulnerability.get("host_info", {}).get("machine_domain"), "Site Name": vulnerability.get("host_info", {}).get("site_name"), } ) human_readable = tableToMarkdown("List Vulnerabilities For Host", outputs, removeNull=True, headers=headers) return CommandResults( raw_response=vulnerability_response, readable_output=human_readable, outputs=vulnerability_response.get("resources"), outputs_prefix="CrowdStrike.VulnerabilityHost", outputs_key_field="id", ) def get_cve_command(args: dict) -> list[dict[str, Any]]: """ Get a list of vulnerabilities by spotlight : args: filter which include params or filter param. : return: a list of cve indicators according to the user. """ cve = args.get("cve") or args.get("cve_id") if not cve: raise DemistoException('Please add a filter argument "cve".') command_results_list = [] http_response = cve_request(cve) raw_cve = [res_element.get("cve") for res_element in http_response.get("resources", [])] if not raw_cve: command_results_list = [(CommandResults(readable_output="No matching results found.")).to_context()] else: for cve in raw_cve: relationships_list = create_relationships(cve) cve_dbot_score = create_dbot_Score(cve=cve, reliability=args.get("Reliability", "A+ - 3rd party enrichment")) cve_indicator = Common.CVE( id=cve.get("id"), cvss="", published=cve.get("published_date"), modified="", description=cve.get("description"), cvss_score=cve.get("base_score"), cvss_vector=cve.get("vector"), dbot_score=cve_dbot_score, publications=create_publications(cve), relationships=relationships_list, ) cve_human_readable = { "ID": cve.get("id"), "Description": cve.get("description"), "Published Date": cve.get("published_date"), "Base Score": cve.get("base_score"), } human_readable = tableToMarkdown( "CrowdStrike Falcon CVE", cve_human_readable, headers=["ID", "Description", "Published Date", "Base Score"] ) command_results = CommandResults( raw_response=cve, readable_output=human_readable, relationships=relationships_list, indicator=cve_indicator ).to_context() if command_results not in command_results_list: command_results_list.append(command_results) return command_results_list def create_ml_exclusion_command(args: dict) -> CommandResults: """Creates a machine learning exclusion. Args: args: Arguments to create the exclusion from. Returns: The created exclusion meta data. """ create_args = assign_params( value=args.get("value"), excluded_from=argToList(args.get("excluded_from")), comment=args.get("comment"), groups=argToList(args.get("groups", "all")), ) exclusion = create_exclusion("ml", create_args).get("resources") human_readable = tableToMarkdown( "CrowdStrike Falcon machine learning exclusion", exclusion, sort_headers=False, headerTransform=underscoreToCamelCase, is_auto_json_transform=True, removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.MLExclusion", outputs_key_field="id", outputs=exclusion, readable_output=human_readable, ) def update_ml_exclusion_command(args: dict) -> CommandResults: """Updates a machine learning exclusion by providing an ID. Args: args: Arguments for updating the exclusion. Returns: The updated exclusion meta data. """ update_args = assign_params(value=args.get("value"), comment=args.get("comment"), groups=argToList(args.get("groups"))) if not update_args: raise Exception("At least one argument (besides the id argument) should be provided to update the exclusion.") update_args.update({"id": args.get("id")}) exclusion = update_exclusion("ml", update_args).get("resources") human_readable = tableToMarkdown( "CrowdStrike Falcon machine learning exclusion", exclusion, sort_headers=False, headerTransform=underscoreToCamelCase, is_auto_json_transform=True, removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.MLExclusion", outputs_key_field="id", outputs=exclusion, readable_output=human_readable, ) def delete_ml_exclusion_command(args: dict) -> CommandResults: """Delete a machine learning exclusion by providing an ID. Args: args: Arguments for deleting the exclusion (in particular only the id is needed). Returns: A message that the exclusion has been deleted. """ ids = argToList(args.get("ids")) delete_exclusion("ml", ids) return CommandResults(readable_output=f'The machine learning exclusions with IDs {" ".join(ids)} was successfully deleted.') def search_ml_exclusion_command(args: dict) -> CommandResults: """Searches machine learning exclusions by providing an ID / value / cusotm-filter. Args: args: Arguments for searching the exclusions. Returns: The exclusions meta data. """ if not (ids := argToList(args.get("ids"))): search_args = assign_params( sort=args.get("sort"), limit=args.get("limit"), offset=args.get("offset"), ) if value := args.get("value"): ids = get_exclusions("ml", f"value:'{value}'", search_args).get("resources") else: ids = get_exclusions("ml", args.get("filter"), search_args).get("resources") if not ids: return CommandResults(readable_output="The arguments/filters you provided did not match any exclusion.") exclusions = get_exclusion_entities("ml", ids).get("resources") human_readable = tableToMarkdown( "CrowdStrike Falcon machine learning exclusions", exclusions, sort_headers=False, headerTransform=underscoreToCamelCase, is_auto_json_transform=True, removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.MLExclusion", outputs_key_field="id", outputs=exclusions, readable_output=human_readable, ) def create_ioa_exclusion_command(args: dict) -> CommandResults: """Creates an IOA exclusion. Args: args: Arguments to create the exclusion from. Returns: The created exclusion meta data. """ create_args = assign_params( name=args.get("exclusion_name"), pattern_id=args.get("pattern_id"), pattern_name=args.get("pattern_name"), cl_regex=args.get("cl_regex"), ifn_regex=args.get("ifn_regex"), comment=args.get("comment"), description=args.get("description"), groups=argToList(args.get("groups", "all")), detection_json=args.get("detection_json"), ) exclusion = create_exclusion("ioa", create_args).get("resources") human_readable = tableToMarkdown( "CrowdStrike Falcon IOA exclusion", exclusion, is_auto_json_transform=True, headerTransform=underscoreToCamelCase, sort_headers=False, removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.IOAExclusion", outputs_key_field="id", outputs=exclusion, readable_output=human_readable, ) def update_ioa_exclusion_command(args: dict) -> CommandResults: """Updates an IOA exclusion by providing an ID. Args: args: Arguments for updating the exclusion. Returns: The updated exclusion meta data. """ update_args = assign_params( name=args.get("exclusion_name"), pattern_id=args.get("pattern_id"), pattern_name=args.get("pattern_name"), cl_regex=args.get("cl_regex"), ifn_regex=args.get("ifn_regex"), comment=args.get("comment"), description=args.get("description"), groups=argToList(args.get("groups")), detection_json=args.get("detection_json"), ) if not update_args: raise Exception("At least one argument (besides the id argument) should be provided to update the exclusion.") update_args.update({"id": args.get("id")}) exclusion = update_exclusion("ioa", update_args).get("resources") human_readable = tableToMarkdown( "CrowdStrike Falcon IOA exclusion", exclusion, is_auto_json_transform=True, headerTransform=underscoreToCamelCase, removeNull=True, sort_headers=False, ) return CommandResults( outputs_prefix="CrowdStrike.IOAExclusion", outputs_key_field="id", outputs=exclusion, readable_output=human_readable, ) def delete_ioa_exclusion_command(args: dict) -> CommandResults: """Delete an IOA exclusion by providing an ID. Args: args: Arguments for deleting the exclusion (in particular only the id is needed). Returns: A message that the exclusion has been deleted. """ ids = argToList(args.get("ids")) delete_exclusion("ioa", ids) return CommandResults(readable_output=f'The IOA exclusions with IDs {" ".join(ids)} was successfully deleted.') def search_ioa_exclusion_command(args: dict) -> CommandResults: """Searches IOA exclusions by providing an ID / name / cusotm-filter. Args: args: Arguments for searching the exclusions. Returns: The exclusions meta data. """ exclusion_name = args.get("name") if not (ids := argToList(args.get("ids"))): search_args = assign_params(limit=args.get("limit"), offset=args.get("offset")) if exclusion_name: ids = get_exclusions("ioa", f"name:~'{exclusion_name}'", search_args).get("resources") else: ids = get_exclusions("ioa", args.get("filter"), search_args).get("resources") if not ids: return CommandResults(readable_output="The arguments/filters you provided did not match any exclusion.") exclusions = get_exclusion_entities("ioa", ids).get("resources", []) if exclusion_name and exclusions: exclusions = list(filter(lambda x: x.get("name") == exclusion_name, exclusions)) human_readable = tableToMarkdown( "CrowdStrike Falcon IOA exclusions", exclusions, is_auto_json_transform=True, headerTransform=underscoreToCamelCase, removeNull=True, sort_headers=False, ) return CommandResults( outputs_prefix="CrowdStrike.IOAExclusion", outputs_key_field="id", outputs=exclusions, readable_output=human_readable, ) def list_quarantined_file_command(args: dict) -> CommandResults: """Get quarantine file metadata by specified IDs / custom-filter. Args: args: Arguments for searching the quarantine files. Returns: The quarantine files meta data. """ if not (ids := argToList(args.get("ids"))): pagination_args = assign_params(limit=args.get("limit", "50"), offset=args.get("offset")) search_args = assign_params( state=args.get("state"), sha256=argToList(args.get("sha256")), filename=argToList(args.get("filename")), hostname=argToList(args.get("hostname")), username=argToList(args.get("username")), ) ids = list_quarantined_files_id(args.get("filter"), search_args, pagination_args).get("resources") if not ids: return CommandResults(readable_output=NO_QUARANTINED_FILES_MSG) files = list_quarantined_files(ids).get("resources") if isinstance(files, list): for file in files: if isinstance(file, dict) and "composite_ids" in file: file["detect_ids"] = file.pop("composite_ids") human_readable = tableToMarkdown( "CrowdStrike Falcon Quarantined File", t=files, headers=QUARANTINE_FILES_OUTPUT_HEADERS, is_auto_json_transform=True, headerTransform=underscoreToCamelCase, sort_headers=False, removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.QuarantinedFile", outputs_key_field="id", outputs=files, readable_output=human_readable, ) def apply_quarantine_file_action_command(args: dict) -> CommandResults: """Apply action to quarantine file. Args: args: Arguments for searching and applying action to the quarantine files. Returns: The applied quarantined files meta data. """ if not (ids := argToList(args.get("ids"))): pagination_args = assign_params(limit=args.get("limit", "50"), offset=args.get("offset")) search_args = assign_params( state=args.get("state"), sha256=argToList(args.get("sha256")), filename=argToList(args.get("filename")), hostname=argToList(args.get("hostname")), username=argToList(args.get("username")), ) if not search_args: raise Exception( "At least one search argument (filename, hostname, sha256, state, username, ids, or filter)" " is required to update the quarantine file." ) ids = list_quarantined_files_id(args.get("filter"), search_args, pagination_args).get("resources") if not ids: # No matching quarantined files were found for the given search arguments/filter. # Returning a friendly message instead of letting the PATCH go out without ids, # which CrowdStrike rejects with HTTP 400 Validation error. return CommandResults(readable_output=NO_QUARANTINED_FILES_MSG) update_args = assign_params( ids=ids, action=args.get("action"), comment=args.get("comment"), ) if not update_args: raise Exception("At least one update argument (action, comment) should be provided to update the quarantine file.") apply_quarantined_files_action(update_args).get("resources") return CommandResults( readable_output=f"The Quarantined File with IDs {ids} was successfully updated.", ) def build_cs_falcon_filter(custom_filter: str | None = None, **filter_args) -> str: """Creates an FQL syntax filter from a dictionary and a custom built filter :custom_filter: custom filter from user (will take priority if conflicts with dictionary), defaults to None :filter_args: args to translate to FQL format. :return: FQL syntax filter. """ custom_filter_list = custom_filter.split("+") if custom_filter else [] arguments = [f"{key}:{argToList(value)}" for key, value in filter_args.items() if value] # custom_filter takes priority because it is first return "%2B".join(custom_filter_list + arguments) def ODS_query_scans_request(**query_params) -> dict: remove_nulls_from_dictionary(query_params) # http_request messes up the params, so they were put directly in the url: url_params = "&".join(f"{k}={v}" for k, v in query_params.items()) return http_request("GET", f"/ods/queries/scans/v1?{url_params}") def ODS_get_scans_by_id_request(ids: list[str]) -> dict: url_params = "&".join(f"ids={query_id}" for query_id in ids) return http_request("GET", f"/ods/entities/scans/v1?{url_params}") def map_scan_resource_to_UI(resource: dict) -> dict: output = { "ID": resource.get("id"), "Status": resource.get("status"), "Severity": resource.get("severity"), # Every host in resource.metadata has a "filecount" which is a dictionary # that counts the files traversed, skipped, found to be malicious and the like. "File Count": "\n-\n".join( "\n".join(f"{k}: {v}" for k, v in filecount.items()) for host in resource.get("metadata", []) if (filecount := host.get("filecount", {})) ), "Description": resource.get("description"), "Hosts/Host groups": resource.get("hosts") or resource.get("host_groups"), "Start time": resource.get("scan_started_on"), "End time": resource.get("scan_completed_on"), "Run by": resource.get("created_by"), } return output def ODS_get_scan_resources_to_human_readable(resources: list[dict]) -> str: human_readable = tableToMarkdown( "CrowdStrike Falcon ODS Scans", [map_scan_resource_to_UI(resource) for resource in resources], headers=[ "ID", "Status", "Severity", "File Count", "Description", "Hosts/Host groups", "End time", "Start time", "Run by", ], ) return human_readable def get_ODS_scan_ids(args: dict) -> list[str] | None: demisto.debug("Fetching IDs from query api") query_filter = build_cs_falcon_filter( custom_filter=args.get("filter"), initiated_from=args.get("initiated_from"), status=args.get("status"), severity=args.get("severity"), scan_started_on=args.get("scan_started_on"), scan_completed_on=args.get("scan_completed_on"), ) raw_response = ODS_query_scans_request( filter=query_filter, offset=args.get("offset"), limit=args.get("limit"), ) return raw_response.get("resources") @polling_function( "cs-falcon-ods-query-scan", poll_message="Retrieving scan results:", polling_arg_name="wait_for_result", interval=arg_to_number(dict_safe_get(demisto.args(), ["interval_in_seconds"], 0, (int, str))), timeout=arg_to_number(dict_safe_get(demisto.args(), ["timeout_in_seconds"], 0, (int, str))), ) def cs_falcon_ODS_query_scans_command(args: dict) -> PollResult: # call the query api if no ids given ids = argToList(args.get("ids")) or get_ODS_scan_ids(args) if not ids: command_results = CommandResults(readable_output="No scans match the arguments/filter.") scan_in_progress = False else: response = ODS_get_scans_by_id_request(ids) resources = response.get("resources", []) scan_in_progress = len(resources) == 1 and dict_safe_get(resources, [0, "status"]) in ("pending", "running") human_readable = ODS_get_scan_resources_to_human_readable(resources) command_results = CommandResults( raw_response=response, outputs_prefix="CrowdStrike.ODSScan", outputs_key_field="id", outputs=resources, readable_output=human_readable, ) return PollResult(response=command_results, continue_to_poll=scan_in_progress, args_for_next_run=args) def ODS_query_scheduled_scans_request(**query_params) -> dict: remove_nulls_from_dictionary(query_params) # http_request messes up the params, so they were put directly in the url: url_params = "&".join(f"{k}={v}" for k, v in query_params.items()) return http_request("GET", f"/ods/queries/scheduled-scans/v1?{url_params}") def ODS_get_scheduled_scans_by_id_request(ids: list[str]) -> dict: url_params = "&".join(f"ids={query_id}" for query_id in ids) return http_request("GET", f"/ods/entities/scheduled-scans/v1?{url_params}") def map_scheduled_scan_resource_to_UI(resource: dict) -> dict: output = { "ID": resource.get("id"), "Hosts targeted": len(resource.get("metadata", [])), "Description": resource.get("description"), "Host groups": resource.get("host_groups"), "Start time": resource.get("schedule", {}).get("start_timestamp"), "Created by": resource.get("created_by"), } return output def ODS_get_scheduled_scan_resources_to_human_readable(resources: list[dict]) -> str: human_readable = tableToMarkdown( "CrowdStrike Falcon ODS Scheduled Scans", [map_scheduled_scan_resource_to_UI(resource) for resource in resources], headers=["ID", "Hosts targeted", "Description", "Host groups", "Start time", "Created by"], ) return human_readable def get_ODS_scheduled_scan_ids(args: dict) -> list[str] | None: demisto.debug("Fetching IDs from query api") query_filter = build_cs_falcon_filter( **{ "custom_filter": args.get("filter"), "initiated_from": args.get("initiated_from"), "status": args.get("status"), "created_on": args.get("created_on"), "created_by": args.get("created_by"), "schedule.start_timestamp": args.get("start_timestamp"), "deleted": args.get("deleted"), } ) raw_response = ODS_query_scheduled_scans_request( filter=query_filter, offset=args.get("offset"), limit=args.get("limit"), ) return raw_response.get("resources") def cs_falcon_ODS_query_scheduled_scan_command(args: dict) -> CommandResults: # call the query api if no ids given ids = argToList(args.get("ids")) or get_ODS_scheduled_scan_ids(args) if not ids: return CommandResults(readable_output="No scheduled scans match the arguments/filter.") response = ODS_get_scheduled_scans_by_id_request(ids) resources = response.get("resources", []) human_readable = ODS_get_scheduled_scan_resources_to_human_readable(resources) command_results = CommandResults( raw_response=response, outputs_prefix="CrowdStrike.ODSScheduledScan", outputs_key_field="id", outputs=resources, readable_output=human_readable, ) return command_results def ODS_query_scan_hosts_request(**query_params) -> dict: remove_nulls_from_dictionary(query_params) # http_request messes up the params, so they were put directly in the url: url_params = "&".join(f"{k}={v}" for k, v in query_params.items()) return http_request("GET", f"/ods/queries/scan-hosts/v1?{url_params}") def ODS_get_scan_hosts_by_id_request(ids: list[str]) -> dict: url_params = "&".join(f"ids={query_id}" for query_id in ids) return http_request("GET", f"/ods/entities/scan-hosts/v1?{url_params}") def get_ODS_scan_host_ids(args: dict) -> list[str]: query_filter = build_cs_falcon_filter( custom_filter=args.get("filter"), host_id=args.get("host_ids"), scan_id=args.get("scan_ids"), status=args.get("status"), started_on=args.get("started_on"), completed_on=args.get("completed_on"), ) raw_response = ODS_query_scan_hosts_request( filter=query_filter, offset=args.get("offset"), limit=args.get("limit"), ) return raw_response.get("resources", []) def map_scan_host_resource_to_UI(resource: dict) -> dict: output = { "ID": resource.get("id"), "Scan ID": resource.get("scan_id"), "Host ID": resource.get("host_id"), "Filecount": resource.get("filecount"), "Status": resource.get("status"), "Severity": resource.get("severity"), "Started on": resource.get("started_on"), } return output def ODS_get_scan_hosts_resources_to_human_readable(resources: list[dict]) -> str: human_readable = tableToMarkdown( "CrowdStrike Falcon ODS Scan Hosts", [map_scan_host_resource_to_UI(resource) for resource in resources], headers=["ID", "Scan ID", "Host ID", "Filecount", "Status", "Severity", "Started on"], ) return human_readable def cs_falcon_ods_query_scan_host_command(args: dict) -> CommandResults: ids = get_ODS_scan_host_ids(args) if not ids: return CommandResults(readable_output="No hosts to display.") response = ODS_get_scan_hosts_by_id_request(ids) resources = response.get("resources", []) human_readable = ODS_get_scan_hosts_resources_to_human_readable(resources) command_results = CommandResults( raw_response=response, outputs_prefix="CrowdStrike.ODSScanHost", outputs_key_field="id", outputs=resources, readable_output=human_readable, ) return command_results def ODS_query_malicious_files_request(**query_params) -> dict: remove_nulls_from_dictionary(query_params) # http_request messes up the params, so they were put directly in the url: url_params = "&".join(f"{k}={v}" for k, v in query_params.items()) return http_request("GET", f"/ods/queries/malicious-files/v1?{url_params}") def ODS_get_malicious_files_by_id_request(ids: list[str]) -> dict: url_params = "&".join(f"ids={query_id}" for query_id in ids) return http_request("GET", f"/ods/entities/malicious-files/v1?{url_params}") def map_malicious_file_resource_to_UI(resource: dict) -> dict: output = { "ID": resource.get("id"), "Scan id": resource.get("scan_id"), "Filename": resource.get("filename"), "Hash": resource.get("hash"), "Severity": resource.get("severity"), "Last updated": resource.get("last_updated"), } return output def ODS_get_malicious_files_resources_to_human_readable(resources: list[dict]) -> str: human_readable = tableToMarkdown( "CrowdStrike Falcon ODS Malicious Files", [map_malicious_file_resource_to_UI(resource) for resource in resources], headers=["ID", "Scan id", "Filename", "Hash", "Severity", "Last updated"], ) return human_readable def get_ODS_malicious_files_ids(args: dict) -> list[str] | None: demisto.debug("Fetching IDs from query api") query_filter = build_cs_falcon_filter( custom_filter=args.get("filter"), host_id=args.get("host_ids"), scan_id=args.get("scan_ids"), filepath=args.get("file_paths"), filename=args.get("file_names"), hash=args.get("hash"), ) raw_response = ODS_query_malicious_files_request( filter=query_filter, offset=args.get("offset"), limit=args.get("limit"), ) return raw_response.get("resources") def cs_falcon_ODS_query_malicious_files_command(args: dict) -> CommandResults: # call the query api if no file_ids given ids = argToList(args.get("file_ids")) or get_ODS_malicious_files_ids(args) if not ids: return CommandResults(readable_output="No malicious files match the arguments/filter.") response = ODS_get_malicious_files_by_id_request(ids) resources = response.get("resources", []) human_readable = ODS_get_malicious_files_resources_to_human_readable(resources) command_results = CommandResults( raw_response=response, outputs_prefix="CrowdStrike.ODSMaliciousFile", outputs_key_field="id", outputs=resources, readable_output=human_readable, ) return command_results def make_create_scan_request_body(args: dict, is_scheduled: bool) -> dict: result = { "host_groups": argToList(args.get("host_groups")), "file_paths": argToList(args.get("file_paths")), "scan_exclusions": argToList(args.get("scan_exclusions")), "scan_inclusions": argToList(args.get("scan_inclusions")), "initiated_from": args.get("initiated_from"), "cpu_priority": CPU_UTILITY_STR_TO_INT_KEY_MAP.get(args.get("cpu_priority")), # type: ignore[arg-type] "description": args.get("description"), "quarantine": argToBoolean(args.get("quarantine")) if args.get("quarantine") is not None else None, "pause_duration": arg_to_number(args.get("pause_duration")), "sensor_ml_level_detection": arg_to_number(args.get("sensor_ml_level_detection")), "sensor_ml_level_prevention": arg_to_number(args.get("sensor_ml_level_prevention")), "cloud_ml_level_detection": arg_to_number(args.get("cloud_ml_level_detection")), "cloud_ml_level_prevention": arg_to_number(args.get("cloud_ml_level_prevention")), "cloud_pup_adware_level_detection": arg_to_number(args.get("cloud_pup_adware_level_detection")), "cloud_pup_adware_level_prevention": arg_to_number(args.get("cloud_pup_adware_level_prevention")), "max_duration": arg_to_number(args.get("max_duration")), } if is_scheduled: result["schedule"] = { "interval": SCHEDULE_INTERVAL_STR_TO_INT.get(args["schedule_interval"].lower()), "start_timestamp": ( dateparser.parse(args["schedule_start_timestamp"]) or return_error("Invalid start_timestamp.") ).strftime("%Y-%m-%dT%H:%M"), } else: result["hosts"] = argToList(args.get("hosts")) return result def ODS_create_scan_request(args: dict, is_scheduled: bool) -> dict: body = make_create_scan_request_body(args, is_scheduled) remove_nulls_from_dictionary(body) return http_request("POST", f'/ods/entities/{"scheduled-" * is_scheduled}scans/v1', json=body) def ODS_verify_create_scan_command(args: dict) -> None: if not (args.get("hosts") or args.get("host_groups")): raise DemistoException("MUST set either hosts OR host_groups.") if not (args.get("file_paths") or args.get("scan_inclusions")): raise DemistoException("MUST set either file_paths OR scan_inclusions.") def ods_create_scan(args: dict, is_scheduled: bool) -> dict: ODS_verify_create_scan_command(args) response = ODS_create_scan_request(args, is_scheduled) resource = dict_safe_get(response, ("resources", 0), return_type=dict, raise_return_type=False) if not (resource and resource.get("id")): raise DemistoException("Unexpected response from CrowdStrike Falcon") return resource def cs_falcon_ods_create_scan_command(args: dict) -> CommandResults: resource = ods_create_scan(args, is_scheduled=False) scan_id = resource.get("id") polling = argToBoolean(args.get("polling", True)) if not polling: human_readable = f"Successfully created scan with ID: {scan_id}" return CommandResults( raw_response=resource, outputs_prefix="CrowdStrike.ODSScan", outputs_key_field="id", outputs=resource, readable_output=human_readable, ) query_scan_args = { "ids": scan_id, "wait_for_result": True, "interval_in_seconds": args.get("interval_in_seconds"), "timeout_in_seconds": args.get("timeout_in_seconds"), } return cs_falcon_ODS_query_scans_command(query_scan_args) def cs_falcon_ods_create_scheduled_scan_command(args: dict) -> CommandResults: resource = ods_create_scan(args, is_scheduled=True) human_readable = f'Successfully created scheduled scan with ID: {resource.get("id")}' command_results = CommandResults( raw_response=resource, outputs_prefix="CrowdStrike.ODSScheduledScan", outputs_key_field="id", outputs=resource, readable_output=human_readable, ) return command_results def ODS_delete_scheduled_scans_request(ids: list[str], scan_filter: str | None = None) -> dict: ids_params = [f"ids={scan_id}" for scan_id in ids] filter_param = [f'filter={scan_filter.replace("+", "%2B")}'] if scan_filter else [] url_params = "&".join(ids_params + filter_param) return http_request("DELETE", f"/ods/entities/scheduled-scans/v1?{url_params}", status_code=500) def cs_falcon_ods_delete_scheduled_scan_command(args: dict) -> CommandResults: ids, scan_filter = argToList(args.get("ids")), args.get("filter") response = ODS_delete_scheduled_scans_request(ids, scan_filter) if dict_safe_get(response, ["errors", 0, "code"]) == 500: raise DemistoException( 'CS Falcon returned an error.\n' 'Code: 500\n' f'Message: {dict_safe_get(response, ["errors", 0, "message"])}\n' 'Perhaps there are no scans to delete?' ) human_readable = tableToMarkdown("Deleted Scans:", response.get("resources", []), headers=["Scan ID"]) command_results = CommandResults( raw_response=response, readable_output=human_readable, ) return command_results def list_identity_entities_command(args: dict) -> CommandResults: """List identity entities Args: args: The demisto.args() dict object. Returns: The command result object. """ client = create_gql_client() args_keys_ls = ["sort_key", "sort_order", "max_risk_score_severity", "min_risk_score_severity"] ls_args_keys_ls = ["type", "entity_id", "primary_display_name", "secondary_display_name", "email"] variables = {} for key in args_keys_ls: if key in args: variables[key] = args.get(key) for key in ls_args_keys_ls: if key in args: variables[key] = args.get(key, "").split(",") if "enabled" in args: variables["enabled"] = argToBoolean(args.get("enabled")) idp_query = gql(""" query ($sort_key: EntitySortKey, $type: [EntityType!], $sort_order: SortOrder, $entity_id: [UUID!], $primary_display_name: [String!], $secondary_display_name: [String!], $max_risk_score_severity: ScoreSeverity, $min_risk_score_severity: ScoreSeverity, $enabled: Boolean, $email: [String!], $first: Int, $after: Cursor) { entities(types: $type, sortKey: $sort_key, sortOrder: $sort_order, entityIds: $entity_id, enabled: $enabled, primaryDisplayNames: $primary_display_name, secondaryDisplayNames: $secondary_display_name, maxRiskScoreSeverity: $max_risk_score_severity, minRiskScoreSeverity: $min_risk_score_severity, emailAddresses: $email, first: $first, after: $after) { pageInfo{ hasNextPage endCursor } nodes{ primaryDisplayName secondaryDisplayName isHuman:hasRole(type: HumanUserAccountRole) isProgrammatic:hasRole(type: ProgrammaticUserAccountRole) ... on UserEntity{ emailAddresses } riskScore riskScoreSeverity riskFactors{ type severity } } } } """) identity_entities_ls = [] next_token = args.get("next_token", "") limit = arg_to_number(args.get("limit", "50")) or 50 page = arg_to_number(args.get("page", "0")) page_size = arg_to_number(args.get("page_size", "50")) res_ls = [] has_next_page = True if page: variables["first"] = page_size while has_next_page and page: if next_token: variables["after"] = next_token res = client.execute(idp_query, variable_values=variables) res_ls.append(res) page -= 1 pageInfo = res.get("entities", {}).get("pageInfo", {}) has_next_page = pageInfo.get("hasNextPage", False) if page == 0: identity_entities_ls.extend(res.get("entities", {}).get("nodes", [])) if has_next_page: next_token = pageInfo.get("endCursor", "") else: while has_next_page and limit > 0: variables["first"] = min(1000, limit) if next_token: variables["after"] = next_token res = client.execute(idp_query, variable_values=variables) res_ls.append(res) pageInfo = res.get("entities", {}).get("pageInfo", {}) has_next_page = pageInfo.get("hasNextPage", False) identity_entities_ls.extend(res.get("entities", {}).get("nodes", [])) if has_next_page: next_token = pageInfo.get("endCursor", "") limit -= 1000 headers = [ "primaryDisplayName", "secondaryDisplayName", "isHuman", "isProgrammatic", "isAdmin", "emailAddresses", "riskScore", "riskScoreSeverity", "riskFactors", ] return CommandResults( outputs_prefix="CrowdStrike.IDPEntity", outputs=createContext(response_to_context(identity_entities_ls), removeNull=True), readable_output=tableToMarkdown( "Identity entities", identity_entities_ls, headers=headers, removeNull=True, headerTransform=pascalToSpace ), raw_response=res_ls, ) def create_gql_client(url_suffix="identity-protection/combined/graphql/v1"): """ Creates a gql client to handle the gql requests. Args: url_suffix: The url suffix for the request. Returns: The created client. """ url_suffix = url_suffix["url"][1:] if url_suffix.startswith("/") else url_suffix kwargs = { "url": f"{SERVER}/{url_suffix}", "verify": USE_SSL, "retries": 10, "headers": {"Authorization": f"Bearer {get_token()}", "Accept": "application/json", "Content-Type": "application/json"}, } transport = RequestsHTTPTransport(**kwargs) # type: ignore[arg-type] client = Client( transport=transport, fetch_schema_from_transport=False, ) return client def cspm_list_policy_details_request(policy_ids: list[str]) -> dict[str, Any]: """Do an API call to retrieve policy details. Args: policy_ids (list[str]): The policy ids. Returns: dict[str, Any]: The raw response of the API. """ query_params = "&".join(f"ids={policy_id}" for policy_id in policy_ids) # Status codes of 500 and 400 are sometimes returned when the policy IDs given do not exist, therefore we want # to catch this case so we can return a proper message to the user # Status code of 207 is returned when the API returns data about the policy IDs that were found, # and an error for the policy IDs that were not found, in the same response return http_request( method="GET", url_suffix="/settings/entities/policy-details/v1", params=query_params, status_code=[500, 400, 207] ) def cs_falcon_cspm_list_policy_details_command(args: dict[str, Any]) -> CommandResults: """Command to list policy details. Args: args (dict[str, Any]): The arguments of the command Raises: DemistoException: If a status code of 500 is returned. Returns: CommandResults: The command results object. """ policy_ids = argToList(args.get("policy_ids")) raw_response = cspm_list_policy_details_request(policy_ids=policy_ids) # The API returns errors in the form of a list, under the key 'errors' if errors := raw_response.get("errors", []): if errors[0].get("code", "") == 500: raise DemistoException( 'CS Falcon CSPM returned an error.\n' 'Code: 500\n' f'Message: {dict_safe_get(raw_response, ["errors", 0, "message"])}\n' 'Perhaps the policy IDs are invalid?' ) for error in errors: if error.get("code") == 400: return_warning( f'CS Falcon CSPM returned an error.\nCode: {error.get("code")}\nMessage: {error.get("message")}\n' ) if resources := raw_response.get("resources", []): human_readable = tableToMarkdown( "CSPM Policy Details:", resources, headers=[ "ID", "description", "policy_statement", "policy_remediation", "cloud_service_subtype", "cloud_platform_type", "cloud_service_type", "default_severity", "policy_type", "tactic", "technique", ], headerTransform=string_to_table_header, ) return CommandResults( readable_output=human_readable, outputs=resources, outputs_key_field="ID", outputs_prefix="CrowdStrike.CSPMPolicy", raw_response=raw_response, ) return CommandResults(readable_output="No policy details were found for the given policy IDs.") def cspm_list_service_policy_settings_request(policy_id: str, cloud_platform: str, service: str) -> dict[str, Any]: """Do an API call to retrieve the policy settings. Args: policy_id (str): The policy ID. cloud_platform (str): The cloud platform to filter by. service (str): The service type to filter by. Returns: dict[str, Any]: The raw response of the API. """ query_params: dict[str, Any] = assign_params(service=service) if policy_id: query_params["policy-id"] = policy_id if cloud_platform: query_params["cloud-platform"] = cloud_platform return http_request(method="GET", url_suffix="/settings/entities/policy/v1", params=query_params, status_code=[207]) def cs_falcon_cspm_list_service_policy_settings_command(args: dict[str, Any]) -> CommandResults: """Command to list service policy settings. Args: args (dict[str, Any]): The arguments of the command. Returns: CommandResults: The command results object. """ policy_id = args.get("policy_id", "") cloud_platform = args.get("cloud_platform", "") service = args.get("service", "") limit = arg_to_number(args.get("limit")) or 50 raw_response = cspm_list_service_policy_settings_request(policy_id=policy_id, cloud_platform=cloud_platform, service=service) if resources := raw_response.get("resources", []): # The API does not support pagination, therefore we have to do it manually paginated_resources = resources[:limit] human_readable = tableToMarkdown( "CSPM Policy Settings:", paginated_resources, headers=[ "policy_id", "is_remediable", "remediation_summary", "name", "policy_type", "cloud_service_subtype", "cloud_service", "default_severity", ], headerTransform=string_to_table_header, ) return CommandResults( readable_output=human_readable, outputs=paginated_resources, outputs_key_field="policy_id", outputs_prefix="CrowdStrike.CSPMPolicySetting", raw_response=raw_response, ) return CommandResults(readable_output="No policy settings were found for the given arguments.") def cspm_update_policy_settings_request( account_id: str, enabled: bool, policy_id: int, regions: list[str], severity: str, tag_excluded: bool | None ) -> dict[str, Any]: """Do an API call to update the policy settings. Args: account_id (str): The account ID. enabled (bool): Whether to enable the policy or not. policy_id (int): The policy ID. regions (list[str]): The regions of the policy. severity (str): The severity of the policy. tag_excluded (bool | None): Whether to exclude tag or not. Returns: dict[str, Any]: The raw response of the API. """ # https://assets.falcon.crowdstrike.com/support/api/swagger.html#/cspm-registration/UpdateCSPMPolicySettings # You have to be logged into https://falcon.crowdstrike.com/ resources_body: dict[str, Any] = { "resources": [ assign_params( account_id=account_id, enabled=enabled, policy_id=policy_id, regions=regions, severity=severity, tag_excluded=tag_excluded, ) ] } return http_request(method="PATCH", url_suffix="/settings/entities/policy/v1", json=resources_body, status_code=500) def cs_falcon_cspm_update_policy_settings_command(args: dict[str, Any]) -> CommandResults: """Command to update policy settings. Args: args (dict[str, Any]): The arguments of the command. Raises: DemistoException: If the policy ID is not an integer. DemistoException: If a status code 500 is returned. Returns: CommandResults: The command results object. """ account_id = args.get("account_id", "") enabled = argToBoolean(args.get("enabled", "true")) policy_id = arg_to_number(args.get("policy_id")) if policy_id is None: raise DemistoException("policy_id must be an integer") regions = argToList(args.get("regions", [])) severity = args.get("severity", "") tag_excluded = args.get("tag_excluded") tag_excluded = argToBoolean(tag_excluded) if tag_excluded else tag_excluded raw_response = cspm_update_policy_settings_request( account_id=account_id, enabled=enabled, policy_id=policy_id, regions=regions, severity=severity, tag_excluded=tag_excluded ) if (errors := raw_response.get("errors", [])) and errors[0].get("code", "") == 500: raise DemistoException( 'CS Falcon CSPM returned an error.\n' 'Code: 500\n' f'Message: {dict_safe_get(raw_response, ["errors", 0, "message"])}\n' 'Perhaps the policy ID or account ID are invalid?' ) return CommandResults(readable_output=f"Policy {policy_id} was updated successfully") def resolve_detections_prepare_body_request(ids: list[str], action_params_values: dict[str, Any]) -> dict[str, Any]: """Create the body of the request to resolve detections. Args: ids (list[str]): The IDs of the detections. action_params_values (dict[str, Any]): A dictionary that holds key-value pairs corresponding to the action_parameters object of the API request. Returns: dict[str, Any]: The body of the request. """ # Values need to be in the form {'name': name_of_key, 'value': value_of_key}, as can be seen here # https://assets.falcon.crowdstrike.com/support/api/swagger.html#/Alerts/PatchEntitiesAlertsV2 # Implemented the same as: # https://github.com/CrowdStrike/falconpy/blob/main/src/falconpy/_payload/_alerts.py#L40 action_params = [] for key, value in action_params_values.items(): if value: param = {"name": key, "value": value} action_params.append(param) ids_request_key = "composite_ids" return {"action_parameters": action_params, ids_request_key: ids} def resolve_detections_request(ids: list[str], **kwargs) -> dict[str, Any]: """Do an API call to resolve detections. Args: ids (list[str]): The IDs of the detections. Returns: dict[str, Any]: The raw response of the API. """ url_suffix = "/alerts/entities/alerts/v3" body_payload = resolve_detections_prepare_body_request(ids=ids, action_params_values=kwargs) demisto.debug(f"In resolve_detections: {url_suffix=}, {body_payload=} ") return http_request(method="PATCH", url_suffix=url_suffix, json=body_payload) def cs_falcon_resolve_identity_detection(args: dict[str, Any]) -> CommandResults: """Command to resolve identity detections. Args: args (dict[str, Any]): The arguments of the command. Returns: CommandResults: The command results object. """ return handle_resolve_detections(args, "IDP Detection(s) {} were successfully updated") def cs_falcon_resolve_mobile_detection(args: dict[str, Any]) -> CommandResults: """Command to resolve mobile detections. Args: args (dict[str, Any]): The arguments of the command. Returns: CommandResults: The command results object. """ return handle_resolve_detections(args, "Mobile Detection(s) {} were successfully updated") def handle_resolve_detections(args: dict[str, Any], hr_template: str) -> CommandResults: """Handle the mobile & identity detections resolve commands. Args: args (dict[str, Any]): The arguments of the command. Returns: CommandResults: The command results object. """ ids = argToList(args.get("ids", "")) or [] update_status = args.get("update_status", "") assign_to_name = args.get("assign_to_name", "") assign_to_uuid = args.get("assign_to_uuid", "") assign_to_user_id = args.get("assign_to_user_id", "") # This argument is sent to the API in the form of a string, having the values 'true' or 'false' unassign = args.get("unassign", "") append_comment = args.get("append_comment", "") add_tag = args.get("add_tag", "") remove_tag = args.get("remove_tag", "") # This argument is sent to the API in the form of a string, having the values 'true' or 'false' show_in_ui = args.get("show_in_ui", "") # We pass the arguments in the form of **kwargs, since we also need the arguments' names for the API, # and it easier to achieve that using **kwargs if sum(map(bool, [assign_to_uuid, assign_to_name, assign_to_user_id])) > 1: raise ValueError("Only one of the arguments assign_to_uuid, assign_to_name, assign_to_user_id should be provided.") resolve_detections_request( ids=ids, update_status=update_status, assign_to_name=assign_to_name, assign_to_uuid=assign_to_uuid, assign_to_user_id=assign_to_user_id, unassign=unassign, append_comment=append_comment, add_tag=add_tag, remove_tag=remove_tag, show_in_ui=show_in_ui, ) return CommandResults(readable_output=hr_template.format(", ".join(ids))) def cs_falcon_list_users_command(args: dict[str, Any]) -> CommandResults: users_ids = argToList(args.get("id")) offset = arg_to_number(args.get("offset")) or 0 limit = arg_to_number(args.get("limit")) or 50 query_filter = args.get("filter") if not users_ids: users_api_response = get_users(offset=offset, limit=limit, query_filter=query_filter) users_ids = users_api_response.get("resources", []) if not users_ids: return CommandResults(readable_output="No matching results found.") users_data_api_response = get_users_data(user_ids=users_ids) users_data = users_data_api_response.get("resources", []) def table_headers_transformer(header: str) -> str: mapping = { "uuid": "UUID", "first_name": "First Name", "last_name": "Last Name", "uid": "E-Mail (UID)", "last_login_at": "Last Login", } return mapping.get(header, header) return CommandResults( outputs_prefix="CrowdStrike.Users", outputs_key_field="uuid", outputs=users_data, readable_output=tableToMarkdown( name="CrowdStrike Users", t=users_data, headers=["uuid", "first_name", "last_name", "uid", "last_login_at"], headerTransform=table_headers_transformer, sort_headers=False, ), raw_response=users_data_api_response, ) def get_ioarules_command(args: dict) -> CommandResults: rule_ids = argToList(args["rule_ids"]) ioarules_response_data = get_ioarules(rule_ids) ioarules = ioarules_response_data.get("resources", []) return CommandResults( outputs_prefix="CrowdStrike.IOARules", outputs_key_field="instance_id", outputs=ioarules, readable_output=tableToMarkdown( name="CrowdStrike IOA Rules", t=ioarules, headers=["instance_id", "description", "enabled", "name", "pattern_id"], headerTransform=string_to_table_header, removeNull=True, sort_headers=False, ), raw_response=ioarules_response_data, ) def list_workflow_definitions_command(args: dict[str, Any]) -> CommandResults: """ Lists workflow definitions from CrowdStrike Falcon. Builds an FQL filter from convenience arguments and calls the API. """ if raw_filter := args.get("filter"): # If explicit filter is provided, use it exclusively (ignore convenience args) filter_query = raw_filter else: # Build FQL filter from convenience arguments filter_parts: list[str] = [] if definition_id := args.get("definition_id"): filter_parts.append(f"id:'{definition_id}'") if activity_id := args.get("activity_id"): filter_parts.append(f"activity_id:'{activity_id}'") if name := args.get("name"): filter_parts.append(f"name:~'{name}'") if description := args.get("description"): filter_parts.append(f"description:~'{description}'") filter_query = "+".join(filter_parts) demisto.debug(f"[Workflow] list_workflow_definitions_command: built {filter_query=}") offset = args.get("offset", "0") limit = arg_to_number(args.get("limit", 50)) or 50 sort = args.get("sort", "") response = list_workflow_definitions(filter_query=filter_query, offset=offset, limit=limit, sort=sort) definitions = response.get("resources", []) demisto.debug(f"[Workflow] list_workflow_definitions_command: found {len(definitions)} definitions") # Build human-readable table hr_data = [] for definition in definitions: trigger = definition.get("trigger", {}) hr_data.append( { "Definition ID": definition.get("id"), "Name": definition.get("name"), "Description": definition.get("description"), "Trigger Event": trigger.get("event"), "Trigger Name": trigger.get("name"), "Trigger Schedule": trigger.get("schedule"), "Trigger Type": trigger.get("type"), } ) readable_output = tableToMarkdown( name="Workflow Definitions", t=hr_data, headers=["Definition ID", "Name", "Description", "Trigger Event", "Trigger Name", "Trigger Schedule", "Trigger Type"], removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.WorkflowDefinition", outputs_key_field="id", outputs=definitions, readable_output=readable_output, raw_response=response, ) def workflow_execute_command(args: dict[str, Any]) -> CommandResults: """ Executes an on-demand workflow in CrowdStrike Falcon. Either definition_id or name must be provided. """ definition_id = argToList(args.get("definition_id")) name = args.get("name") if not definition_id and not name: raise DemistoException("Either 'definition_id' or 'name' must be provided.") execution_cid = argToList(args.get("execution_cid")) key = args.get("key") source_event_url = args.get("source_event_url") # body is mendatory in the http request - if not provided, we set to empty dict body = args.get("body", "{}") response = execute_workflow( definition_id=definition_id or None, name=name, execution_cid=execution_cid or None, key=key, source_event_url=source_event_url, body=body, ) resources = response.get("resources", []) demisto.debug(f"[Workflow] workflow_execute_command: got {len(resources)} resources") # Build human-readable output # The API returns resources as a list of execution ID strings, not dicts. hr_data = [{"Execution ID": resource} for resource in resources] readable_output = tableToMarkdown( name="Workflow Execution", t=hr_data, headers=["Execution ID"], removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.Workflow", outputs=resources, readable_output=readable_output, raw_response=response, ) def list_workflow_executions_command(args: dict[str, Any]) -> CommandResults: """ Lists workflow executions from CrowdStrike Falcon. Builds an FQL filter from convenience arguments and calls the API. """ if raw_filter := args.get("filter"): # If explicit filter is provided, use it exclusively (ignore convenience args) filter_query = raw_filter else: # Build FQL filter from convenience arguments filter_parts: list[str] = [] if definition_id := args.get("definition_id"): filter_parts.append(f"definition_id:'{definition_id}'") if definition_name := args.get("definition_name"): filter_parts.append(f"definition_name:~'{definition_name}'") if execution_id := args.get("execution_id"): filter_parts.append(f"id:'{execution_id}'") filter_query = "+".join(filter_parts) demisto.debug(f"[Workflow] list_workflow_executions_command: built {filter_query=}") offset = args.get("offset", "0") limit = arg_to_number(args.get("limit", 50)) or 50 sort = args.get("sort", "") response = list_workflow_executions(filter_query=filter_query, offset=offset, limit=limit, sort=sort) executions = response.get("resources", []) demisto.debug(f"[Workflow] list_workflow_executions_command: found {len(executions)} executions") # Build human-readable table from activities hr_data = [] for execution in executions: execution_id_val = execution.get("execution_id", execution.get("id")) activities = execution.get("activities", []) if activities: for activity in activities: hr_data.append( { "Execution ID": execution_id_val, "Activity Node ID": activity.get("node_id"), "Activity Start Timestamp": activity.get("start_timestamp"), "Activity End Timestamp": activity.get("end_timestamp"), "Activity Status": activity.get("status"), "Activity Name": activity.get("name"), "Activity Type": activity.get("type"), } ) else: hr_data.append({"Execution ID": execution_id_val}) readable_output = tableToMarkdown( name="Workflow Executions", t=hr_data, headers=[ "Execution ID", "Activity Node ID", "Activity Start Timestamp", "Activity End Timestamp", "Activity Status", "Activity Name", "Activity Type", ], removeNull=True, ) return CommandResults( outputs_prefix="CrowdStrike.Workflows.Execution", outputs_key_field="execution_id", outputs=executions, readable_output=readable_output, raw_response=response, ) def list_workflow_execution_results_command(args: dict[str, Any]) -> CommandResults: """ Gets detailed results for specific workflow executions. """ ids = argToList(args.get("ids")) response = get_workflow_execution_results(ids=ids) results = response.get("resources", []) demisto.debug(f"[Workflow] list_workflow_execution_results_command: got {len(results)} results") # Build human-readable table from nested activities hr_data = [] for result in results: execution_id = result.get("execution_id") activities = result.get("activities", []) for activity in activities: hr_data.append( { "Execution ID": execution_id, "Activity Node ID": activity.get("node_id"), "Activity Start Timestamp": activity.get("start_timestamp"), "Activity End Timestamp": activity.get("end_timestamp"), "Activity Status": activity.get("status"), "Activity ID": activity.get("id"), "Activity Name": activity.get("name"), "Activity Type": activity.get("type"), } ) readable_output = tableToMarkdown( name="Workflow Execution Results", t=hr_data, headers=[ "Execution ID", "Activity Node ID", "Activity Start Timestamp", "Activity End Timestamp", "Activity Status", "Activity ID", "Activity Name", "Activity Type", ], removeNull=True, ) # Append errors if any (e.g., some IDs not found) errors = response.get("errors", []) if errors: error_lines = ["\n**Errors:**"] for error in errors: error_code = error.get("code", "Unknown") error_message = error.get("message", "Unknown error") error_lines.append(f"- Code: {error_code}, Message: {error_message}") readable_output += "\n".join(error_lines) return CommandResults( outputs_prefix="CrowdStrike.Workflows.ExecutionResult", outputs_key_field="execution_id", outputs=results, readable_output=readable_output, raw_response=response, ) def workflow_execution_action_command(args: dict[str, Any]) -> CommandResults: """ Performs an action (cancel or resume) on one or more workflow executions. Handles partial success where some IDs are affected and others return errors. """ ids = argToList(args.get("ids")) action_name = args.get("action_name", "") if action_name not in ("cancel", "resume"): raise DemistoException(f"Invalid action_name '{action_name}'. Must be 'cancel' or 'resume'.") response = perform_workflow_execution_action(ids=ids, action_name=action_name) action_past_tense = "cancelled" if action_name == "cancel" else "resumed" # Extract resources_affected from meta.writes resources_affected = response.get("meta", {}).get("writes", {}).get("resources_affected", 0) # Extract errors if any errors = response.get("errors", []) # Calculate succeeded IDs error_ids = {error.get("id") for error in errors if error.get("id")} succeeded_ids = [id_ for id_ in ids if id_ not in error_ids] # Build readable output hr_parts: list[str] = [] if succeeded_ids: hr_parts.append(f"{len(succeeded_ids)} workflow execution(s) {action_past_tense}: {', '.join(succeeded_ids)}") else: hr_parts.append(f"0 workflow execution(s) {action_past_tense}.") if errors: hr_parts.append("\n**Errors:**") for error in errors: error_id = error.get("id", "Unknown") error_code = error.get("code", "Unknown") error_message = error.get("message", "Unknown error") hr_parts.append(f"- ID: {error_id} — Code: {error_code}, Message: {error_message}") readable_output = "\n".join(hr_parts) demisto.debug( f"[Workflow] workflow_execution_action_command: {action_name} completed, " f"{resources_affected} affected, {len(errors)} errors for {len(ids)} ids" ) return CommandResults( readable_output=readable_output, raw_response=response, ) def main(): # pragma: no cover command = demisto.command() args = demisto.args() demisto.debug(f"Command being called is {command}") try: if command == "test-module": result = module_test() return_results(result) elif command == "fetch-incidents": last_run, incidents = fetch_items(command=command) demisto.incidents(incidents) elif command == "fetch-events": last_run, events = fetch_items(command=command) send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) demisto.setLastRun(last_run) # Mirroring commands elif command == "get-remote-data": disable_for_xsiam() return_results(get_remote_data_command(args)) elif command == "get-modified-remote-data": disable_for_xsiam() return_results(get_modified_remote_data_command(args)) elif command == "update-remote-system": disable_for_xsiam() return_results(update_remote_system_command(args)) elif command == "get-mapping-fields": disable_for_xsiam() return_results(get_mapping_fields_command()) # ------- End Mirroring commands ---------- elif command in ("cs-device-ran-on", "cs-falcon-device-ran-on"): return_results(get_indicator_device_id()) elif demisto.command() == "cs-falcon-search-device": return_results(search_device_command()) elif command == "cs-falcon-get-behavior": demisto.results(get_behavior_command()) elif command == "cs-falcon-search-detection": return_results(search_detections_command()) elif command == "cs-falcon-resolve-detection": demisto.results(resolve_detection_command()) elif command == "cs-falcon-contain-host": demisto.results(contain_host_command()) elif command == "cs-falcon-lift-host-containment": demisto.results(lift_host_containment_command()) elif command == "cs-falcon-run-command": demisto.results(run_command()) elif command == "cs-falcon-upload-script": demisto.results(upload_script_command()) elif command == "cs-falcon-get-script": demisto.results(get_script_command()) elif command == "cs-falcon-delete-script": demisto.results(delete_script_command()) elif command == "cs-falcon-list-scripts": demisto.results(list_scripts_command()) elif command == "cs-falcon-upload-file": demisto.results(upload_file_command()) elif command == "cs-falcon-delete-file": demisto.results(delete_file_command()) elif command == "cs-falcon-get-file": demisto.results(get_file_command()) elif command == "cs-falcon-list-files": demisto.results(list_files_command()) elif command == "cs-falcon-run-script": demisto.results(run_script_command()) elif command == "cs-falcon-run-get-command": demisto.results(run_get_command()) elif command == "cs-falcon-status-get-command": demisto.results(status_get_command(args)) elif command == "cs-falcon-status-command": demisto.results(status_command()) elif command == "cs-falcon-get-extracted-file": demisto.results(get_extracted_file_command(args)) elif command == "cs-falcon-list-host-files": demisto.results(list_host_files_command()) elif command == "cs-falcon-refresh-session": demisto.results(refresh_session_command()) elif command == "cs-falcon-list-detection-summaries": return_results(list_detection_summaries_command()) elif command == "cs-falcon-list-case-summaries": return_results(list_case_summaries_command()) elif command == "cs-falcon-get-evidence-for-case": return_results(get_evidence_for_case_command(args)) elif command == "cs-falcon-search-iocs": return_results(search_iocs_command(**args)) elif command == "cs-falcon-get-ioc": return_results(get_ioc_command(ioc_type=args.get("type"), value=args.get("value"))) elif command == "cs-falcon-upload-ioc": return_results(upload_ioc_command(**args)) elif command == "cs-falcon-update-ioc": return_results(update_ioc_command(**args)) elif command == "cs-falcon-delete-ioc": return_results(delete_ioc_command(ioc_type=args.get("type"), value=args.get("value"))) elif command == "cs-falcon-search-custom-iocs": return_results(search_custom_iocs_command(**args)) elif command == "cs-falcon-get-custom-ioc": return_results(get_custom_ioc_command(ioc_type=args.get("type"), value=args.get("value"), ioc_id=args.get("ioc_id"))) elif command == "cs-falcon-upload-custom-ioc": return_results(upload_custom_ioc_command(**args)) elif command == "cs-falcon-update-custom-ioc": return_results(update_custom_ioc_command(**args)) elif command == "cs-falcon-delete-custom-ioc": return_results(delete_custom_ioc_command(ioc_id=args.get("ioc_id"))) elif command == "cs-falcon-device-count-ioc": return_results(get_ioc_device_count_command(ioc_type=args.get("type"), value=args.get("value"))) elif command == "cs-falcon-process-details": return_results(get_process_details_command(**args)) elif command == "cs-falcon-processes-ran-on": return_results( get_proccesses_ran_on_command(ioc_type=args.get("type"), value=args.get("value"), device_id=args.get("device_id")) ) elif command == "endpoint": return_results(get_endpoint_command()) elif command == "cs-falcon-create-host-group": return_results(create_host_group_command(**args)) elif command == "cs-falcon-update-host-group": return_results(update_host_group_command(**args)) elif command == "cs-falcon-list-host-groups": return_results(list_host_groups_command(**args)) elif command == "cs-falcon-delete-host-groups": return_results(delete_host_groups_command(host_group_ids=argToList(args.get("host_group_id")))) elif command == "cs-falcon-list-host-group-members": return_results(list_host_group_members_command(**args)) elif command == "cs-falcon-add-host-group-members": return_results( add_host_group_members_command(host_group_id=args.get("host_group_id"), host_ids=argToList(args.get("host_ids"))) ) elif command == "cs-falcon-remove-host-group-members": return_results( remove_host_group_members_command( host_group_id=args.get("host_group_id"), host_ids=argToList(args.get("host_ids")) ) ) elif command == "cs-falcon-batch-upload-custom-ioc": return_results(upload_batch_custom_ioc_command(**args)) elif command == "cs-falcon-rtr-kill-process": return_results(rtr_kill_process_command(args)) elif command == "cs-falcon-rtr-remove-file": return_results(rtr_remove_file_command(args)) elif command == "cs-falcon-rtr-list-processes": host_id = args.get("host_id") offline = argToBoolean(args.get("queue_offline", False)) timeout = arg_to_number(args.get("timeout")) return_results( rtr_general_command_on_hosts( [host_id], "ps", "ps", execute_run_batch_write_cmd_with_timer, True, offline, timeout=timeout ) ) elif command == "cs-falcon-rtr-list-network-stats": host_id = args.get("host_id") offline = argToBoolean(args.get("queue_offline", False)) timeout = arg_to_number(args.get("timeout")) return_results( rtr_general_command_on_hosts( [host_id], "netstat", "netstat", execute_run_batch_write_cmd_with_timer, True, offline, timeout=timeout ) ) elif command == "cs-falcon-rtr-read-registry": return_results(rtr_read_registry_keys_command(args)) elif command == "cs-falcon-rtr-list-scheduled-tasks": full_command = f"runscript -Raw=```schtasks /query /fo LIST /v```" # noqa: F541 host_ids = argToList(args.get("host_ids")) offline = argToBoolean(args.get("queue_offline", False)) timeout = arg_to_number(args.get("timeout")) return_results( rtr_general_command_on_hosts( host_ids, "runscript", full_command, execute_run_batch_admin_cmd_with_timer, offline, timeout=timeout ) ) elif command == "cs-falcon-rtr-retrieve-file": return_results(rtr_polling_retrieve_file_command(args)) elif command == "cs-falcon-spotlight-search-vulnerability": return_results(cs_falcon_spotlight_search_vulnerability_command(args)) elif command == "cs-falcon-spotlight-list-host-by-vulnerability": return_results(cs_falcon_spotlight_list_host_by_vulnerability_command(args)) elif command == "cve": return_results(get_cve_command(args)) elif command == "cs-falcon-create-ml-exclusion": return_results(create_ml_exclusion_command(args)) elif command == "cs-falcon-update-ml-exclusion": return_results(update_ml_exclusion_command(args)) elif command == "cs-falcon-delete-ml-exclusion": return_results(delete_ml_exclusion_command(args)) elif command == "cs-falcon-search-ml-exclusion": return_results(search_ml_exclusion_command(args)) elif command == "cs-falcon-create-ioa-exclusion": return_results(create_ioa_exclusion_command(args)) elif command == "cs-falcon-update-ioa-exclusion": return_results(update_ioa_exclusion_command(args)) elif command == "cs-falcon-delete-ioa-exclusion": return_results(delete_ioa_exclusion_command(args)) elif command == "cs-falcon-search-ioa-exclusion": return_results(search_ioa_exclusion_command(args)) elif command == "cs-falcon-list-quarantined-file": return_results(list_quarantined_file_command(args)) elif command == "cs-falcon-apply-quarantine-file-action": return_results(apply_quarantine_file_action_command(args)) elif command == "cs-falcon-ods-query-scan": return_results(cs_falcon_ODS_query_scans_command(args)) elif command == "cs-falcon-ods-query-scheduled-scan": return_results(cs_falcon_ODS_query_scheduled_scan_command(args)) elif command == "cs-falcon-ods-query-scan-host": return_results(cs_falcon_ods_query_scan_host_command(args)) elif command == "cs-falcon-ods-query-malicious-files": return_results(cs_falcon_ODS_query_malicious_files_command(args)) elif command == "cs-falcon-ods-create-scan": return_results(cs_falcon_ods_create_scan_command(args)) elif command == "cs-falcon-ods-create-scheduled-scan": return_results(cs_falcon_ods_create_scheduled_scan_command(args)) elif command == "cs-falcon-ods-delete-scheduled-scan": return_results(cs_falcon_ods_delete_scheduled_scan_command(args)) elif command == "cs-falcon-list-identity-entities": return_results(list_identity_entities_command(args)) # New commands elif command == "cs-falcon-cspm-list-policy-details": return_results(cs_falcon_cspm_list_policy_details_command(args=args)) elif command == "cs-falcon-cspm-list-service-policy-settings": return_results(cs_falcon_cspm_list_service_policy_settings_command(args=args)) elif command == "cs-falcon-cspm-update-policy_settings": return_results(cs_falcon_cspm_update_policy_settings_command(args=args)) elif command == "cs-falcon-resolve-identity-detection": return_results(cs_falcon_resolve_identity_detection(args=args)) elif command == "cs-falcon-resolve-mobile-detection": return_results(cs_falcon_resolve_mobile_detection(args=args)) elif command == "cs-falcon-list-users": return_results(cs_falcon_list_users_command(args=args)) elif command == "cs-falcon-get-ioarules": return_results(get_ioarules_command(args=args)) elif command == "fetch-assets": fetch_assets_command() elif command == "cs-falcon-list-cnapp-alerts": return_results(list_cnapp_alerts_command(args=args)) elif command == "cs-falcon-add-case-tag": return_results(add_case_tags_command(args)) elif command == "cs-falcon-delete-case-tag": return_results(delete_case_tags_command(args)) elif command == "cs-falcon-resolve-case": return_results(resolve_case_command(args)) elif command == "cs-falcon-search-ngsiem-events": return_results(cs_falcon_search_ngsiem_events_command(args)) elif command == "cs-falcon-list-workflow-definitions": return_results(list_workflow_definitions_command(args)) elif command == "cs-falcon-workflow-execute": return_results(workflow_execute_command(args)) elif command == "cs-falcon-list-workflow-executions": return_results(list_workflow_executions_command(args)) elif command == "cs-falcon-list-workflow-execution-results": return_results(list_workflow_execution_results_command(args)) elif command == "cs-falcon-workflow-execution-action": return_results(workflow_execution_action_command(args)) else: raise NotImplementedError(f"CrowdStrike Falcon error: command {command} is not implemented") except Exception as e: return_error(f"Failed to execute {command!r} command.\nError:\n{e!s}") if __name__ in ("__main__", "builtin", "builtins"): main()