Cybereason
Endpoint detection and response to manage and query malops, connections and processes.
Endpoint · Cybereason
Details
| ID | Cybereason |
|---|---|
| Provider | LevelBlue |
| Category | Endpoint |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Endpoint detection and response to manage and query malops, connections and processes.
This integration was integrated and tested with version 21.2 of Cybereason
Configure Cybereason in Cortex
| Parameter | Required |
|---|---|
| Server URL (e.g. https://192.168.0.1) | True |
| Credentials | False |
| Password | False |
| Trust any certificate (not secure) | False |
| Use system proxy settings | False |
| Fetch incidents | False |
| Incident type | False |
| First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) | False |
| Fetch by “MALOP UPDATE TIME” (Fetching by Malop creation time is no longer supported) | False |
Cybereason MalOp to XSOAR Incident Map
This involves the mapping of response fields to XSOAR incidents, enhancing the ability to manage and track security incidents effectively.
Overview
- Incident Mapping: The integration maps specific response fields to corresponding incident fields within XSOAR, ensuring that all relevant information is captured accurately.
- Custom Fields: In addition to standard incident fields, custom fields have been introduced to accommodate unique data requirements specific to our workflow. These fields provide flexibility and enhance the granularity of the incident information.
malopcreationtimemalopupdatetimemaloprootcauseelementnamemaloprootcauseelementtypemalopseveritymalopdetectiontypemalopedrmalopurlmalopgroup
These custom fields provide flexibility and enhance the granularity of the incident information.
Usage
- Configure Custom Fields: Ensure that all custom fields are properly set up in XSOAR before running the fetch function.
- Enable Fetch Incidents: Functionality responsible to fetch Malops.
- Monitor Incidents: Once the MalOps are converted, they will appear as incidents in XSOAR, allowing for effective incident management.
Cybereason MalOp to XSOAR Incident Map
This involves the mapping of response fields to XSOAR incidents, enhancing the ability to manage and track security incidents effectively.
Overview
- Incident Mapping: The integration maps specific response fields to corresponding incident fields within XSOAR, ensuring that all relevant information is captured accurately.
- Custom Fields: In addition to standard incident fields, custom fields have been introduced to accommodate unique data requirements specific to our workflow. These fields provide flexibility and enhance the granularity of the incident information.
malopcreationtimemalopupdatetimemaloprootcauseelementnamemaloprootcauseelementtypemalopseveritymalopdetectiontypemalopedrmalopurlmalopgroup
These custom fields provide flexibility and enhance the granularity of the incident information.
Fetchin MalOps
The functionality for fetching MalOps is implemented through the fetch_incidents function. This function is responsible for retrieving MalOps and subsequently converting them into XSOAR incidents.
- Conversion Process: The conversion from MalOps to incidents is handled by the
malop_to_incidentfunction. This function processes MalOps one by one, ensuring each is correctly mapped to its corresponding incident structure.
Usage
- Configure Custom Fields: Ensure that all custom fields are properly set up in XSOAR before running the fetch function.
- Run Fetch Incidents: Execute the
fetch_incidentsfunction to initiate the retrieval and conversion process. - Monitor Incidents: Once the MalOps are converted, they will appear as incidents in XSOAR, allowing for effective incident management.
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.
cybereason-query-processes
Searches for processes with various filters.
Base Command
cybereason-query-processes
Input
| Argument Name | Description | Required |
|---|---|---|
| machine | The hostname of the machine. | Optional |
| onlySuspicious | Show only suspicious processes. Possible values are: true, false. Default is false. | Optional |
| limit | Maximum number of results to retrieve. Default is 10000. | Optional |
| processName | Process name to filter by. | Optional |
| saveToContext | If true, save the result to the context. Possible values are: true, false. Default is false. | Optional |
| hasIncomingConnection | Filter only processes with incoming connections. Possible values are: true, false. Default is false. | Optional |
| hasOutgoingConnection | Filter only processes with outgoing connections. Possible values are: true, false. Default is false. | Optional |
| hasExternalConnection | If process has external connection. Possible values are: true, false. | Optional |
| unsignedUnknownReputation | If process is not known to reputation services and its image file is unsigned. Possible values are: true, false. | Optional |
| fromTemporaryFolder | If process is running from temporary folder. Possible values are: true, false. | Optional |
| privilegesEscalation | If process was identified elevating its privileges to local system user. Possible values are: true, false. | Optional |
| maliciousPsExec | If the process was executed by PsExec service and is suspicious as being executed maliciously. Possible values are: true, false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Process.Name | Unknown | The process name |
| Cybereason.Process.Malicious | Unknown | Malicious status of the process |
| Cybereason.Process.CreationTime | Unknown | The process creation time |
| Cybereason.Process.EndTime | Unknown | The process end time |
| Cybereason.Process.CommandLine | Unknown | The command line of the process |
| Cybereason.Process.SignedAndVerified | Unknown | Is the process signed and verified |
| Cybereason.Process.ProductType | Unknown | The product type |
| Cybereason.Process.Children | Unknown | Children of the process |
| Cybereason.Process.Parent | Unknown | The parent process |
| Cybereason.Process.OwnerMachine | Unknown | The machine’s hostname |
| Cybereason.Process.User | Unknown | The user who ran the process |
| Cybereason.Process.ImageFile | Unknown | Image file of the process |
| Cybereason.Process.SHA1 | Unknown | SHA1 of the process file |
| Cybereason.Process.MD5 | Unknown | MD5 of the process file |
| Cybereason.Process.CompanyName | Unknown | The company’s name |
| Cybereason.Process.ProductName | Unknown | The product’s name |
Command example
!cybereason-query-processes machine=machine-name hasOutgoingConnection=true hasIncomingConnection=true
Context Example
{
"Process": [
{
"Children": null,
"CommandLine": "C:\\WINDOWS\\system32\\svchost.exe -k LocalService -s W32Time",
"CompanyName": "Microsoft Corporation",
"CreationTime": "2022-05-06T04:15:33.939000",
"EndTime": "",
"ImageFile": "svchost.exe",
"MD5": "<md5>",
"Malicious": "indifferent",
"Name": "svchost.exe",
"OwnerMachine": "<machine-name>",
"Parent": "services.exe",
"ProductName": "Microsoft\u00ae Windows\u00ae Operating System",
"ProductType": "SVCHOST",
"SHA1": "<sha1>",
"SignedandVerified": "true",
"User": "machine-name\\local service"
}
]
}
Human Readable Output
Cybereason Processes
Name Malicious Creation Time End Time Command Line Signed and Verified Product Type Children Parent Owner Machine User Image File SHA1 MD5 Company Name Product Name svchost.exe indifferent 2022-05-06T04:15:33.939000 C:\WINDOWS\system32\svchost.exe -k LocalService -s W32Time true SVCHOST services.exe machine-name machine-name\local service svchost.exe wxyz1234 abc123 Microsoft Corporation Microsoft® Windows® Operating System
cybereason-is-probe-connected
Checks if the machine is currently connected to the Cybereason server
Base Command
cybereason-is-probe-connected
Input
| Argument Name | Description | Required |
|---|---|---|
| machine | The hostname of the machine to check. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Machine.isConnected | boolean | true if machine is connected, else false |
| Cybereason.Machine.Name | string | Machine name |
Command example
!cybereason-is-probe-connected machine=machine-name
Context Example
{
"Cybereason": {
"Machine": {
"Name": "<machine-name>",
"isConnected": true
}
}
}
Human Readable Output
true
cybereason-query-connections
Searches for connections.
Base Command
cybereason-query-connections
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | Filter connections which contain this IP (in or out). | Optional |
| machine | Filter connections on the given machine. | Optional |
| saveToContext | If true, save the result to the context. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Connection.Name | Unknown | The connection’s name |
| Cybereason.Connection.Direction | Unknown | OUTGOING/INCOMING |
| Cybereason.Connection.ServerAddress | Unknown | Address of the Cybereason machine |
| Cybereason.Connection.ServerPort | Unknown | Port of the Cybereason machine |
| Cybereason.Connection.PortType | Unknown | Type of the connection |
| Cybereason.Connection.ReceivedBytes | Unknown | Received bytes count |
| Cybereason.Connection.TransmittedBytes | Unknown | Transmitted bytes count |
| Cybereason.Connection.RemoteCountry | Unknown | The connection’s remote country |
| Cybereason.Connection.OwnerMachine | Unknown | The machine’s hostname |
| Cybereason.Connection.OwnerProcess | Unknown | The process which performed the connection |
| Cybereason.Connection.CreationTime | Unknown | Creation time of the connection |
| Cybereason.Connection.EndTime | Unknown | End time of the connection |
Command example
!cybereason-query-connections ip=<host>
Context Example
{
"Connection": [
{
"CreationTime": "2021-04-20T00:00:00.00000",
"Direction": "OUTGOING",
"EndTime": "2021-04-20T00:00:00.000000",
"Name": "<connection_ip_addresses>",
"OwnerMachine": "simplify-cyber",
"OwnerProcess": "nbtscan.exe",
"PortType": "SERVICE_WINDOWS",
"ReceivedBytes": "0",
"RemoteCountry": null,
"ServerAddress": "<server_address>",
"ServerPort": "137",
"TransmittedBytes": "50"
}
]
}
Human Readable Output
Cybereason Connections for: 192.168.1.103
Creation Time Direction End Time Name Owner Machine Owner Process Port Type Received Bytes Remote Country Server Address Server Port Transmitted Bytes 2021-04-20T00:00:00.000000 OUTGOING 2021-04-20T00:00:00.000000 connection_ip_addresses simplify-cyber test.exe SERVICE_WINDOWS 0 192.168.1.103 137 50
cybereason-isolate-machine
Isolates a machine that has been infected from the rest of the network
Base Command
cybereason-isolate-machine
Input
| Argument Name | Description | Required |
|---|---|---|
| machine | Machine name to be isolated. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Machine | string | Machine name |
| Cybereason.IsIsolated | boolean | Is the machine isolated |
Command example
!cybereason-isolate-machine machine=machine-name
Context Example
{
"Cybereason": {
"IsIsolated": true,
"Machine": "<machine-name>"
}
}
Human Readable Output
Machine was isolated successfully.
cybereason-unisolate-machine
Stops isolation of a machine
Base Command
cybereason-unisolate-machine
Input
| Argument Name | Description | Required |
|---|---|---|
| machine | Machine name to be un-isolated. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Machine | string | Machine name |
| Cybereason.IsIsolated | boolean | Is the machine isolated |
Command example
!cybereason-unisolate-machine machine=machine-name
Context Example
{
"Cybereason": {
"IsIsolated": false,
"Machine": "<machine-name>"
}
}
Human Readable Output
Machine was un-isolated successfully.
cybereason-query-malops
Returns a list of all Malops and details on the Malops.
Base Command
cybereason-query-malops
Input
| Argument Name | Description | Required |
|---|---|---|
| filters | Filter to filter response by, given in Cybereason API syntax. | Optional |
| totalResultLimit | The total number of results to return for your Server. Ensure you make the limit a reasonable number to maximize Server performance and not to overload the system. | Optional |
| perGroupLimit | The number of items to return per Malop group. | Optional |
| templateContext | The level of detail to provide in the response. Possible values include: SPECIFIC: References value contain only the count in the ElementValues class. The Suspicions map is calculated for each results, with the suspicion name and the first time the suspicion appeared. The Evidence map is not calculated for the results. CUSTOM: Reference values contain the specific Elements, up to the limit defined in the perFeatureLimit parameter. The Suspicions map is not calculated for the results. The Evidence map is not calculated for the results. DETAILS: Reference values contain the specific Elements, up to the limit defined in the perFeatureLimit parameter. The Suspicions map is calculated for each result, containing the suspicion name and the first time the suspicion appeared. The Evidence map is not calculated for the results. Possible values are: MALOP, SPECIFIC, CUSTOM, DETAILS, OVERVIEW. Default is MALOP. | Optional |
| withinLastDays | Return all the malops within the last days. | Optional |
| malopGuid | Malop GUIDs to filter by (Comma separated values supported, e.g. 11.123456789,11.9874563210). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Malops.GUID | string | The unique globally unique identifier (guid) for the Malop. |
| Cybereason.Malops.CreationTime | string | The time reported as when the malicious behavior began on the system. This is not the time that the Malop was first detected by Cybereason. |
| Cybereason.Malops.DecisionFeature | string | The reason that Cybereason has raised the Malop. |
| Cybereason.Malops.Link | string | Link to the Malop on Cybereason. |
| Cybereason.Malops.Suspects | string | Malop suspect type and name |
| Cybereason.Malops.LastUpdatedTime | string | Last updated time of malop |
| Cybereason.Malops.AffectedMachine | string | List of machines affected by this Malop |
| Cybereason.Malops.InvolvedHash | string | List of file hashes involved in this Malop |
| Cybereason.Malops.Status | string | Malop managemant status |
Command example
#### Context Example
```json
{
"Cybereason": {
"Malops": [
{
"AffectedMachine": [
"win10-cybereaso",
"marketing"
],
"CreationTime": "2021-07-12T00:00:00.000000",
"DecisionFailure": "blackListedFileHash",
"GUID": "<malop_id>",
"InvolvedHash": [
1
],
"LastUpdateTime": "2021-08-28T00:00:00.000000",
"Link": "<malop_link>",
"Status": "OPEN",
"Suspects": "Process: test.exe"
}
]
}
}
Human Readable Output
Cybereason Malops
GUID Link CreationTime Status LastUpdateTime DecisionFailure Suspects AffectedMachine InvolvedHash https://test.server.net:0000/#/malop/11.1234567890 2021-07-12T00:00:00.000000 OPEN 2021-08-28T00:00:00.000000 blackListedFileHash Process: test.exe affected_machine_name 1
cybereason-malop-processes
Returns a list of malops
Base Command
cybereason-malop-processes
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuids | Array of malop GUIDs separated by comma. (Malop GUID can be retrieved with the command cybereason-query-malops command). | Required |
| machineName | Machine names which were affected by malop. Comma separated values supported (e.g., machine1,machine2). | Optional |
| dateTime | Starting Date and Time to filter the Processes based on their creation date. The format for the input is (“YYYY/MM/DD HH:MM:SS”). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Process.Name | string | The process name |
| Cybereason.Process.Malicious | Unknown | Malicious status of the process |
| Cybereason.Process.CreationTime | date | The process creation time |
| Cybereason.Process.EndTime | date | The process end time |
| Cybereason.Process.CommandLine | string | The command line of the process |
| Cybereason.Process.SignedAndVerified | Unknown | Is the process signed and verified |
| Cybereason.Process.ProductType | Unknown | The product type |
| Cybereason.Process.Children | Unknown | Children of the process |
| Cybereason.Process.Parent | Unknown | The parent process |
| Cybereason.Process.OwnerMachine | Unknown | The machine’s hostname |
| Cybereason.Process.User | string | The user who ran the process |
| Cybereason.Process.ImageFile | Unknown | Image file of the process |
| Cybereason.Process.SHA1 | string | SHA1 of the process file |
| Cybereason.Process.MD5 | string | MD5 of the process file |
| Cybereason.Process.CompanyName | string | The company’s name |
| Cybereason.Process.ProductName | string | The product’s name |
Command example
!cybereason-malop-processes malopGuids=<malop_id>
Context Example
{
"Process": [
{
"Children": null,
"CommandLine": "\"C:\\Users\\user\\winrar-x64-602.pdf.exe\"",
"CompanyName": "Hello World",
"CreationTime": "2022-03-14T00:00:00.000000",
"EndTime": "2022-03-14T00:00:00.000000",
"ImageFile": "<image_file_name>",
"MD5": "<md5>",
"Malicious": "indifferent",
"Name": "<file_name>",
"OwnerMachine": "<machine-name>",
"Parent": "explorer.exe",
"ProductName": "WinRAR",
"ProductType": null,
"SHA1": "<sha1>",
"SignedandVerified": null,
"User": "machine-name\\user"
}
]
}
Human Readable Output
Cybereason Malop Processes
Name Malicious Creation Time End Time Command Line Parent Owner Machine User Image File SHA1 MD5 Company Name Product Name winrar-x64-602.exe indifferent 2022-03-14T00:00:00.000000 2022-03-14T00:00:00.000000 “C:\Users\user\winrar-x64-602.exe” explorer.exe machine-name machine-name\user winrar-x64-602.exe 1234sajklfshljjvhlsdfhilh23 md5_hash Hello World WinRAR
cybereason-add-comment
Add new comment to malop
Base Command
cybereason-add-comment
Input
| Argument Name | Description | Required |
|---|---|---|
| comment | Comment to add to the malop. | Required |
| malopGuid | Malop GUID to add comment to. (Malop GUID can be retrieved with the command cybereason-query-malops command). | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-add-comment comment=NewComment malopGuid=<malop_id>
Human Readable Output
Comment added successfully
cybereason-update-malop-status
Updates malop status
Base Command
cybereason-update-malop-status
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | Malop GUID to update its status. | Required |
| status | Status to update. Possible values are: To Review, Unread, Remediated, Not Relevant, Open. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Malops.GUID | string | Malop GUID |
| Cybereason.Malops.Status | string | Malop status: To Review,Unread,Remediated,Not Relevant |
Command example
!cybereason-update-malop-status malopGuid=<malop_id> status="To Review"
Context Example
{
"Cybereason": {
"Malops": {
"GUID": "<malop_id>",
"Status": "To Review"
}
}
}
Human Readable Output
Successfully updated malop to status To Review
cybereason-prevent-file
Prevent malop process file
Base Command
cybereason-prevent-file
Input
| Argument Name | Description | Required |
|---|---|---|
| md5 | Malop process file MD5 to prevent. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Process.MD5 | string | Process file MD5 |
| Cybereason.Process.Prevent | boolean | True if process file is prevented, else false |
Command example
!cybereason-prevent-file md5=MD5
Context Example
{
"Process": {
"MD5": "<md5>",
"Prevent": true
}
}
Human Readable Output
File was prevented successfully
cybereason-unprevent-file
Unprevent malop process file
Base Command
cybereason-unprevent-file
Input
| Argument Name | Description | Required |
|---|---|---|
| md5 | Malop process file MD5 to unprevent. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Process.MD5 | string | Process file MD5 |
| Cybereason.Process.Prevent | boolean | True if process file is prevented, else false |
Command example
!cybereason-unprevent-file md5=MD5
Context Example
{
"Process": {
"MD5": "MD5",
"Prevent": false
}
}
Human Readable Output
File was unprevented successfully
cybereason-query-file
Query files as part of investigation
Base Command
cybereason-query-file
Input
| Argument Name | Description | Required |
|---|---|---|
| file_hash | File hash (SHA-1 and MD5 supported). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.File.Path | string | File path |
| Cybereason.File.SHA1 | string | File SHA-1 hash |
| Cybereason.File.Machine | string | Machine name on which file is located |
| Cybereason.File.SuspicionsCount | number | File suspicions count |
| Cybereason.File.Name | string | File name |
| Cybereason.File.CreationTime | date | File creation time |
| Cybereason.File.Suspicion | string | File suspicions object of suspicion as key and detected date as value |
| Cybereason.File.OSVersion | string | Machine OS version on which file is located |
| Cybereason.File.ModifiedTime | date | File modified date |
| Cybereason.File.Malicious | boolean | Is file malicious |
| Cybereason.File.Company | string | Company name |
| Cybereason.File.MD5 | string | File MD5 hash |
| Cybereason.File.IsConnected | boolean | Is machine connected to Cybereason |
| Cybereason.File.Signed | boolean | Is file signed |
| Cybereason.File.Evidence | string | File evidences |
Command example
!cybereason-query-file file_hash=<file_hash>
Context Example
{
"Cybereason": {
"File": {
"Company": "Hello World",
"CreationTime": "2022-02-28T00:00:00.000Z",
"Evidence": [],
"IsConnected": false,
"MD5": "<md5>",
"Machine": "<machine-name>",
"Malicious": false,
"ModifiedTime": "2022-05-09T00:00:00.000Z",
"Name": "<file_name>",
"OSVersion": null,
"Path": "c:\\users\\user\\winrar-x64-602.exe",
"SHA1": "<sha1>",
"Signed": true,
"Suspicion": {},
"SuspicionsCount": null
}
}
}
Human Readable Output
Cybereason file query results for the file hash: 77ab1e20c685e716b82c7c90b373316fc84cde23
Company CreationTime IsConnected MD5 Machine Malicious ModifiedTime Name Path SHA1 Signed Hello World 2022-02-28T00:00:00.000Z false MD5 machine-name false 2022-05-09T00:00:00.000Z winrar-x64-602.pdf.exe c:\users\test\winrar-x64-602.pdf.exe 1245sedecthebdfkjkgjljldl2348 true
cybereason-query-domain
Query domains as part of investigation
Base Command
cybereason-query-domain
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | Domain to query. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Domain.Name | string | Domain name |
| Cybereason.Domain.Malicious | boolean | Is domain malicious |
| Cybereason.Domain.IsInternalDomain | boolean | Is domain internal |
| Cybereason.Domain.Reputation | string | Domain reputation |
| Cybereason.Domain.SuspicionsCount | number | Domain suspicions count |
| Cybereason.Domain.WasEverResolved | boolean | Was domain ever resolved |
| Cybereason.Domain.WasEverResolvedAsASecondLevelDomain | boolean | Was domain ever resolved as a second level domain |
Command example
!cybereason-query-domain domain=www2.bing.com
Context Example
{
"Cybereason": {
"Domain": {
"IsInternalDomain": false,
"Malicious": false,
"Name": "www2.bing.com",
"Reputation": null,
"SuspicionsCount": 0,
"WasEverResolved": false,
"WasEverResolvedAsASecondLevelDomain": true
}
}
}
Human Readable Output
Cybereason domain query results for the domain: www2.bing.com
Name Reputation IsInternalDomain WasEverResolved WasEverResolvedAsASecondLevelDomain Malicious SuspicionsCount www2.bing.com indifferent false false true false 0 www2.bing.com false false true false 0
cybereason-query-user
Query users as part of investigation
Base Command
cybereason-query-user
Input
| Argument Name | Description | Required |
|---|---|---|
| username | Username to query. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.User.Username | string | User name |
| Cybereason.User.Domain | string | User domain |
| Cybereason.User.LastMachineLoggedInTo | string | Last machine which user logged in to |
| Cybereason.User.LocalSystem | boolean | Is local system |
| Cybereason.User.Organization | string | User organization |
Command example
!cybereason-query-user username="user-name"
Context Example
{
"Cybereason": {
"User": {
"Domain": "<machine-name>",
"LastMachineLoggedInTo": "<machine-name>",
"LocalSystem": false,
"Organization": "INTEGRATION",
"Username": "user-name"
}
}
}
Human Readable Output
Cybereason user query results for the username: machine-name\prase
Username Domain LastMachineLoggedInTo Organization LocalSystem machine-name\prase machine-name machine-name INTEGRATION false
cybereason-archive-sensor
Archives a Sensor.
Base Command
cybereason-archive-sensor
Input
| Argument Name | Description | Required |
|---|---|---|
| sensorID | Sensor ID of Cybereason Sensor. | Required |
| archiveReason | Reason for Archiving Cybereason Sensor. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-archive-sensor sensorID=SENSOR_ID archiveReason="Archive this Sensor"
Human Readable Output
Sensor archive status: Failed Actions: 0. Succeeded Actions: 1
cybereason-unarchive-sensor
Unarchives a Sensor.
Base Command
cybereason-unarchive-sensor
Input
| Argument Name | Description | Required |
|---|---|---|
| sensorID | Sensor ID of Cybereason Sensor. | Required |
| unarchiveReason | Reason for Unarchiving Cybereason Sensor. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-unarchive-sensor sensorID=SENSOR_ID unarchiveReason="Unarchive this Sensor"
Human Readable Output
Sensor unarchive status: Failed Actions: 0. Succeeded Actions: 1
cybereason-delete-sensor
Deletes a Sensor.
Base Command
cybereason-delete-sensor
Input
| Argument Name | Description | Required |
|---|---|---|
| sensorID | Sensor ID of Cybereason Sensor. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-delete-sensor sensorID=SENSOR_ID
Human Readable Output
Sensor deleted successfully.
cybereason-start-fetchfile
Start fetching the file to download
Base Command
cybereason-start-fetchfile
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGUID | Malop GUID for fetching a file from a sensor to download. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-start-fetchfile malopGUID=<malop_id> userName=<user_name>
Human Readable Output
Successfully started fetching file for the given malop
cybereason-fetchfile-progress
Return a batch id for files waiting for download
Base Command
cybereason-fetchfile-progress
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | Malop GUID to know the progress for downloading a file. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Download.Progress.fileName | unknown | Filename for tha given malop |
| Cybereason.Download.Progress.status | unknown | Status for batch ID |
| Cybereason.Download.Progress.batchID | unknown | Unique batch id |
Command example
!cybereason-fetchfile-progress malopGuid=<malop_id>
Context Example
{
"Download": {
"progress": {
"MalopID": "<malop_id>",
"batchID": [
-1234
],
"fileName": [
"<file_name>"
],
"status": [
true
]
}
}
}
Human Readable Output
Filename: [‘winrar-x64-602.exe’] Status: [True] Batch ID: [-1234]
cybereason-download-file
Downloads the actual file to the machine
Base Command
cybereason-download-file
Input
| Argument Name | Description | Required |
|---|---|---|
| batchID | The batch id for the file download operation. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-download-file batchID=-1234
Context Example
{
"File": {
"EntryID": "<entry_id>",
"Extension": "zip",
"Info": "application/zip",
"MD5": "<md5>",
"Name": "download.zip",
"SHA1": "<sha1>",
"SHA256": "<SHA256>",
"SHA512": "<SHA512>",
"SSDeep": "<SSDeep_value>",
"Size": 3168792,
"Type": "Zip archive data, at least v2.0 to extract"
}
}
Human Readable Output
Integration log: Downloading the file with this Batch ID: -1234
cybereason-close-file-batch-id
Aborts a file download operation that is in progress
Base Command
cybereason-close-file-batch-id
Input
| Argument Name | Description | Required |
|---|---|---|
| batchID | The batch id to abort a file download operation. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-close-file-batch-id batchID=-1234
Human Readable Output
Successfully aborts a file download operation that is in progress.
cybereason-available-remediation-actions
Get all remediation action details whatever available for that malop
Base Command
cybereason-available-remediation-actions
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-available-remediation-actions malopGuid=<malop_id>
Human Readable Output
{ "data": [ { "machineConnected": false, "machineId": "machine_id", "machineName": "<machine-name>", "machinesCount": 1, "malopId": "<malop_id>", "malopType": "MalopProcess", "remediationType": "BLOCK_FILE", "targetId": "<target_id>", "targetName": "<target_name>", "uniqueId": "<unique_id>" }, { "machineConnected": false, "machineId": "<machine_id>", "machineName": "<machine-name>", "machinesCount": 1, "malopId": "<malop_id>", "malopType": "MalopProcess", "remediationType": "UNQUARANTINE_FILE", "targetId": "<target_id>", "targetName": "<target_name>", "uniqueId": "<unique_id>" } ], "errorMessage": "", "status": "SUCCESS" }
cybereason-kill-process
Kill a processes for the malicious file. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-kill-process
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name to kill the process. | Required |
| targetId | Target ID to kill the process. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-kill-process machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Kill the Process"
Human Readable Output
Kill process remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-quarantine-file
Quarantine the detected malicious file in a secure location. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-quarantine-file
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name to quarantine a file. | Required |
| targetId | Target ID to quarantine a file. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-quarantine-file machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Quarantine the File"
Human Readable Output
Quarantine file remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-unquarantine-file
Unquarantine the detected malicious file in a secure location. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-unquarantine-file
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name to unquarantine a file. | Required |
| targetId | Target ID to unquarantine a file. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-unquarantine-file machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Unquarantine the File"
Human Readable Output
Unquarantine file remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-block-file
Block a file only in particular machine. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-block-file
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name whose files needs to be blocked. | Required |
| targetId | Target ID of file to be blocked. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-block-file machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Block a File"
Human Readable Output
Block file remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-delete-registry-key
Delete a registry entry associated with a malicious process. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-delete-registry-key
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name to delete the registry key. | Required |
| targetId | Target ID to delete the registry key. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-delete-registry-key machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Remove the registry key"
Human Readable Output
Delete registry key remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-kill-prevent-unsuspend
Prevent detected ransomware from running on the machine. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-kill-prevent-unsuspend
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name to prevent detected ransomware from running on the machine. | Required |
| targetId | Target ID to prevent detected ransomware from running on the machine. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-kill-prevent-unsuspend machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Kill Prevent"
Human Readable Output
Kill prevent unsuspend remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-unsuspend-process
Prevent a file associated with ransomware. (User will get inputs by executing the ‘cybereason-available-remediation-actions’ command if this remediation action is available for that Malop)
Base Command
cybereason-unsuspend-process
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | The unique ID assigned by the Cybereason platform for the Malop. | Required |
| machine | Machine name to prevent a file associated with ransomware. | Required |
| targetId | Target ID to prevent a file associated with ransomware. | Required |
| userName | The complete Cybereason user name string for the user performing the request. | Required |
| comment | Comment to add to the malop. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-unsuspend-process machine=machine-name malopGuid=<malop_id> targetId=<target_id> userName=<user_name> comment="Unsuspend Process"
Human Readable Output
Unsuspend process remediation action status is: SUCCESS
Remediation ID: REMEDIATION_ID
cybereason-malware-query
Malware query with options and values to filter
Base Command
cybereason-malware-query
Input
| Argument Name | Description | Required |
|---|---|---|
| needsAttention | Filter for Fetching Malwares by Malware needsAttention. Possible values are: True, False. | Optional |
| type | Filter for Fetching Malwares by Malware Type. (Possible filter values for Type are “KnownMalware,UnknownMalware,FilelessMalware,ApplicationControlMalware,RansomwareMalware”). | Optional |
| status | Filter for Fetching Malwares by Malware Status. (Possible filter values for Status are “Done,Excluded,Detected,Prevented,Remediated,DeleteOnRestart,Quarantined”). | Optional |
| timestamp | Filter for Fetching Malwares by Timestamp. Enter the time (in epoch). | Optional |
| limit | Filter for Fetching Malwares by Malware Limit. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-malware-query limit=5 needsAttention=True status=Done type=KnownMalware timestamp=1582206286000
Human Readable Output
{ "data": { "hasMoreResults": false, "malwares": [], "totalResults": 0 }, "expectedResults": 0, "failedServersInfo": null, "failures": 0, "hidePartialSuccess": false, "message": "", "status": "SUCCESS" }
cybereason-start-host-scan
Start or stop a full or quick scan for a host.
Base Command
cybereason-start-host-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| sensorID | Sensor ID of a sensor. (Comma separated values supported.). | Required |
| scanType | Select a method/type to scan a host. Possible values are: FULL, QUICK, STOP. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-start-host-scan sensorID=SENSOR_ID scanType=FULL
Human Readable Output
Batch ID: -11156
cybereason-fetch-scan-status
Get the results for host scanning.
Base Command
cybereason-fetch-scan-status
Input
| Argument Name | Description | Required |
|---|---|---|
| batchID | The batch ID obtained after initiating the scan. | Required |
Context Output
There is no context output for this command.
Command example
!cybereason-fetch-scan-status batchID=-11156
Human Readable Output
{ "abortHttpStatusCode": null, "abortTime": 0, "abortTimeout": false, "aborterUser": null, "actionArguments": [ "ScheduleScanAction", "FULL" ], "actionType": "SchedulerScan", "batchId": -11156, "creatorUser": "<user_name>", "finalState": true, "globalStats": { "stats": { "AbortTimeout": 0, "Aborted": 0, "Aborting": 0, "AlreadyUpdated": 0, "BadArgument": 0, "ChunksRequired": 0, "Disconnected": 0, "EndedWithInvalidParam": 0, "EndedWithNoValidFolder": 0, "EndedWithSensorTimeout": 0, "EndedWithTooManyResults": 0, "EndedWithTooManySearches": 0, "EndedWithUnknownError": 0, "EndedWithUnsupportedFilter": 0, "EndedWithYaraCompileError": 0, "Failed": 0, "FailedSending": 0, "FailedSendingToServer": 0, "GettingChunks": 0, "InProgress": 0, "InvalidState": 0, "MsiFileCorrupted": 0, "MsiSendFail": 0, "NewerInstalled": 0, "None": 0, "NotSupported": 0, "Pending": 0, "Primed": 0, "ProbeRemoved": 0, "SendingMsi": 0, "SendingPlatform": 0, "Started": 0, "Succeeded": 1, "Timeout": 0, "TimeoutSending": 0, "UnauthorizedUser": 0, "UnknownProbe": 0, "partialResponse": 0 } }, "initiatorUser": "<user_name>", "startTime": 1652279731232, "totalNumberOfProbes": 1 }
cybereason-get-sensor-id
Get the Sensor ID of a machine.
Base Command
cybereason-get-sensor-id
Input
| Argument Name | Description | Required |
|---|---|---|
| machineName | The hostname of the machine. | Optional |
Context Output
There is no context output for this command.
Command example
!cybereason-get-sensor-id machineName=machine-name
Human Readable Output
Sensor ID for the machine ‘machine-id’ is: SENSOR_ID
cybereason-get-machine-details
Get the results related to machines.
Base Command
cybereason-get-machine-details
Input
| Argument Name | Description | Required |
|---|---|---|
| machineName | The hostname of the machine. | Required |
| page | The page number of machine records to retrieve (used for pagination) starting from 1. The page size is defined by the “pageSize” argument. | Optional |
| pageSize | The number of machine records per page to retrieve (used for pagination). The page number is defined by the “page” argument. | Optional |
| limit | The maximum number of records to retrieve. If “pageSize” is defined, this argument is ignored. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Sensor.MachineID | string | Sensor ID of machine |
| Cybereason.Sensor.MachineName | string | Host name of machine |
| Cybereason.Sensor.MachineFQDN | string | FQDN of machine |
| Cybereason.Sensor.GroupID | string | Group ID of machine |
| Cybereason.Sensor.GroupName | string | Group Name of machine |
Command example
!cybereason-get-machine-details machineName=xyz-1
Context Example
{
"MachineID": "example-machine-id",
"MachineName": "example-machine-name",
"MachineFQDN": "example-machine-fqdn",
"GroupID": "example-group-id",
"GroupName": "example-group-name"
}
Base Command
cybereason-query-malop-management
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | malopGuid of the Cybereason Malop. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Malops.GUID | string | The unique globally unique identifier (guid) for the Malop. |
| Cybereason.Malops.CreationTime | string | The time reported as when the malicious behavior began on the system. This is not the time that the Malop was first detected by Cybereason. |
| Cybereason.Malops.Link | string | Link to the Malop on Cybereason. |
| Cybereason.Malops.LastUpdatedTime | string | Last updated time of malop |
| Cybereason.Malops.InvolvedHash | string | List of file hashes involved in this Malop |
| Cybereason.Malops.Status | string | Malop managemant status |
| Cybereason.Malops.MalopCloserName | string | List of Malop Closer Name involved in this Malop |
| Cybereason.Malops.Machines | string | List of Machines involved in this Malop |
| Cybereason.Malops.Severity | string | Severity of Malop |
| Cybereason.Malops.MitreTechniques | string | List of Mitre Techniques involved in this Malop |
| Cybereason.Malops.Users | string | List of Users involved in this Malop |
| Cybereason.Malops.DetectionTypes | string | List of Detection Types involved in this Malop |
| Cybereason.Malops.DecisionStatuses | string | List of Decision Statuses involved in this Malop |
| Cybereason.Malops.DetectionEngines | string | List of Detection Engines involved in this Malop |
Command example
!cybereason-query-malop-management malopGuid=<malop-guid>
Context Example
{
"GUID": "malop-guid",
"Link": "malop-url",
"CreationTime": 1686720403740,
"LastUpdateTime": 1686720403743,
"Status": "Pending",
"InvolvedHash": "involed-hash"
}
Base Command
cybereason_process_attack_tree_command
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | malopGuid of the Cybereason Malop | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Process.ProcessID | string | Cybereason Process ID |
| Cybereason.Process.URL | string | Attack tree url for a given Process |
Command example
!cybereason-process-attack-tree processGuid=<process-guid>
Context Example
{
"Process": [
{
"ProcessID": "<process-id>",
"URL": "<url>"
},
{
"ProcessID": "<process-id>",
"URL": "<url>"
}
]
}
cybereason-update-malop-investigation-status
Updates malop investigation status.
Base Command
cybereason-update-malop-investigation-status
Input
| Argument Name | Description | Required |
|---|---|---|
| malopGuid | Malop GUID to update its investigation status. | Required |
| investigationStatus | Investigation status to update. Possible values are: Pending, Reopened, Under Investigation, On Hold, Closed. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Cybereason.Malops.GUID | string | Malop GUID. |
| Cybereason.Malops.InvestigationStatus | string | Malop investigation status: Pending, Reopened, Under Investigation, On Hold, Closed. |
Command example
!cybereason-update-malop-investigation-status malopGuid=<malop_guid> investigationStatus="Under Investigation"
Context Example
{
"Cybereason": {
"Malops": {
"GUID": "<malop_guid>",
"InvestigationStatus": "Under Investigation"
}
}
}
Human Readable Output
Successfully updated malop to investigation status "Under Investigation"!
Configuration parameters
server— Server URL (e.g. https://192.168.0.1) (required)credentials— Credentialsunsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalfetch_time— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year)fetch_by— Fetch by "MALOP UPDATE TIME" (Fetching by Malop creation time is no longer supported)enable_epp_poll— Check this box to Enable Polling for Cybereason EPP Malops.
Commands (37)
-
cybereason-add-commentAdd new comment to malop.
-
cybereason-archive-sensorArchives a Sensor.
-
cybereason-available-remediation-actionsGet all remediation action details whatever available for that malop.
-
cybereason-block-fileBlock a file only in particular machine. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-close-file-batch-idAborts a file download operation that is in progress.
-
cybereason-delete-registry-keyDelete a registry entry associated with a malicious process. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-delete-sensorDeletes a Sensor.
-
cybereason-download-fileDownloads the actual file to the machine.
-
cybereason-fetch-scan-statusGet the results for host scanning.
-
cybereason-fetchfile-progressReturn a batch id for files waiting for download.
-
cybereason-get-machine-detailsGet the Machine FQDN and Machine Group Details.
-
cybereason-get-sensor-idGet the Sensor ID of a machine.
-
cybereason-is-probe-connectedChecks if the machine is currently connected to the Cybereason server.
-
cybereason-isolate-machineIsolates a machine that has been infected from the rest of the network.
-
cybereason-kill-prevent-unsuspendPrevent detected ransomware from running on the machine. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-kill-processKill a processes for the malicious file. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-malop-processesReturns a list of malops.
-
cybereason-malware-queryMalware query with options and values to filter.
-
cybereason-prevent-filePrevent malop process file.
-
cybereason-process-attack-treeGet Process Attack Tree URL.
-
cybereason-quarantine-fileQuarantine the detected malicious file in a secure location. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-query-connectionsSearches for connections.
-
cybereason-query-domainQuery domains as part of investigation.
-
cybereason-query-fileQuery files as part of investigation.
-
cybereason-query-malop-managementGet Management Malop details.
-
cybereason-query-malopsReturns a list of all Malops and details on the Malops.
-
cybereason-query-processesSearches for processes with various filters.
-
cybereason-query-userQuery users as part of investigation.
-
cybereason-start-fetchfileStart fetching the file to download.
-
cybereason-start-host-scanStart or stop a full or quick scan for a host.
-
cybereason-unarchive-sensorUnarchives a Sensor.
-
cybereason-unisolate-machineStops isolation of a machine.
-
cybereason-unprevent-fileUnprevent malop process file.
-
cybereason-unquarantine-fileUnquarantine the detected malicious file in a secure location. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-unsuspend-processPrevent a file associated with ransomware. (User will get inputs by executing the 'cybereason-available-remediation-actions' command if this remediation action is available for that Malop).
-
cybereason-update-malop-investigation-statusUpdates malop investigation status.
-
cybereason-update-malop-statusUpdates malop status.
import demistomock as demisto from CommonServerPython import * """ IMPORTS """ import requests import os import json from datetime import datetime, timedelta import time import re import urllib3 # disable insecure warnings urllib3.disable_warnings() """ GLOBAL VARS """ SERVER = demisto.params().get("server", "") if SERVER.endswith("/"): SERVER = SERVER[:-1] USERNAME = demisto.params().get("credentials", {}).get("identifier") PASSWORD = demisto.params().get("credentials", {}).get("password") USE_SSL = not demisto.params().get("unsecure", False) CERTIFICATE = demisto.params().get("credentials", {}).get("credentials", {}).get("sshkey") FETCH_TIME_DEFAULT = "3 days" FETCH_TIME = demisto.params().get("fetch_time", FETCH_TIME_DEFAULT) FETCH_TIME = FETCH_TIME if FETCH_TIME and FETCH_TIME.strip() else FETCH_TIME_DEFAULT FETCH_BY = demisto.params().get("fetch_by", "MALOP UPDATE TIME") IS_EPP_ENABLED = argToBoolean(demisto.params().get("enable_epp_poll", False)) STATUS_MAP = {"To Review": "TODO", "Remediated": "CLOSED", "Unread": "UNREAD", "Not Relevant": "FP", "Open": "OPEN"} INVESTIGATION_STATUS_MAP = { "Pending": "Pending", "Reopened": "ReOpened", "Under Investigation": "UnderInvestigation", "On Hold": "OnHold", "Closed": "Closed", } # Field = the name as received from CR API, Header = The name which will be mapped to Demisto command, # Type = Data that is received from CR API PROCESS_INFO = [ {"field": "elementDisplayName", "header": "Name", "type": "filterData"}, {"field": "imageFile.maliciousClassificationType", "header": "Malicious", "type": "simple"}, {"field": "creationTime", "header": "Creation Time", "type": "time"}, {"field": "endTime", "header": "End Time", "type": "time"}, {"field": "commandLine", "header": "Command Line", "type": "simple"}, {"field": "isImageFileSignedAndVerified", "header": "Signed and Verified", "type": "simple"}, {"field": "productType", "header": "Product Type", "type": "simple"}, {"field": "children", "header": "Children", "type": "simple"}, {"field": "parentProcess", "header": "Parent", "type": "element"}, {"field": "ownerMachine", "header": "Owner Machine", "type": "element"}, {"field": "calculatedUser", "header": "User", "type": "element"}, {"field": "imageFile", "header": "Image File", "type": "element"}, {"field": "imageFile.sha1String", "header": "SHA1", "type": "simple"}, {"field": "imageFile.md5String", "header": "MD5", "type": "simple"}, {"field": "imageFile.companyName", "header": "Company Name", "type": "simple"}, {"field": "imageFile.productName", "header": "Product Name", "type": "simple"}, ] PROCESS_FIELDS = [element["field"] for element in PROCESS_INFO] PROCESS_HEADERS = [element["header"] for element in PROCESS_INFO] MALOP_HEADERS = [ "GUID", "Link", "CreationTime", "Status", "LastUpdateTime", "DecisionFailure", "Suspects", "AffectedMachine", "InvolvedHash", ] SINGLE_MALOP_HEADERS = [ "GUID", "Link", "CreationTime", "Status", "LastUpdateTime", "InvolvedHash", "Severity", "Machines", "Users", "DecisionStatuses", "DetectionTypes", "DetectionEngines", "MitreTechniques", "MalopCloserName", ] DOMAIN_HEADERS = [ "Name", "Reputation", "IsInternalDomain", "WasEverResolved", "WasEverResolvedAsASecondLevelDomain", "Malicious", "SuspicionsCount", ] USER_HEADERS = ["Username", "Domain", "LastMachineLoggedInTo", "Organization", "LocalSystem"] SENSOR_HEADERS = ["MachineID", "MachineName", "MachineFQDN", "GroupID", "GroupName"] PROCESS_URL_HEADERS = ["URL", "ProcessID"] CONNECTION_INFO = [ {"field": "elementDisplayName", "header": "Name", "type": "simple"}, {"field": "direction", "header": "Direction", "type": "simple"}, {"field": "serverAddress", "header": "Server Address", "type": "simple"}, {"field": "serverPort", "header": "Server Port", "type": "simple"}, {"field": "portType", "header": "Port Type", "type": "simple"}, {"field": "aggregatedReceivedBytesCount", "header": "Received Bytes", "type": "simple"}, {"field": "aggregatedTransmittedBytesCount", "header": "Transmitted Bytes", "type": "simple"}, {"field": "remoteAddressCountryName", "header": "Remote Country", "type": "simple"}, {"field": "ownerMachine", "header": "Owner Machine", "type": "element"}, {"field": "ownerProcess", "header": "Owner Process", "type": "element"}, {"field": "calculatedCreationTime", "header": "Creation Time", "type": "time"}, {"field": "endTime", "header": "End Time", "type": "time"}, ] DOMAIN_HEADERS = [ "Name", "Reputation", "IsInternalDomain", "WasEverResolved", "WasEverResolvedAsASecondLevelDomain", "Malicious", "SuspicionsCount", ] USER_HEADERS = ["Username", "Domain", "LastMachineLoggedInTo", "Organization", "LocalSystem"] SENSOR_HEADERS = ["MachineID", "MachineName", "MachineFQDN", "GroupID", "GroupName"] PROCESS_URL_HEADERS = ["URL", "ProcessID"] CONNECTION_INFO = [ {"field": "elementDisplayName", "header": "Name", "type": "simple"}, {"field": "direction", "header": "Direction", "type": "simple"}, {"field": "serverAddress", "header": "Server Address", "type": "simple"}, {"field": "serverPort", "header": "Server Port", "type": "simple"}, {"field": "portType", "header": "Port Type", "type": "simple"}, {"field": "aggregatedReceivedBytesCount", "header": "Received Bytes", "type": "simple"}, {"field": "aggregatedTransmittedBytesCount", "header": "Transmitted Bytes", "type": "simple"}, {"field": "remoteAddressCountryName", "header": "Remote Country", "type": "simple"}, {"field": "ownerMachine", "header": "Owner Machine", "type": "element"}, {"field": "ownerProcess", "header": "Owner Process", "type": "element"}, {"field": "calculatedCreationTime", "header": "Creation Time", "type": "time"}, {"field": "endTime", "header": "End Time", "type": "time"}, ] TOKEN_VALIDITY_PERIOD_SECONDS = 28000 CONNECTION_FIELDS = [element["field"] for element in CONNECTION_INFO] CONNECTION_HEADERS = [element["header"] for element in CONNECTION_INFO] JSESSIONID = "" HEADERS = {"Content-Type": "application/json", "Connection": "close", "Cookie": f"JSESSIONID={JSESSIONID}"} """ HELPER FUNCTIONS """ def build_query(query_fields: list, path: list, template_context: str = "SPECIFIC") -> dict: limit = demisto.getArg("limit") results_limit = int(limit) if limit else 10000 group_limit = int(limit) if limit else 100 query = { "customFields": query_fields, "perFeatureLimit": 100, "perGroupLimit": group_limit, "queryPath": path, "queryTimeout": 120000, "templateContext": template_context, "totalResultLimit": results_limit, } return query class Client(BaseClient): def __init__(self, base_url, verify, headers, proxy): super().__init__(base_url=base_url, verify=verify, headers=headers, proxy=proxy) def cybereason_api_call( self, method: str, url_suffix: str, data: dict = None, json_body: Any = None, headers: dict = HEADERS, return_json: bool = True, custom_response: bool = False, retries: int = 0, backoff_factor: int = 5, ) -> Any: demisto.info(f"cybereason_api_call: running request with url={SERVER + url_suffix}. API Query: {json_body}") # Helper function to perform the API call def make_request(): return self._http_request( method, url_suffix=url_suffix, data=data, json_data=json_body, resp_type="response", headers=headers, error_handler=self.error_handler, retries=retries, backoff_factor=backoff_factor, ) try: res = make_request() # Check for redirection to login page logic if res.status_code == 200 and "login" not in url_suffix and "login" in str(res.url): demisto.info("cybereason_api_call: Token Trigger In Retry method: Session expired, logging in again.") new_token, login_time = login(self) # Update context and headers integration_context = get_integration_context() integration_context["jsession_id"] = new_token integration_context["valid_until"] = login_time + TOKEN_VALIDITY_PERIOD_SECONDS set_integration_context(integration_context) HEADERS["Cookie"] = f"JSESSIONID={new_token}" demisto.info("cybereason_api_call: New token generated. Retrying original request.") # Retry the request with the new token res = make_request() if custom_response: return res if res.status_code not in [200, 204]: raise Exception( "Your request failed with the following error: " + str(res.content) + ". Response Status code: " + str(res.status_code) ) except Exception as e: raise Exception(e) if return_json: try: return res.json() except Exception as e: error_content = res.content error_msg = "" if "Login" in str(error_content): error_msg = "Authentication failed, verify the credentials are correct." raise ValueError(f"Failed to process the API response. {str(error_msg)} {str(error_content)} - {str(e)}") return None def error_handler(self, res: requests.Response): # Handle error responses gracefully command = demisto.command() if res.status_code == 500: if command == "cybereason-download-file": raise Exception("The given Batch ID has expired") elif command == "cybereason-close-file-batch-id": raise Exception("The given Batch ID does not exist") def translate_timestamp(timestamp: str) -> str: return datetime.fromtimestamp(int(timestamp) / 1000).isoformat() def update_output(output: dict, simple_values: dict, element_values: dict, info_dict: list) -> dict: for info in info_dict: info_type = info.get("type", "") if info_type == "simple": output[info["header"]] = dict_safe_get(simple_values, [info.get("field"), "values", 0]) elif info_type == "element": output[info["header"]] = dict_safe_get(element_values, [info.get("field"), "elementValues", 0, "name"]) elif info_type == "time": time_stamp_str = dict_safe_get( simple_values, [info.get("field"), "values", 0], default_return_value="", return_type=str ) output[info["header"]] = translate_timestamp(time_stamp_str) if time_stamp_str else "" return output def get_pylum_id(client: Client, machine: str) -> str: query_fields = ["pylumId"] path = [{"requestedType": "Machine", "filters": [{"facetName": "elementDisplayName", "values": [machine]}], "isResult": True}] json_body = build_query(query_fields, path) response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) data = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) pylum_id = dict_safe_get(list(data.values()), [0, "simpleValues", "pylumId", "values", 0]) if not pylum_id: raise ValueError("Could not find machine") return pylum_id def get_machine_guid(client: Client, machine_name: str) -> str: query_fields = ["elementDisplayName"] path = [ {"requestedType": "Machine", "filters": [{"facetName": "elementDisplayName", "values": [machine_name]}], "isResult": True} ] json_body = build_query(query_fields, path) response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) data = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) return dict_safe_get(list(data.keys()), [0]) """ FUNCTIONS """ def is_probe_connected_command(client: Client, args: dict, is_remediation_command: bool = False) -> Any: machine = str(args.get("machine")) is_connected = False response = is_probe_connected(client, machine) elements = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) for value in list(elements.values()): machine_name = dict_safe_get( value, ["simpleValues", "elementDisplayName", "values", 0], default_return_value="", return_type=str ) if machine_name.upper() == machine.upper(): is_connected = True break if is_remediation_command: return is_connected return CommandResults( readable_output=f"{is_connected}", outputs_prefix="Cybereason.Machine", outputs_key_field="Name", outputs={"isConnected": is_connected, "Name": machine}, ) def is_probe_connected(client: Client, machine: str) -> dict: query_fields = ["elementDisplayName"] path = [ { "requestedType": "Machine", "filters": [ {"facetName": "elementDisplayName", "values": [machine]}, {"facetName": "isActiveProbeConnected", "values": [True]}, ], "isResult": True, } ] json_body = build_query(query_fields, path) return client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) def query_processes_command(client: Client, args: dict): machine = str(args.get("machine")) process_name = args.get("processName") only_suspicious = args.get("onlySuspicious") has_incoming_connection = args.get("hasIncomingConnection") has_outgoing_connection = args.get("hasOutgoingConnection") has_external_connection = args.get("hasExternalConnection") unsigned_unknown_reputation = args.get("unsignedUnknownReputation") from_temporary_folder = args.get("fromTemporaryFolder") privileges_escalation = args.get("privilegesEscalation") maclicious_psexec = args.get("maliciousPsExec") response = query_processes( client, machine, process_name, only_suspicious, has_incoming_connection, has_outgoing_connection, has_external_connection, unsigned_unknown_reputation, from_temporary_folder, privileges_escalation, maclicious_psexec, ) elements = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) outputs = [] for item in list(elements.values()): if not isinstance(item, dict): raise ValueError("Cybereason raw response is not valid, item in elements is not a dict") simple_values = item.get("simpleValues", {}) element_values = item.get("elementValues", {}) output = {} for info in PROCESS_INFO: if info.get("type") == "filterData": output[info["header"]] = dict_safe_get(item, ["filterData", "groupByValue"]) output = update_output(output, simple_values, element_values, PROCESS_INFO) outputs.append(output) context = [] for output in outputs: # Remove whitespaces from dictionary keys context.append({key.translate({32: None}): value for key, value in output.items()}) return CommandResults( readable_output=tableToMarkdown("Cybereason Processes", outputs, headers=PROCESS_HEADERS), outputs_prefix="Cybereason.Process", outputs_key_field="Name", outputs=context, ) def query_processes( client: Client, machine: str, process_name: Any, only_suspicious: str = None, has_incoming_connection: str = None, has_outgoing_connection: str = None, has_external_connection: str = None, unsigned_unknown_reputation: str = None, from_temporary_folder: str = None, privileges_escalation: str = None, maclicious_psexec: str = None, ) -> dict: machine_filters = [] process_filters = [] if machine: machine_filters.append({"facetName": "elementDisplayName", "values": [machine]}) if process_name: process_filters.append({"facetName": "elementDisplayName", "values": [process_name]}) if only_suspicious and only_suspicious == "true": process_filters.append({"facetName": "hasSuspicions", "values": [True]}) if has_incoming_connection == "true": process_filters.append({"facetName": "hasIncomingConnection", "values": [True]}) if has_outgoing_connection == "true": process_filters.append({"facetName": "hasOutgoingConnection", "values": [True]}) if has_external_connection == "true": process_filters.append({"facetName": "hasExternalConnection", "values": [True]}) if unsigned_unknown_reputation == "true": process_filters.append({"facetName": "unknownUnsignedEvidence", "values": [True]}) if from_temporary_folder == "true": process_filters.append({"facetName": "runningFromTempEvidence", "values": [True]}) if privileges_escalation == "true": process_filters.append({"facetName": "privilegeEscalationSuspicion", "values": [True]}) if maclicious_psexec == "true": process_filters.append({"facetName": "executedByPsexecSuspicion", "values": [True]}) path = [ { "requestedType": "Machine", "filters": machine_filters, "connectionFeature": {"elementInstanceType": "Machine", "featureName": "processes"}, }, {"requestedType": "Process", "filters": process_filters, "isResult": True}, ] json_body = build_query(PROCESS_FIELDS, path) return client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) def query_connections_command(client: Client, args: dict): machine = argToList(args.get("machine")) ip = argToList(args.get("ip")) if ip and machine: raise Exception("Too many arguments given.") elif not ip and not machine: raise Exception("Not enough arguments given.") if machine: input_list = machine else: input_list = ip for filter_input in input_list: response = query_connections(client, machine, ip, filter_input) elements = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) outputs = [] for item in list(elements.values()): simple_values = dict_safe_get(item, ["simpleValues"], default_return_value={}, return_type=dict) element_values = dict_safe_get(item, ["elementValues"], default_return_value={}, return_type=dict) output = update_output({}, simple_values, element_values, CONNECTION_INFO) outputs.append(output) context = [] for output in outputs: # Remove whitespaces from dictionary keys context.append({key.translate({32: None}): value for key, value in output.items()}) return CommandResults( readable_output=tableToMarkdown(f"Cybereason Connections for: {filter_input}", outputs), outputs_prefix="Cybereason.Connection", outputs_key_field="Name", outputs=context, ) return None def query_connections(client: Client, machine: str, ip: str, filter_input: str) -> dict: if machine: path = [ { "requestedType": "Connection", "filters": [], "connectionFeature": {"elementInstanceType": "Connection", "featureName": "ownerMachine"}, "isResult": True, }, { "requestedType": "Machine", "filters": [{"facetName": "elementDisplayName", "values": [filter_input], "filterType": "Equals"}], }, ] elif ip: path = [ { "requestedType": "Connection", "filters": [{"facetName": "elementDisplayName", "values": [filter_input]}], "isResult": True, } ] else: path = [{}] json_body = build_query(CONNECTION_FIELDS, path) response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) return response def query_malops_command(client: Client, args: dict): total_result_limit = arg_to_number(args.get("totalResultLimit")) per_group_limit = arg_to_number(args.get("perGroupLimit")) template_context = args.get("templateContext") filters = json.loads(str(args.get("filters"))) if args.get("filters") else [] within_last_days = args.get("withinLastDays") guid_list = argToList(args.get("malopGuid")) if within_last_days: current_timestamp = time.time() current_datetime = datetime.fromtimestamp(current_timestamp) within_last_days_datetime = current_datetime - timedelta(days=int(within_last_days)) within_last_days_timestamp = ( time.mktime(within_last_days_datetime.timetuple()) + within_last_days_datetime.microsecond / 1e6 ) # Converting datetime to time within_last_days_timestamp *= 1000 filters.append({"facetName": "malopLastUpdateTime", "values": [within_last_days_timestamp], "filterType": "GreaterThan"}) demisto.info(f"GUID List to be filtered in query_malops_command : {guid_list}") malop_process_type, malop_loggon_session_type = query_malops( client, total_result_limit, per_group_limit, template_context, filters, guid_list=guid_list ) outputs = [] data: dict = {} for response in (malop_process_type, malop_loggon_session_type): data = response.get("data", {}) if response else {} malops_map = dict_safe_get(data, ["resultIdToElementDataMap"], default_return_value={}, return_type=dict) if not data or not malops_map: continue for guid, malop in malops_map.items(): simple_values = dict_safe_get(malop, ["simpleValues"], {}, dict) management_status = dict_safe_get( simple_values, ["managementStatus", "values", 0], default_return_value="", return_type=str ) if management_status.upper() == "CLOSED": continue creation_time = translate_timestamp(dict_safe_get(simple_values, ["creationTime", "values", 0])) malop_last_update_time = translate_timestamp(dict_safe_get(simple_values, ["malopLastUpdateTime", "values", 0])) raw_decision_failure = dict_safe_get( simple_values, ["decisionFeature", "values", 0], default_return_value="", return_type=str ) decision_failure = raw_decision_failure.replace("Process.", "") raw_suspects = dict_safe_get(malop, ["elementValues", "suspects"], default_return_value={}, return_type=dict) suspects_string = "" if raw_suspects: suspects = dict_safe_get(raw_suspects, ["elementValues", 0], default_return_value={}, return_type=dict) suspects_string = f"{suspects.get('elementType')}: {suspects.get('name')}" affected_machines = [] elementValues = dict_safe_get( malop, ["elementValues", "affectedMachines", "elementValues"], default_return_value=[], return_type=list ) for machine in elementValues: if not isinstance(machine, dict): raise ValueError("Cybereason raw response is not valid, machine in elementValues is not a dict") affected_machines.append(machine.get("name", "")) involved_hashes = [] # type: List[str] cause_elements_amount = dict_safe_get(simple_values, ["rootCauseElementHashes", "totalValues"]) if cause_elements_amount != 0: involved_hashes.append(cause_elements_amount) malop_output = { "GUID": guid, "Link": SERVER + "/#/malop/" + guid, "CreationTime": creation_time, "DecisionFailure": re.sub(r"\([^)]*\)", "", decision_failure), "Suspects": suspects_string, "LastUpdateTime": malop_last_update_time, "Status": management_status, "AffectedMachine": affected_machines, "InvolvedHash": involved_hashes, } outputs.append(malop_output) return CommandResults( readable_output=tableToMarkdown("Cybereason Malops", outputs, headers=MALOP_HEADERS) if outputs else "No malops found", outputs_prefix="Cybereason.Malops", outputs_key_field="GUID", outputs=outputs, ) def get_detection_details(client: Client, non_edr_guid): try: detection_details_response = {} json_body = {"malopGuid": non_edr_guid} detection_details_response = client.cybereason_api_call("POST", "/rest/detection/details", json_body=json_body) except Exception as error: demisto.info(f"Exception while getting detection/details response: {error}") return detection_details_response def poll_malops(client: Client, start_time): end_time = round(datetime.now().timestamp()) * 1000 json_body = {"startTime": start_time, "endTime": end_time} api_response = client.cybereason_api_call("POST", "/rest/detection/inbox", json_body=json_body) demisto.debug(f"Fetching the length of rest/dectection malops : {len(api_response)}") return api_response def query_malops( client: Client, total_result_limit: int = None, per_group_limit: int = None, template_context: str = None, filters: list = None, guid_list: Optional[List[str]] = None, ) -> Any: json_body = { "totalResultLimit": int(total_result_limit) if total_result_limit else 10000, "perGroupLimit": int(per_group_limit) if per_group_limit else 10000, "perFeatureLimit": 100, "templateContext": template_context or "MALOP", "queryPath": [{"requestedType": None, "guidList": guid_list, "result": True, "filters": filters}], } # By Cybereason documentation - Inorder to get all malops, The client should send 2 requests as follow: # First request - "MalopProcess" json_body["queryPath"][0]["requestedType"] = "MalopProcess" # type: ignore malop_process_type = client.cybereason_api_call("POST", "/rest/crimes/unified", json_body=json_body) # Second request - "MalopLogonSession" json_body["queryPath"][0]["requestedType"] = "MalopLogonSession" # type: ignore malop_loggon_session_type = client.cybereason_api_call("POST", "/rest/crimes/unified", json_body=json_body) return malop_process_type, malop_loggon_session_type def isolate_machine_command(client: Client, args: dict): machine = str(args.get("machine")) response, pylum_id = isolate_machine(client, machine) result = response.get(pylum_id) if result == "Succeeded": return [ CommandResults( readable_output="Machine was isolated successfully.", outputs_prefix="Cybereason", outputs_key_field="Machine", outputs={"Machine": machine, "IsIsolated": True}, ) ] else: raise Exception("Failed to isolate machine.") def isolate_machine(client: Client, machine: str) -> Any: pylum_id = get_pylum_id(client, machine) cmd_url = "/rest/monitor/global/commands/isolate" json_body = {"pylumIds": [pylum_id]} response = client.cybereason_api_call("POST", cmd_url, json_body=json_body) return response, pylum_id def unisolate_machine_command(client: Client, args: dict): machine = str(args.get("machine")) response, pylum_id = unisolate_machine(client, machine) result = response.get(pylum_id) if result == "Succeeded": return [ CommandResults( readable_output="Machine was un-isolated successfully.", outputs_prefix="Cybereason", outputs_key_field="Machine", outputs={"Machine": machine, "IsIsolated": False}, ) ] else: raise Exception("Failed to un-isolate machine.") def unisolate_machine(client: Client, machine: str) -> Any: pylum_id = get_pylum_id(client, machine) cmd_url = "/rest/monitor/global/commands/un-isolate" json_body = {"pylumIds": [pylum_id]} response = client.cybereason_api_call("POST", cmd_url, json_body=json_body) return response, pylum_id def malop_processes_command(client: Client, args: dict): malop_guids = args.get("malopGuids") machine_name = str(args.get("machineName")) date_time = str(args.get("dateTime")) milliseconds = 0 filter_input = [] if date_time != "None": date_time_parser = dateparser.parse(date_time) if not date_time_parser: demisto.info("Returning all the processes since the entered date is not valid.") if date_time_parser: epoch_time = date_time_parser.timestamp() milliseconds = int(epoch_time) * 1000 filter_input = [{"facetName": "creationTime", "filterType": "GreaterThan", "values": [milliseconds], "isResult": True}] if isinstance(malop_guids, str): malop_guids = malop_guids.split(",") elif not isinstance(malop_guids, list): raise Exception("malopGuids must be array of strings") machine_name_list = [machine.lower() for machine in argToList(machine_name)] response = malop_processes(client, malop_guids, filter_input) elements = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) outputs = [] for item in list(elements.values()): simple_values = dict_safe_get(item, ["simpleValues"], default_return_value={}, return_type=dict) element_values = dict_safe_get(item, ["elementValues"], default_return_value={}, return_type=dict) if machine_name_list != ["none"]: machine_list = dict_safe_get( element_values, ["ownerMachine", "elementValues"], default_return_value=[], return_type=list ) wanted_machine = False for machine in machine_list: current_machine_name = machine.get("name", "").lower() if current_machine_name in machine_name_list: wanted_machine = True break if not wanted_machine: continue output = {} for info in PROCESS_INFO: if info.get("type", "") == "filterData": output[info["header"]] = dict_safe_get(item, ["filterData", "groupByValue"]) output = update_output(output, simple_values, element_values, PROCESS_INFO) outputs.append(output) else: output = {} for info in PROCESS_INFO: if info.get("type", "") == "filterData": output[info["header"]] = dict_safe_get(item, ["filterData", "groupByValue"]) output = update_output(output, simple_values, element_values, PROCESS_INFO) outputs.append(output) context = [] for output in outputs: # Remove whitespaces from dictionary keys context.append({key.translate({32: None}): value for key, value in output.items()}) demisto.debug(f"context, {context}") demisto.debug(f"outputs, {outputs}") return CommandResults( readable_output=tableToMarkdown("Cybereason Malop Processes", outputs, headers=PROCESS_HEADERS, removeNull=True), outputs_prefix="Cybereason.Process", outputs_key_field="Name", outputs=context, ) def malop_processes(client: Client, malop_guids: list, filter_value: list) -> dict: json_body = { "queryPath": [ { "requestedType": "MalopProcess", "filters": [], "guidList": malop_guids, "connectionFeature": {"elementInstanceType": "MalopProcess", "featureName": "suspects"}, }, {"requestedType": "Process", "filters": filter_value, "isResult": True}, ], "totalResultLimit": 1000, "perGroupLimit": 1200, "perFeatureLimit": 1200, "templateContext": "MALOP", "queryTimeout": None, } return client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) def add_comment_command(client: Client, args: dict): comment = str(args.get("comment")) if args.get("comment") else "" malop_guid = str(args.get("malopGuid")) try: add_comment(client, malop_guid, comment.encode("utf-8")) return CommandResults(readable_output="Comment added successfully") except Exception as e: raise Exception("Failed to add new comment. Orignal Error: " + str(e)) def add_comment(client: Client, malop_guid: str, comment: Any): cmd_url = "/rest/crimes/comment/" + malop_guid client.cybereason_api_call("POST", cmd_url, data=comment, return_json=False) def update_malop_status_command(client: Client, args: dict): status = str(args.get("status")) malop_guid = str(args.get("malopGuid")) if status not in STATUS_MAP: raise Exception("Invalid status. Given status must be one of the following: To Review,Unread,Remediated or Not Relevant") update_malop_status(client, malop_guid, status) return CommandResults( readable_output=f"Successfully updated malop {malop_guid} to status {status}", outputs_prefix="Cybereason.Malops", outputs_key_field="GUID", outputs={"GUID": malop_guid, "Status": status}, ) def update_malop_status(client: Client, malop_guid: str, status: str) -> None: api_status = STATUS_MAP[status] json_body = {malop_guid: api_status} response = client.cybereason_api_call("POST", "/rest/crimes/status", json_body=json_body) if response["status"] != "SUCCESS": raise Exception(f"Failed to update malop {malop_guid} status to {status}. Message: {response['message']}") def prevent_file_command(client: Client, args: dict): file_hash = str(args.get("md5")) if args.get("md5") else "" response = prevent_file(client, file_hash) if response["outcome"] == "success": return CommandResults( readable_output="File was prevented successfully", outputs_prefix="Cybereason.Process", outputs_key_field="MD5", outputs={"MD5": file_hash, "Prevent": True}, ) else: raise Exception("Failed to prevent file") def prevent_file(client: Client, file_hash: str) -> dict: json_body = [{"keys": [file_hash], "maliciousType": "blacklist", "remove": False, "prevent": True}] return client.cybereason_api_call("POST", "/rest/classification/update", json_body=json_body) def unprevent_file_command(client: Client, args: dict): file_hash = str(args.get("md5")) response = unprevent_file(client, file_hash) if response["outcome"] == "success": return CommandResults( readable_output="File was unprevented successfully", outputs_prefix="Cybereason.Process", outputs_key_field="MD5", outputs={"MD5": file_hash, "Prevent": False}, ) else: raise Exception("Failed to unprevent file") def unprevent_file(client: Client, file_hash: str) -> dict: json_body = [{"keys": [str(file_hash)], "remove": True, "prevent": False}] return client.cybereason_api_call("POST", "/rest/classification/update", json_body=json_body) def available_remediation_actions_command(client: Client, args: dict): malop_guid = args.get("malopGuid") json_body = {"detectionEventMalopGuids": [], "processMalopGuids": [malop_guid]} response = client.cybereason_api_call("POST", "/rest/detection/custom-remediation", json_body=json_body) if response: cybereason_outputs = [] data_list = dict_safe_get(response, ["data"], default_return_value={}, return_type=list) if data_list: for data in data_list: uniqueId = data["uniqueId"] remediationType = data["remediationType"] targetName = data["targetName"] targetID = data["targetId"] machineName = data["machineName"] malopType = data["malopType"] malopId = data["malopId"] machineConnected = data["machineConnected"] cybereason_outputs.append( { "UniqueId": uniqueId, "RemediationType": remediationType, "TargetName": targetName, "TargetID": targetID, "MachineName": machineName, "MalopType": malopType, "MalopId": malopId, "MachineConnected": machineConnected, } ) return CommandResults( readable_output=tableToMarkdown( f"Cybereason available remediation actions for malop {malop_guid}:", cybereason_outputs, removeNull=False ), outputs_prefix="Cybereason.Remediation", outputs_key_field="TargetID", outputs=cybereason_outputs, ) def kill_process_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Kill Process Remediation Action Succeeded" remediation_action = "KILL_PROCESS" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Kill process remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Kill process remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def quarantine_file_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Quarantine File Remediation Action Succeeded" remediation_action = "QUARANTINE_FILE" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Quarantine file remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Quarantine file remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def unquarantine_file_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Unquarantine File Remediation Action Succeded" remediation_action = "UNQUARANTINE_FILE" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Unquarantine file remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Unquarantine file remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def block_file_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Block File Remediation Action Succeeded" remediation_action = "BLOCK_FILE" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Block file remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Block file remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def delete_registry_key_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Delete Registry Key Remediation Action Succeeded" remediation_action = "DELETE_REGISTRY_KEY" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Delete registry key remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Delete registry key remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def kill_prevent_unsuspend_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Kill Prevent Unsuspend Remediation Action Succeeded" remediation_action = "KILL_PREVENT_UNSUSPEND" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Kill prevent unsuspend remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Kill prevent unsuspend remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n" Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def unsuspend_process_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) machine_name = str(args.get("machine")) target_id = str(args.get("targetId")) user_name = str(args.get("userName")) comment = str(args.get("comment")) if args.get("comment") else "Unsuspend Process Remediation Action Succeeded" remediation_action = "UNSUSPEND_PROCESS" is_machine_connected = is_probe_connected_command(client, args, is_remediation_command=True) if is_machine_connected is True: response = get_remediation_action(client, malop_guid, machine_name, target_id, remediation_action) action_status = get_remediation_action_status(client, user_name, malop_guid, response, comment) if dict_safe_get(action_status, ["Remediation status"]) == "SUCCESS": success_response = f"""Unsuspend process remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" return CommandResults(readable_output=success_response) elif dict_safe_get(action_status, ["Remediation status"]) == "FAILURE": failure_response = f"""Unsuspend process remediation action status is: {dict_safe_get( action_status, ['Remediation status'])} \n Reason: {dict_safe_get( action_status, ['Reason'])} \n Remediation ID: {dict_safe_get(action_status, ['Remediation ID'])}""" raise DemistoException(failure_response) return None else: raise DemistoException("Machine must be connected to Cybereason in order to perform this action.") def get_remediation_action(client: Client, malop_guid: str, machine_name: str, target_id: str, remediation_action: str) -> dict: machine_guid = get_machine_guid(client, machine_name) json_body = { "malopId": malop_guid, "actionsByMachine": {machine_guid: [{"targetId": target_id, "actionType": remediation_action}]}, } return client.cybereason_api_call("POST", "/rest/remediate", json_body=json_body) def get_remediation_action_status(client: Client, user_name: str, malop_guid: str, response: dict, comment: str) -> dict: remediation_id = dict_safe_get(response, ["remediationId"]) progress_api_response = get_remediation_action_progress(client, user_name, malop_guid, remediation_id) status = dict_safe_get(progress_api_response, ["Remediation status"]) if status == "SUCCESS": add_comment(client, malop_guid, comment.encode("utf-8")) progress_api_response["Remediation ID"] = remediation_id return progress_api_response def get_remediation_action_progress(client: Client, username: str, malop_id: str, remediation_id: str) -> dict: final_response = "" final_response = client.cybereason_api_call( "GET", "/rest/remediate/progress/" + username + "/" + str(malop_id) + "/" + remediation_id, retries=3, backoff_factor=5 ) statusLog_lenght = len(dict_safe_get(final_response, ["statusLog"])) if statusLog_lenght == 0: raise Exception("The given target ID is incorrect.") else: statusLog_final_response = dict_safe_get(final_response, ["statusLog", statusLog_lenght - 1]) statusLog_final_error = dict_safe_get(statusLog_final_response, ["error"]) statusLog_final_status = dict_safe_get(statusLog_final_response, ["status"]) if statusLog_final_error is None: return {"Remediation status": statusLog_final_status} else: return {"Remediation status": statusLog_final_status, "Reason": dict_safe_get(statusLog_final_error, ["message"])} def query_file_command(client: Client, args: dict) -> Any: file_hash_list = argToList(args.get("file_hash")) for file_hash in file_hash_list: filters = [] hash_type = get_hash_type(file_hash) if hash_type == "sha1": filters.append({"facetName": "sha1String", "values": [file_hash], "filterType": "ContainsIgnoreCase"}) elif hash_type == "md5": filters.append({"facetName": "md5String", "values": [file_hash], "filterType": "ContainsIgnoreCase"}) else: raise Exception("Hash type is not supported.") data = query_file(client, filters) if data: cybereason_outputs = [] files = dict_safe_get(data, ["resultIdToElementDataMap"], {}, dict) for fname, fstat in files.items(): raw_machine_details = dict_safe_get( get_file_machine_details(client, fname), ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict, ) machine_details = dict_safe_get( raw_machine_details, dict_safe_get(list(raw_machine_details.keys()), [0]), default_return_value={}, return_type=dict, ) simple_values = dict_safe_get(fstat, ["simpleValues"], default_return_value={}, return_type=dict) file_name = dict_safe_get(simple_values, ["elementDisplayName", "values", 0]) md5 = dict_safe_get(simple_values, ["md5String", "values", 0]) sha1 = dict_safe_get(simple_values, ["sha1String", "values", 0]) path = dict_safe_get(simple_values, ["correctedPath", "values", 0]) machine = dict_safe_get(fstat, ["elementValues", "ownerMachine", "elementValues", 0, "name"]) machine_element_values = dict_safe_get( machine_details, ["elementValues"], default_return_value={}, return_type=dict ) machine_simple_values = dict_safe_get( machine_details, ["simpleValues"], default_return_value={}, return_type=dict ) os_version = dict_safe_get(machine_simple_values, ["ownerMachine.osVersionType", "values", 0]) raw_suspicions = dict_safe_get(machine_details, ["suspicions"], default_return_value={}, return_type=dict) suspicions = {} for key, value in raw_suspicions.items(): suspicions[key] = timestamp_to_datestring(value) evidences = [] for key in list(machine_element_values.keys()): if "evidence" in key.lower(): evidences.append(key) for key in list(machine_simple_values.keys()): if "evidence" in key.lower(): evidences.append(key) company_name = None if "companyName" in simple_values: company_name = dict_safe_get(simple_values, ["companyName", "values", 0]) created_time = None if "createdTime" in simple_values: created_time = timestamp_to_datestring(dict_safe_get(simple_values, ["createdTime", "values", 0])) modified_time = None if "modifiedTime" in simple_values: modified_time = timestamp_to_datestring(dict_safe_get(simple_values, ["modifiedTime", "values", 0])) is_signed = None if "isSigned" in simple_values: is_signed = dict_safe_get(simple_values, ["isSigned", "values", 0]) == "true" cybereason_outputs.append( { "Name": file_name, "CreationTime": created_time, "ModifiedTime": modified_time, "Malicious": fstat.get("isMalicious"), "MD5": md5, "SHA1": sha1, "Path": path, "Machine": machine, "SuspicionsCount": machine_details.get("suspicionCount"), "IsConnected": ( dict_safe_get(machine_simple_values, ["ownerMachine.isActiveProbeConnected", "values", 0]) == "true" ), "OSVersion": os_version, "Suspicion": suspicions, "Evidence": evidences, "Signed": is_signed, "Company": company_name, } ) return CommandResults( readable_output=tableToMarkdown( f"Cybereason file query results for the file hash: {file_hash}", cybereason_outputs, removeNull=True ), outputs_prefix="Cybereason.File", outputs_key_field="Name", outputs=cybereason_outputs, ) else: raise DemistoException("No results found.") return None def query_file(client: Client, filters: list) -> dict: query_fields = [ "md5String", "ownerMachine", "avRemediationStatus", "isSigned", "signatureVerified", "sha1String", "maliciousClassificationType", "createdTime", "modifiedTime", "size", "correctedPath", "productName", "productVersion", "companyName", "internalName", "elementDisplayName", ] path = [{"requestedType": "File", "filters": filters, "isResult": True}] json_body = build_query(query_fields, path) response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) if response.get("status") == "SUCCESS" and "data" in response: return response["data"] else: raise Exception("Error occurred while trying to query the file.") def get_file_machine_details(client: Client, file_guid: str) -> dict: query_fields = [ "ownerMachine", "self", "elementDisplayName", "correctedPath", "canonizedPath", "mount", "mountedAs", "createdTime", "modifiedTime", "md5String", "sha1String", "productType", "companyName", "productName", "productVersion", "signerInternalOrExternal", "signedInternalOrExternal", "signatureVerifiedInternalOrExternal", "signedByMicrosoft", "extensionType", "size", "avRemediationStatus", "classificationDetectionName", "avScanTime", "relatedToMalop", "isSuspicious", "maliciousClassificationType", "classificationBlocking", "isDownloadedFromInternet", "downloadedFromDomain", "downloadedFromIpAddress", "downloadedFromUrl", "downloadedFromUrlReferrer", "downloadedFromEmailFrom", "downloadedFromEmailMessageId", "downloadedFromEmailSubject", "ownerMachine.isActiveProbeConnected", "ownerMachine.osVersionType", "quarantineVersion", "originalVersion", ] path = [{"requestedType": "File", "guidList": [file_guid], "result": True}] json_body = build_query(query_fields, path, template_context="DETAILS") return client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) def query_domain_command(client: Client, args: dict) -> Any: domain_list = argToList(args.get("domain")) for domain_input in domain_list: filters = [{"facetName": "elementDisplayName", "values": [domain_input], "filterType": "ContainsIgnoreCase"}] data = query_domain(client, filters) if data: cybereason_outputs = [] domains = dict_safe_get(data, ["resultIdToElementDataMap"], default_return_value={}, return_type=dict) for domain in list(domains.values()): if not isinstance(domain, dict): raise ValueError("Cybereason raw response is not valid, domain in domains.values() is not dict") simple_values = dict_safe_get(domain, ["simpleValues"], default_return_value={}, return_type=dict) reputation = dict_safe_get(simple_values, ["maliciousClassificationType", "values", 0]) is_internal_domain = dict_safe_get(simple_values, ["isInternalDomain", "values", 0]) == "true" was_ever_resolved = dict_safe_get(simple_values, ["everResolvedDomain", "values", 0]) == "true" was_ever_resolved_as = dict_safe_get(simple_values, ["everResolvedSecondLevelDomain", "values", 0]) == "true" malicious = domain.get("isMalicious") suspicions_count = domain.get("suspicionCount") cybereason_outputs.append( { "Name": domain_input, "Reputation": reputation, "Malicious": malicious, "SuspicionsCount": suspicions_count, "IsInternalDomain": is_internal_domain, "WasEverResolved": was_ever_resolved, "WasEverResolvedAsASecondLevelDomain": was_ever_resolved_as, } ) return CommandResults( readable_output=tableToMarkdown( f"Cybereason domain query results for the domain: {domain_input}", cybereason_outputs, headers=DOMAIN_HEADERS ), outputs_prefix="Cybereason.Domain", outputs_key_field="Name", outputs=cybereason_outputs, ) else: raise DemistoException("No results found.") return None def query_domain(client: Client, filters: list) -> dict: query_fields = [ "maliciousClassificationType", "isInternalDomain", "everResolvedDomain", "everResolvedSecondLevelDomain", "elementDisplayName", ] path = [{"requestedType": "DomainName", "filters": filters, "isResult": True}] json_body = build_query(query_fields, path) response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) if response.get("status", "") == "SUCCESS" and "data" in response: return response["data"] else: raise Exception("Error occurred while trying to query the file.") def query_user_command(client: Client, args: dict): username_list = argToList(args.get("username")) for username in username_list: filters = [{"facetName": "elementDisplayName", "values": [username], "filterType": "ContainsIgnoreCase"}] data = query_user(client, filters) if data: cybereason_outputs = [] users = dict_safe_get(data, ["resultIdToElementDataMap"], default_return_value={}, return_type=dict) for user in list(users.values()): simple_values = dict_safe_get(user, ["simpleValues"], default_return_value={}, return_type=dict) element_values = dict_safe_get(user, ["elementValues"], default_return_value={}, return_type=dict) domain = dict_safe_get(simple_values, ["domain", "values", 0]) local_system = dict_safe_get(simple_values, ["isLocalSystem", "values", 0]) == "true" machine = dict_safe_get(element_values, ["ownerMachine", "elementValues", 0, "name"]) organization = dict_safe_get(element_values, ["ownerOrganization", "elementValues", 0, "name"]) cybereason_outputs.append( { "Username": username, "Domain": domain, "LastMachineLoggedInTo": machine, "Organization": organization, "LocalSystem": local_system, } ) return CommandResults( readable_output=tableToMarkdown( f"Cybereason user query results for the username: {username}", cybereason_outputs, headers=USER_HEADERS ), outputs_prefix="Cybereason.User", outputs_key_field="Username", outputs=cybereason_outputs, ) else: raise DemistoException("No results found.") return None def query_user(client: Client, filters: list) -> dict: query_fields = ["domain", "ownerMachine", "ownerOrganization", "isLocalSystem", "elementDisplayName"] path = [{"requestedType": "User", "filters": filters, "isResult": True}] json_body = build_query(query_fields, path) response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) if response.get("status", "") == "SUCCESS" and "data" in response: return response["data"] else: raise Exception("Error occurred while trying to query the file.") def archive_sensor_command(client: Client, args: dict): sensor_id = args.get("sensorID") archive_reason = args.get("archiveReason") data = {"sensorsIds": [sensor_id], "argument": archive_reason} response = client.cybereason_api_call( "POST", "/rest/sensors/action/archive", json_body=data, return_json=False, custom_response=True ) if response.status_code == 204: output = f"The selected Sensor with Sensor ID: {sensor_id} is not available for archive." elif response.status_code == 200: output = "" try: response_json = response.json() output = "Sensor archive status: " output += "Failed Actions: " + str(response_json["globalStats"]["stats"]["Failed"]) + ". " output += "Succeeded Actions: " + str(response_json["globalStats"]["stats"]["Succeeded"]) except Exception as e: raise Exception("Exception occurred while processing response for Archive action: " + str(e)) else: try: json_response = response.json() raise DemistoException(f"Could not archive Sensor. The received response is {json_response}") except Exception: raise Exception( "Your request failed with the following error: " + response.content + ". Response Status code: " + str(response.status_code) ) return CommandResults(readable_output=output) def unarchive_sensor_command(client: Client, args: dict): sensor_id = args.get("sensorID") unarchive_reason = args.get("unarchiveReason") data = {"sensorsIds": [sensor_id], "argument": unarchive_reason} response = client.cybereason_api_call( "POST", "/rest/sensors/action/unarchive", json_body=data, return_json=False, custom_response=True ) if response.status_code == 204: output = f"The selected Sensor with Sensor ID: {sensor_id} is not available for unarchive." elif response.status_code == 200: output = "" try: response_json = response.json() output = "Sensor unarchive status: " output += "Failed Actions: " + str(response_json["globalStats"]["stats"]["Failed"]) + ". " output += "Succeeded Actions: " + str(response_json["globalStats"]["stats"]["Succeeded"]) except Exception as e: raise Exception("Exception occurred while processing response for Unarchive action: " + str(e)) else: try: json_response = response.json() raise DemistoException(f"Could not unarchive Sensor. The received response is {json_response}") except Exception: raise Exception( "Your request failed with the following error: " + response.content + ". Response Status code: " + str(response.status_code) ) return CommandResults(readable_output=output) def delete_sensor_command(client: Client, args: dict): sensor_id = args.get("sensorID") data = {"sensorsIds": [sensor_id]} response = client.cybereason_api_call( "POST", "/rest/sensors/action/delete", json_body=data, return_json=False, custom_response=True ) if response.status_code == 204: output = f"The selected Sensor with Sensor ID: {sensor_id} is not available for deleting." elif response.status_code == 200: output = "Sensor deleted successfully." else: try: json_response = response.json() raise DemistoException(f"Could not delete Sensor. The received response is {json_response}") except Exception: raise Exception( "Your request failed with the following error: " + response.content + ". Response Status code: " + str(response.status_code) ) return CommandResults(readable_output=output) def malop_to_incident(malop: dict) -> dict: if not isinstance(malop, dict): raise ValueError("Cybereason raw response is not valid, malop is not dict") status = 0 malopStatus = "" if malop.get("status", ""): malopStatus = malop.get("status", "UNREAD") elif malop.get("simpleValues", ""): malopStatus = malop.get("simpleValues", {}).get("managementStatus", {}).get("values", ["UNREAD"])[0] if (malopStatus == "Remediated") or (malopStatus == "TODO"): status = 1 elif (malopStatus == "Closed") or (malopStatus == "RESOLVED"): status = 2 else: status = 0 guid_string = malop.get("guidString", "") if not guid_string: guid_string = malop.get("guid", "") if malop.get("isEdr", "") or malop.get("edr", "") or malop.get("simpleValues", ""): link = SERVER + "/#/malop/" + guid_string isEdr = True else: link = SERVER + "/#/detection-malop/" + guid_string isEdr = False if simple_values := malop.get("simpleValues"): malopCreationTime = simple_values.get("creationTime", {}).get("values", ["2010-01-01"])[0] malopUpdateTime = simple_values.get("malopLastUpdateTime", {}).get("values", ["2010-01-01"])[0] else: malopCreationTime = str(malop.get("creationTime", "2010-01-01")) malopUpdateTime = str(malop.get("lastUpdateTime", "2010-01-01")) if element_values := malop.get("elementValues"): if root_cause_elements := element_values.get("primaryRootCauseElements", {}).get("elementValues", []): rootCauseElementName = root_cause_elements[0].get("name", "") rootCauseElementType = root_cause_elements[0].get("elementType", "") else: rootCauseElementName = "" rootCauseElementType = "" else: rootCauseElementName = malop.get("primaryRootCauseName", "") rootCauseElementType = malop.get("rootCauseElementType", "") if malop_detection_type := malop.get("malopDetectionType"): detectionType = malop_detection_type else: detectionType = malop.get("simpleValues", {}).get("detectionType", {}).get("values", [""])[0] malopGroup = malop.get("group", "") severity = malop.get("severity", "") incident = { "rawjson": json.dumps(malop), "name": "Cybereason Malop " + guid_string, "dbotmirrorid": guid_string, "CustomFields": { "malopcreationtime": malopCreationTime, "malopupdatetime": malopUpdateTime, "maloprootcauseelementname": rootCauseElementName, "maloprootcauseelementtype": rootCauseElementType, "malopseverity": severity, "malopdetectiontype": detectionType, "malopedr": isEdr, "malopurl": link, "malopgroup": malopGroup, }, "labels": [{"type": "GUID", "value": guid_string}], "status": status, } return incident def fetch_incidents(client: Client): last_run = demisto.getLastRun() if last_run and last_run.get("creation_time"): last_update_time = int(last_run.get("creation_time")) else: # In first run last_update_time, _ = parse_date_range(FETCH_TIME, to_timestamp=True) max_update_time = int(last_update_time) if FETCH_BY == "MALOP UPDATE TIME": pass elif FETCH_BY == "MALOP CREATION TIME": pass else: raise Exception("Given filter to fetch by is invalid.") integration_context = get_integration_context() valid_until = integration_context.get("valid_until") demisto.debug(f"Fetch Incidents: Token is valid until: {valid_until}") end_time = integration_context.get("end_time") start_time = integration_context.get("start_time") offset = integration_context.get("offset") total_malops_fetched = integration_context.get("total_malops_fetched") if not start_time: start_time = last_update_time if not end_time: end_time = int(datetime.now().timestamp()) * 1000 if not offset: offset = 0 if not total_malops_fetched: total_malops_fetched = 0 demisto.info( f"Fetch Incidents: Polling started..... " f"offset: {offset}, " f"total_malops_fetched: {total_malops_fetched}, " f"start_time {start_time}, " f"end_time {end_time}" ) malop_management_response = get_malop_management_data(client, start_time, end_time, offset) edr_guid_list, non_edr_guid_list = [], [] total_malops_available = malop_management_response.get("data", {}).get("totalHits", 0) malop_management_response = malop_management_response.get("data", {}).get("data", []) malop_count_per_poll = len(malop_management_response) demisto.info( f"Fetch Incidents: Malop stats: " f"Malop per paginated call {malop_count_per_poll}. " f"Malops per polling cycle {total_malops_available}" ) total_malops_fetched += malop_count_per_poll for malop in malop_management_response: if malop.get("isEdr"): edr_guid_list.append(malop["guid"]) else: non_edr_guid_list.append(malop["guid"]) demisto.info(f"Fetch Incidents: EDR GUID list: {edr_guid_list}") demisto.info(f"Fetch Incidents: Non-EDR GUID List: {non_edr_guid_list}") incidents = [] if edr_guid_list: malop_process_type, malop_loggon_session_type = query_malops( client, total_result_limit=10000, per_group_limit=10000, guid_list=edr_guid_list ) for response in (malop_process_type, malop_loggon_session_type): malops = dict_safe_get(response, ["data", "resultIdToElementDataMap"], default_return_value={}, return_type=dict) for malop in list(malops.values()): simple_values = dict_safe_get(malop, ["simpleValues"], default_return_value={}, return_type=dict) simple_values.pop("iconBase64", None) simple_values.pop("malopActivityTypes", None) malop_update_time = int(dict_safe_get(simple_values, ["malopLastUpdateTime", "values", 0])) if int(malop_update_time) > int(max_update_time): max_update_time = malop_update_time guid_string = malop.get("guidString", "") if not guid_string: guid_string = malop.get("guid", "") try: incident = malop_to_incident(malop) except Exception: demisto.debug(f"Fetch Incidents: EDR Malop conversion to XSOAR Incident failed with Malop ID {guid_string}") continue incidents.append(incident) demisto.info(f"Fetch Incidents: EPP Malop conversion to XSOAR Incident succeded with Malop ID {guid_string}") if IS_EPP_ENABLED: if non_edr_guid_list: demisto.info("Fetch Incidents: Starting to process for EPP Malops...") for non_edr_malop in non_edr_guid_list: detection_detail_response = get_detection_details(client, non_edr_malop) try: incident = malop_to_incident(detection_detail_response) malop_update_time = detection_detail_response.get("lastUpdateTime", "") if int(malop_update_time) > int(max_update_time): max_update_time = malop_update_time except Exception: demisto.debug(f"Fetch Incidents: EPP Malop conversion to XSOAR Incident failed with Malop ID {non_edr_malop}") continue incidents.append(incident) demisto.info(f"Fetch Incidents: EPP Malop conversion to XSOAR Incident succeded with Malop ID {non_edr_malop}") else: demisto.info("Fetch Incidents: No EPP Malops found while polling") else: demisto.info("Fetch Incidents: EPP Malop Polling not enabled, hence skipping the processing of EPP Malops") # Adding one microsecond delay to avoid ingesting duplicates max_update_time += 1 demisto.setLastRun({"creation_time": max_update_time}) demisto.incidents(incidents) if total_malops_fetched < total_malops_available: offset += malop_count_per_poll demisto.debug( f"Fetch Incidents: Total malop fetched: {total_malops_fetched} " f"is less than total malops available: {total_malops_available}. " f"Updating offset to {offset}" ) else: offset = 0 demisto.debug( f"Fetch Incidents: All Malops polled, " f"no new Malops available in next page. " f"Current stats: Total Malops Available: {total_malops_available} " f"Total Malops Fetched: {total_malops_fetched}" ) start_time = "" end_time = "" total_malops_fetched = "" integration_context["total_malops_fetched"] = total_malops_fetched integration_context["start_time"] = str(start_time) integration_context["end_time"] = end_time integration_context["offset"] = offset set_integration_context(integration_context) demisto.debug("Fetch Incidents: Integration context data saved successfully for Polling cycle") def login(client: Client): demisto.debug("login: function initiated") headers = { "Content-Type": "application/x-www-form-urlencoded", "Connection": "close", } data = {"username": USERNAME, "password": PASSWORD} client.cybereason_api_call("POST", "/login.html", data=data, headers=headers, custom_response=True, return_json=False) JSESSIONID = client._session.cookies.get("JSESSIONID") creation_time = int(time.time()) return JSESSIONID, creation_time def validate_jsession(client: Client, explicit_refresh=False): current_time = int(time.time()) integration_context = get_integration_context() token = integration_context.get("jsession_id") valid_until = integration_context.get("valid_until") demisto.debug(f"Validate JSESSION: Token is valid until: {valid_until} and explicit token refresh: {explicit_refresh}") should_refresh = explicit_refresh or not token or not valid_until or current_time >= valid_until demisto.debug(f"Value of Should Refresh: {should_refresh}. Token {token}") if not should_refresh: # Token is still valid and no explicit refresh requested, so use existing token demisto.debug("Token is still valid - did not expire") HEADERS["Cookie"] = f"JSESSIONID={token}" return demisto.debug("Token expired, missing, or explicit refresh requested. Logging in to get a new token.") new_token, login_time = login(client) integration_context["jsession_id"] = new_token integration_context["valid_until"] = login_time + TOKEN_VALIDITY_PERIOD_SECONDS set_integration_context(integration_context) HEADERS["Cookie"] = f"JSESSIONID={new_token}" def client_certificate(): cert = CERTIFICATE if "Bag Attributes" not in cert: raise Exception("Could not find Bag Attributes") if "-----BEGIN CERTIFICATE-----" not in cert: raise Exception("Could not find certificate file") if "-----BEGIN RSA PRIVATE KEY-----" in cert: # guardrails-disable-line i = cert.index("-----BEGIN RSA PRIVATE KEY-----") # guardrails-disable-line else: raise Exception("Could not find certificate key") client_cert = cert[:i] client_key = cert[i:] f = open("client.cert", "wb") f.write(client_cert) f.flush() f.close() f = open("client.pem", "wb") f.write(client_key) f.close() client_cert_file = os.path.abspath("client.cert") client_key_file = os.path.abspath("client.pem") session.cert = (client_cert_file, client_key_file) # Time to check if we are logged on response = session.get(url=SERVER) if response.status_code != 200 and response.status_code != 302: raise Exception("Failed to connect to server") # First time we may get a redirect, but second time should be 200 response = session.get(url=SERVER) if response.status_code != 200: raise Exception("Failed to login with certificate. Expected response 200. Got: " + str(response.status_code)) def logout(client: Client): demisto.debug("Logout function is getting called") client.cybereason_api_call("GET", "/logout", return_json=False) """ EXECUTION CODE """ LOG(f"command is {demisto.command()}") session = requests.session() def get_file_guids(client: Client, malop_id: str) -> dict: """Get all File GUIDs for the given malop""" processes = fetch_malop_processes(client, malop_id) img_file_guids = fetch_imagefile_guids(client, processes) return img_file_guids def fetch_malop_processes(client: Client, malop_id: str) -> list: json_body = { "queryPath": [ { "requestedType": "MalopProcess", "filters": [], "guidList": [malop_id], "connectionFeature": {"elementInstanceType": "MalopProcess", "featureName": "suspects"}, }, {"requestedType": "Process", "filters": [], "isResult": True}, ], "totalResultLimit": 1000, "perGroupLimit": 1200, "perFeatureLimit": 1200, "templateContext": "DETAILS", "queryTimeout": None, "customFields": ["maliciousByDualExtensionByFileRootCause", "creationTime", "endTime", "elementDisplayName"], } response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) try: result = response["data"]["resultIdToElementDataMap"] except Exception as e: raise ValueError(f"Exception when parsing JSON response: {str(e)}") return list(result.keys()) def fetch_imagefile_guids(client: Client, processes: list) -> dict: json_body = { "queryPath": [{"requestedType": "Process", "guidList": processes, "result": True}], "totalResultLimit": 1000, "perGroupLimit": 1000, "perFeatureLimit": 100, "templateContext": "DETAILS", "customFields": [ "ownerMachine", "calculatedUser", "parentProcess", "execedBy", "service", "self", "openedFiles", "children", "elementDisplayName", "applicablePid", "tid", "creationTime", "firstSeenTime", "lastSeenTime", "endTime", "commandLine", "decodedCommandLine", "imageFilePath", "iconBase64", "isAggregate", "isServiceHost", "isDotNetProtected", "imageFile", "imageFile.extensionType", "imageFile.correctedPath", "imageFile.sha1String", "imageFile.md5String", "imageFile.productType", "imageFile.companyName", "imageFile.productName", "imageFile.signerInternalOrExternal", "imageFile.avRemediationStatus", "imageFile.comments", "fileAccessEvents", "registryEvents", "hookedFunctions", "productType", "imageFile.signedInternalOrExternal", "imageFile.signatureVerifiedInternalOrExternal", "imageFile.maliciousClassificationType", "imageFile.isDownloadedFromInternet", "imageFile.downloadedFromDomain", "imageFile.downloadedFromIpAddress", "imageFile.downloadedFromUrl", "imageFile.downloadedFromUrlReferrer", "imageFile.downloadedFromEmailFrom", "imageFile.downloadedFromEmailMessageId", "imageFile.downloadedFromEmailSubject", "ownerMachine.isActiveProbeConnected", "ownerMachine.osType", "ownerMachine.osVersionType", "ownerMachine.deviceModel", "childrenCreatedByThread", "failedToAccess", "autorun", "loadedModules", "markedForPrevention", "executionPrevented", "ransomwareAutoRemediationSuspended", "ransomwareAffectedFiles", "totalNumOfInstances", "lastMinuteNumOfInstances", "lastSeenTimeStamp", "cveEventsStr", "isExectuedByWmi", "wmiQueryStrings", "wmiPersistentObjects", "createdByWmi.wmiOperation", "createdByWmi.clientPid", "createdByWmi.isLocal", "createdByWmi.clientProcess", "createdByWmi.clientMachine", "injectionMethod", "originInjector", "hostProcess", "creatorThread", "hostedChildren", "isInjectingProcess", "injectedChildren", "isFullProcessMemoryDump", "creatorProcess", "createdChildren", "seenCreation", "newProcess", "processRatio", "hashRatio", "connections", "listeningConnections", "externalConnections", "internalConnections", "localConnections", "dynamicConfigurationConnections", "incomingConnections", "outgoingConnections", "absoluteHighVolumeExternalConnections", "totalNumberOfConnections", "totalTransmittedBytes", "totalReceivedBytes", "resolvedDnsQueriesDomainToIp", "resolvedDnsQueriesDomainToDomain", "resolvedDnsQueriesIpToDomain", "unresolvedDnsQueriesFromIp", "unresolvedDnsQueriesFromDomain", "cpuTime", "memoryUsage", "hasVisibleWindows", "integrity", "isHidden", "logonSession", "remoteSession", "isWhiteListClassification", "matchedWhiteListRuleIds", ], } response = client.cybereason_api_call("POST", "/rest/visualsearch/query/simple", json_body=json_body) img_file_guids = {} result = response["data"]["resultIdToElementDataMap"] try: for _process, details in list(result.items()): image_files = ( "" if details["elementValues"]["imageFile"]["elementValues"] is None else details["elementValues"]["imageFile"]["elementValues"] ) for image_file in image_files: img_file_guids[image_file["name"]] = image_file["guid"] # type: ignore[index] except Exception as e: demisto.error(str(e)) return img_file_guids def start_fetchfile_command(client: Client, args: dict): malop_id = str(args.get("malopGUID")) user_name = str(args.get("userName")) response = get_file_guids(client, malop_id) for _filename, file_guid in list(response.items()): api_response = start_fetchfile(client, file_guid, user_name) try: if api_response["status"] == "SUCCESS": return CommandResults(readable_output="Successfully started fetching file for the given malop") except Exception: raise Exception("Failed to start fetch file process") return None def start_fetchfile(client: Client, element_id: str, user_name: str) -> dict: json_body = {"elementGuids": [element_id], "initiatorUserName": user_name} return client.cybereason_api_call("POST", "/rest/fetchfile/start", json_body=json_body) def fetchfile_progress_command(client: Client, args: dict): malop_id = str(args.get("malopGuid")) response = get_file_guids(client, malop_id) new_malop_comments = get_batch_id(client, response) filename = [] status = [] message = [] output_message = [] for item in range(len(new_malop_comments)): filename.append(new_malop_comments[item].get("name")) status.append(new_malop_comments[item].get("isSuccess")) message.append(new_malop_comments[item].get("message")) if status[item] is True: output_message.append("Filename: " + str(filename) + " Status: " + str(status) + " Batch ID: " + str(message)) else: output_message.append(str(message)) return CommandResults( readable_output=str(output_message), outputs_prefix="Cybereason.Download.Progress", outputs_key_field="fileName", outputs={"fileName": filename, "status": status, "batchID": message, "MalopID": malop_id}, ) def get_batch_id(client: Client, suspect_files_guids: dict) -> list: new_malop_comments = [] progress_response = fetchfile_progress(client) result = progress_response for file_status in result["data"]: if file_status["fileName"] in list(suspect_files_guids.keys()) and file_status["succeeded"] is True: batch_id = file_status["batchId"] file_name = file_status["fileName"] new_malop_comments.append({"isSuccess": True, "message": batch_id, "name": file_name}) del suspect_files_guids[file_status["fileName"]] for suspect_file in list(suspect_files_guids.keys()): malop_comment = f"Could not download the file {suspect_file} from source machine, even after waiting for 80 seconds." demisto.info(malop_comment) if not new_malop_comments: raise DemistoException(malop_comment) else: return new_malop_comments def fetchfile_progress(client: Client): return client.cybereason_api_call("GET", "/rest/fetchfile/downloads/progress", retries=3, backoff_factor=5) def download_fetchfile_command(client: Client, args: dict): batch_id = str(args.get("batchID")) response = download_fetchfile(client, batch_id) if response.status_code == 200: file_download = fileResult("download.zip", response.content) return file_download else: error_message = f"request failed with the following error: {response.content} Response Status code:{response.status_code}" raise DemistoException(error_message) def download_fetchfile(client: Client, batch_id: str) -> Any: url = f"/rest/fetchfile/getfiles/{batch_id}" return client.cybereason_api_call("GET", url, custom_response=True, return_json=False) def close_fetchfile_command(client: Client, args: dict): batch_id = str(args.get("batchID")) response = close_fetchfile(client, batch_id) try: if response.json()["status"] == "SUCCESS": return CommandResults(readable_output="Successfully aborts a file download operation that is in progress.") except Exception: raise Exception("The given Batch ID does not exist") def close_fetchfile(client: Client, batch_id: str) -> Any: url = f"/rest/fetchfile/close/{batch_id}" return client.cybereason_api_call("GET", url, custom_response=True, return_json=False) def malware_query_command(client: Client, args: dict): needs_attention = bool(argToBoolean(args.get("needsAttention"))) if args.get("needsAttention") else False malware_type = str(args.get("type")) malware_status = str(args.get("status")) time_stamp = str(args.get("timestamp")) limit_range = arg_to_number(args.get("limit")) if limit_range: if limit_range > 0: filter_response = malware_query_filter(client, needs_attention, malware_type, malware_status, time_stamp, limit_range) return CommandResults(raw_response=filter_response) return None else: raise DemistoException("Limit cannot be zero or a negative number.") def malware_query_filter( client: Client, needs_attention: bool, malware_type: str, malware_status: str, time_stamp: str, limit_range: Optional[int] ) -> dict: query = [] if needs_attention: query.append({"fieldName": "needsAttention", "operator": "Is", "values": [bool(needs_attention)]}) if malware_type != "None": types = malware_type.split(",") query.append({"fieldName": "type", "operator": "Equals", "values": types}) if malware_status != "None": is_status = malware_status.split(",") query.append({"fieldName": "status", "operator": "Equals", "values": is_status}) if time_stamp != "None": query.append({"fieldName": "timestamp", "operator": "GreaterThan", "values": [arg_to_number(time_stamp)]}) response = malware_query(client, query, limit_range) return response def malware_query(client: Client, action_values: list, limit: Optional[int]) -> dict: json_body = {"filters": action_values, "sortingFieldName": "timestamp", "sortDirection": "DESC", "limit": limit, "offset": 0} return client.cybereason_api_call("POST", "/rest/malware/query", json_body=json_body) def start_host_scan_command(client: Client, args: dict): sensor_ids = argToList(args.get("sensorID")) argument = args.get("scanType") json_body = {"sensorsIds": sensor_ids, "argument": argument} response = client.cybereason_api_call( "POST", "/rest/sensors/action/schedulerScan", json_body=json_body, return_json=False, custom_response=True ) if response.status_code == 204: return CommandResults(readable_output=f"Given Sensor ID/ID's {sensor_ids} is/are not available for scanning.") elif response.status_code == 200: try: response_json = response.json() batch_id = dict_safe_get(response_json, ["batchId"]) return CommandResults(readable_output=f"Scanning initiation successful. Batch ID: {batch_id}") except Exception as e: raise Exception("Exception occurred while processing response for scanning a host: " + str(e)) else: try: json_response = response.json() raise DemistoException("Could not scan the host. The received response is" + json_response) except Exception: raise Exception( "Your request failed with the following error: " + response.content + ". Response Status code: " + str(response.status_code) ) def fetch_scan_status_command(client: Client, args: dict): batch_id = str(args.get("batchID")) action_response = client.cybereason_api_call("GET", "/rest/sensors/allActions") output = "The given batch ID does not match with any actions on sensors." for item in action_response: if dict_safe_get(item, ["batchId"]) == int(batch_id): output = item break return CommandResults(raw_response=output) def get_sensor_id_command(client: Client, args: dict): machine_name = str(args.get("machineName")) json_body = {} if machine_name: json_body = {"filters": [{"fieldName": "machineName", "operator": "Equals", "values": [machine_name]}]} response = client.cybereason_api_call("POST", "/rest/sensors/query", json_body=json_body) if dict_safe_get(response, ["sensors"]) == []: raise DemistoException("Could not find any Sensor ID for the machine" + machine_name) else: output = {} for single_sensor in response["sensors"]: output[single_sensor["machineName"]] = single_sensor["sensorId"] return CommandResults(readable_output=f"Available Sensor IDs are {output}") def get_machine_details_command(client: Client, args: dict): machine_name = str(args.get("machineName")) json_body = get_machine_details_command_pagination_params(args) json_body["filters"] = [{"fieldName": "machineName", "operator": "Equals", "values": [machine_name]}] response = client.cybereason_api_call("POST", "/rest/sensors/query", json_body=json_body) empty_output_message = f"No Machine Details found for the given Machine Name: {machine_name}" if dict_safe_get(response, ["sensors"]) == []: return CommandResults(readable_output=empty_output_message) else: outputs = [] for single_sensor in response.get("sensors"): outputs.append( { "MachineID": single_sensor.get("sensorId"), "MachineName": single_sensor.get("machineName"), "MachineFQDN": single_sensor.get("fqdn"), "GroupID": single_sensor.get("groupId"), "GroupName": single_sensor.get("groupName"), } ) return CommandResults( readable_output=tableToMarkdown("Machine Details", outputs, headers=SENSOR_HEADERS) if outputs else empty_output_message, outputs_prefix="Cybereason.Sensor", outputs_key_field="MachineID", outputs=outputs, ) def get_malop_management_data(client: Client, start_time, end_time, offset): query = { "search": {}, "range": {"from": start_time, "to": end_time}, "pagination": {"pageSize": 50, "offset": offset}, "filter": { "malop": { "status": ["Active"], }, }, "federation": {"groups": []}, "sort": [{"field": "LastUpdateTime", "order": "desc"}], } demisto.info(f"API query for malop management Request: {query}") response = client.cybereason_api_call("POST", "/rest/mmng/v2/malops", json_body=query) return response def query_malop_management_command(client: Client, args: dict): malop_guid = args.get("malopGuid") json_body = { "search": {"malop": {"guid": f"{malop_guid}"}}, "pagination": {"offset": 0}, "range": {"from": 0, "to": 9999999999999}, } response = client.cybereason_api_call("POST", "/rest/mmng/v2/malops", json_body=json_body) if dict_safe_get(response, ["data", "data"]) == []: raise DemistoException(f"Could not find details for the provided MalopGuid {malop_guid}") else: outputs = [] for single_malop in response.get("data", {}).get("data", []): demisto.debug(f"single_malop: {single_malop}") guid = single_malop.get("guid", "") creation_time = single_malop.get("creationTime", "") malop_last_update_time = single_malop.get("lastUpdateTime", "") management_status = single_malop.get("investigationStatus", "") involved_hashes = single_malop.get("rootCauseElementHashes", []) malop_severity = single_malop.get("severity", "") machines = single_malop.get("machines", []) filtered_machines = [ { "guid": machine.get("guid"), "displayName": machine.get("displayName"), "pylumId": machine.get("pylumId"), } for machine in machines ] users = single_malop.get("users", []) filtered_users = [ { "guid": user.get("guid"), "displayName": user.get("displayName"), } for user in users ] decision_statuses = single_malop.get("decisionStatuses", []) dectection_types = single_malop.get("detectionTypes", []) detection_engines = single_malop.get("detectionEngines", []) mitre_techniques = single_malop.get("mitreTechniques") closer_name = single_malop.get("closerName") if single_malop["isEdr"]: link = SERVER + "/#/malop/" + guid else: link = SERVER + "/#/detection-malop/" + guid malop_output = { "GUID": guid, "Link": link, "CreationTime": creation_time, "LastUpdateTime": malop_last_update_time, "Status": management_status, "InvolvedHash": involved_hashes, "Severity": malop_severity, "Machines": filtered_machines, "Users": filtered_users, "DecisionStatuses": decision_statuses, "DetectionTypes": dectection_types, "DetectionEngines": detection_engines, "MitreTechniques": mitre_techniques, "MalopCloserName": closer_name, } outputs.append(malop_output) return CommandResults( readable_output=tableToMarkdown("Cybereason Malop", outputs, headers=SINGLE_MALOP_HEADERS) if outputs else "No malop found", outputs_prefix="Cybereason.Malops", outputs_key_field="GUID", outputs=outputs, ) def cybereason_process_attack_tree_command(client: Client, args: dict): process_guid_list = argToList(args.get("processGuid")) outputs = [] for guid in process_guid_list: url = SERVER + "/#/processTree?guid=" + guid + "&viewedGuids=" + guid + "&rootType=Process" process_output = { "ProcessID": guid, "URL": url, } outputs.append(process_output) empty_output_message = "No Process Details found for the given ProcessID" return CommandResults( readable_output=tableToMarkdown("Process Attack Tree URL", outputs, headers=PROCESS_URL_HEADERS) if outputs else empty_output_message, outputs_prefix="Cybereason.Process", outputs_key_field="ProcessID", outputs=outputs, ) def update_malop_investigation_status_command(client: Client, args: dict): malop_guid = str(args.get("malopGuid")) investigation_status = str(args.get("investigationStatus")) if investigation_status not in INVESTIGATION_STATUS_MAP: raise Exception(f"Invalid investigation status. Must be one of: {', '.join(INVESTIGATION_STATUS_MAP.keys())}") update_malop_investigation_status(client, malop_guid, investigation_status) return CommandResults( readable_output=f'Successfully updated malop {malop_guid} to investigation status "{investigation_status}"!', outputs_prefix="Cybereason.Malops", outputs_key_field="GUID", outputs={"GUID": malop_guid, "InvestigationStatus": investigation_status}, ) def update_malop_investigation_status(client: Client, malop_guid: str, investigation_status: str) -> None: json_body = {"investigationStatus": INVESTIGATION_STATUS_MAP[investigation_status]} response = client.cybereason_api_call("PUT", f"/rest/mmng/v2/malops/{malop_guid}", json_body=json_body) if response["status"] != "SUCCESS": raise DemistoException(f"Failed to update malop {malop_guid} to \"{investigation_status}\": {response['message']}") def get_machine_details_command_pagination_params(args: dict) -> dict: """ Generate pagination parameters for fetching machine details based on the given arguments. This function calculates the 'limit' and 'offset' parameters for pagination based on the provided 'page' and 'pageSize' arguments. If 'page' and 'pageSize' are valid integer values, the function returns a dictionary containing 'limit' and 'offset' calculated accordingly. If 'page' or 'pageSize' are not valid integers, the function falls back to using the 'limit' argument or defaults to 50 with an 'offset' of 0. Args: args (dict): The demisto.args() dictionary containing the optional arguments for pagination: 'page', 'pageSize', 'limit'. Returns: dict: A dictionary containing the calculated 'limit' and 'offset' parameters for pagination. """ page = arg_to_number(args.get("page")) page_size = arg_to_number(args.get("pageSize")) if isinstance(page, int) and isinstance(page_size, int): return {"limit": page_size, "offset": (page - 1) * page_size} else: return {"limit": arg_to_number(args.get("limit", 50)), "offset": 0} def main(): auth = "" params = demisto.params() args = demisto.args() proxy = params.get("proxy", False) demisto.debug(f"Command being called is {demisto.command()}") try: client = Client(base_url=SERVER, verify=USE_SSL, headers=HEADERS, proxy=proxy) if CERTIFICATE: client_certificate() auth = "CERT" elif USERNAME and PASSWORD: validate_jsession(client) auth = "BASIC" else: raise Exception("No credentials were provided") if demisto.command() == "test-module": # Tests connectivity and credentails on login query_user(client, []) return_results("ok") elif demisto.command() == "fetch-incidents": fetch_incidents(client) elif demisto.command() == "cybereason-is-probe-connected": return_results(is_probe_connected_command(client, args)) elif demisto.command() == "cybereason-query-processes": return_results(query_processes_command(client, args)) elif demisto.command() == "cybereason-query-malops": return_results(query_malops_command(client, args)) elif demisto.command() == "cybereason-query-connections": return_results(query_connections_command(client, args)) elif demisto.command() == "cybereason-isolate-machine": return_results(isolate_machine_command(client, args)) elif demisto.command() == "cybereason-unisolate-machine": return_results(unisolate_machine_command(client, args)) elif demisto.command() == "cybereason-malop-processes": return_results(malop_processes_command(client, args)) elif demisto.command() == "cybereason-add-comment": return_results(add_comment_command(client, args)) elif demisto.command() == "cybereason-update-malop-status": return_results(update_malop_status_command(client, args)) elif demisto.command() == "cybereason-prevent-file": return_results(prevent_file_command(client, args)) elif demisto.command() == "cybereason-unprevent-file": return_results(unprevent_file_command(client, args)) elif demisto.command() == "cybereason-available-remediation-actions": return_results(available_remediation_actions_command(client, args)) elif demisto.command() == "cybereason-kill-process": return_results(kill_process_command(client, args)) elif demisto.command() == "cybereason-quarantine-file": return_results(quarantine_file_command(client, args)) elif demisto.command() == "cybereason-unquarantine-file": return_results(unquarantine_file_command(client, args)) elif demisto.command() == "cybereason-block-file": return_results(block_file_command(client, args)) elif demisto.command() == "cybereason-delete-registry-key": return_results(delete_registry_key_command(client, args)) elif demisto.command() == "cybereason-kill-prevent-unsuspend": return_results(kill_prevent_unsuspend_command(client, args)) elif demisto.command() == "cybereason-unsuspend-process": return_results(unsuspend_process_command(client, args)) elif demisto.command() == "cybereason-query-file": return_results(query_file_command(client, args)) elif demisto.command() == "cybereason-query-domain": return_results(query_domain_command(client, args)) elif demisto.command() == "cybereason-query-user": return_results(query_user_command(client, args)) elif demisto.command() == "cybereason-start-fetchfile": return_results(start_fetchfile_command(client, args)) elif demisto.command() == "cybereason-fetchfile-progress": return_results(fetchfile_progress_command(client, args)) elif demisto.command() == "cybereason-download-file": return_results(download_fetchfile_command(client, args)) elif demisto.command() == "cybereason-close-file-batch-id": return_results(close_fetchfile_command(client, args)) elif demisto.command() == "cybereason-archive-sensor": return_results(archive_sensor_command(client, args)) elif demisto.command() == "cybereason-unarchive-sensor": return_results(unarchive_sensor_command(client, args)) elif demisto.command() == "cybereason-delete-sensor": return_results(delete_sensor_command(client, args)) elif demisto.command() == "cybereason-malware-query": return_results(malware_query_command(client, args)) elif demisto.command() == "cybereason-start-host-scan": return_results(start_host_scan_command(client, args)) elif demisto.command() == "cybereason-fetch-scan-status": return_results(fetch_scan_status_command(client, args)) elif demisto.command() == "cybereason-get-sensor-id": return_results(get_sensor_id_command(client, args)) elif demisto.command() == "cybereason-get-machine-details": return_results(get_machine_details_command(client, args)) elif demisto.command() == "cybereason-query-malop-management": return_results(query_malop_management_command(client, args)) elif demisto.command() == "cybereason-process-attack-tree": return_results(cybereason_process_attack_tree_command(client, args)) elif demisto.command() == "cybereason-update-malop-investigation-status": return_results(update_malop_investigation_status_command(client, args)) else: raise NotImplementedError(f"Command {demisto.command()} is not implemented.") except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{str(e)}") finally: if auth and auth == "CERT": os.remove(os.path.abspath("client.pem")) os.remove(os.path.abspath("client.cert")) if __name__ in ("__main__", "builtin", "builtins"): main()