Details
| ID | Cylance Protect v2 |
|---|---|
| Provider | Arctic Wolf |
| Category | Endpoint |
| From Version | 5.0.0 |
| Docker Image | demisto/auth-utils:1.0.0.11671917 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Overview
Use the Cylance Protect v2 integration to manage endpoints, streamline remediation, and response from Cortex XSOAR.
This integration was integrated and tested with version 2.0.5 rev6 of Cylance Protect and Optics.
Prerequisites
Before you integrate Cylance Protect on Cortex XSOAR, you need to obtain a Cylance token.
- In Cylance, navigate to Settings > Integrations.
- Click Add Application.
- Enter an Application Name, and select the necessary privileges.
- Click Save.
- Record the Application ID and Application Secret for later use. You will not be able to access these later.
- Locate the Tenant ID at the top right side of the Integrations page and record it for later use.
Configure the Cylance Protect v2 Integration on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Cylance Protect v2.
- Click Add instance to create and configure a new integration instance.
- Name: A textual name for the integration instance.
- Server URL: URL of Cylance server.
- Application ID
- Application Secret
- Tenant API Key
- Use system proxy settings
- File Threshold: Default is -59
- Fetch Incidents
- Trust any certificate (not secure)
- Click Test to validate the URLs and connection.
Understanding the Cylance Score
The Cylance score ranges from -100 to 100, and is translated as follows.
Score translation
| Score Range | Color | Severity Level |
| -100 to -60 | Red | Malicious |
| -59 to 0 | Red | Supsicious |
| 1-100 | Green | Good |
Use Cases
- Retrieve and update threats and devices.
- Produce threat data report of indicators.
- Retrieve and create policies and zones.
Commands
You can execute these commands from the Cortex XSOAR 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.
- List console device resources for a tenant: cylance-protect-get-devices
- Get a console device resource for a tenant: cylance-protect-get-device
- Update a device: cylance-protect-update-device
- Get information for device threats: cylance-protect-get-device-threats
- Get information for console policy resources: cylance-protect-get-policies
- Create a zone: cylance-protect-create-zone
- Get information for multiple zones: cylance-protect-get-zones
- Get information for a single zone: cylance-protect-get-zone
- Update a zone: cylance-protect-update-zone
- Get information for a threat: cylance-protect-get-threat
- Get information for a threat device: cylance-protect-get-threat-devices
- Generate a report for indicators: cylance-protect-get-indicators-report
- Get information for threats: cylance-protect-get-threats
- Update device threats: cylance-protect-update-device-threats
- Get a list for hashes: cylance-protect-get-list
- Download a threat: cylance-protect-download-threat
- Add a hash to a list: cylance-protect-add-hash-to-list
- Delete a hash from a list: cylance-protect-delete-hash-from-lists
- Get details of a policy: cylance-protect-get-policy-details
- Delete devices: cylance-protect-delete-devices
- Create a new Instaquery: cylance-optics-create-instaquery
- Get Instaquery result: cylance-optics-get-instaquery-result
- List current Instaquery: cylance-optics-list-instaquery
1. List console device resources for a tenant
Returns a list of console device resources that belong to a tenant. The list is sorted by registration created date, with the most recent at the top of the list.
Base Command
cylance-protect-get-devices
Input
| Input Parameter | Description |
| pageNumber | Page number, default is 1 |
| pageSize | Specifies if the command polls for the result of the analysis, default is 100, maximum is 200 |
Context Output
| Path | Description |
| CylanceProtect.Device.AgentVersion | CylancePROTECT Agent version installed on the device |
| CylanceProtect.Device.DateFirstRegistered | Date and time (in UTC) when the device record was created |
| CylanceProtect.Device.ID | Device’s unique identifier |
| Endpoint.IPAddress | List of IP addresses for the device |
| Endpoint.MACAddress | List of MAC addresses for the device |
| Endpoint.Hostname | Device name |
| CylanceProtect.Device.Policy.ID | Device policy ID |
| CylanceProtect.Device.State | Machine state |
| CylanceProtect.Device.Policy.Name | Device policy name |
| CylanceProtect.Device.Hostname | Device name |
| CylanceProtect.Device.MACAddress | List of MAC addresses for the device |
| CylanceProtect.Device.IPAddress | List of IP addresses for the device |
Command Example
!cylance-protect-get-devices pageNumber=2 pageSize=75
Raw Output
{
"agent_version":"2.0.1440",
"date_first_registered":"2018-01-21T15:45:42",
"id":"652bbfa9-cf74-4e24-90f7-d01b16429701",
"ip_addresses":[
"172.31.31.110"
],
"mac_addresses":[
"06-F8-13-8B-16-C9"
],
"name":"WIN-0VJ9RO3P33Q",
"policy":{
"id":null,
"name":"Default"
},
"state":"Online"
}
2. Get a console device resource for a tenant
Returns a single device resource that belongs to a tenant.
Base Command
cylance-protect-get-device
Input
| Input Parameter | Description |
| id | Device ID |
Context Output
| Path | Description |
| CylanceProtect.Device.AgentVersion | CylancePROTECT Agent version installed on the device |
| CylanceProtect.Device.DateFirstRegistered | Date and time (in UTC) when the device record was created |
| CylanceProtect.Device.BackgroundDetection | If true, the agent is running |
| CylanceProtect.Device.DateLastModified | Date and time (in UTC) when the device record was last modified |
| CylanceProtect.Device.DateOffline | Date and time (in UTC) when the device last communicated with the console |
| CylanceProtect.Device.Hostname | Hostname for the device |
| CylanceProtect.Device.ID | Unique identifier for the device |
| CylanceProtect.Device.IPAddress | List of IP addresses for the device |
| CylanceProtect.Device.MACAddress | List of MAC addresses for the device |
| CylanceProtect.Device.IsSafe | If true, there are no outstanding threats |
| CylanceProtect.Device.UpdateAvailable | If true, there is available update for the device |
| CylanceProtect.Device.State | Machine state |
| Endpoint.Hostname | Device hostname |
| Endpoint.MACAddress | List of MAC addresses for the device |
| Endpoint.IPAddress | List of IP addresses for the device |
| Endpoint.OSVersion | Device OS version |
| CylanceProtect.Device.OSVersion | Device OS version |
| CylanceProtect.Device.Name | Device name |
Command Example
!cylance-protect-get-devices pageNumber=2 pageSize=75
Raw Output
{
"agent_version":"2.0.1440",
"date_first_registered":"2018-01-21T15:45:42",
"id":"652bbfa9-cf74-4e24-90f7-d01b16429701",
"ip_addresses":[
"172.31.31.110"
],
"mac_addresses":[
"06-F8-13-8B-16-C9"
],
"name":"WIN-0VJ9RO3P33Q",
"policy":{
"id":null,
"name":"Default"
},
"state":"Online"
}
3. Update a device
Updates a specified device.
Base Command
cylance-protect-update-device
Input
| Input Parameter | Description |
| id | Device ID |
| name | Device name |
| policyId | Policy ID |
| addZones | Zones IDs to add |
| removeZones | Zones IDs to remove |
Context Output
There is no context output for this command.
Command Example
!cylance-protect-update-device id=652bbfa9-cf74-4e24-90f7-d01b16429701
Raw Output
{
"Name":"TestName",
"PolicyID":"7bcb0817-e9c9-444d-96e2-be9b59f429cb",
"id":"6033f7a1-e66c-4aef-9c7d-ed454457d071"
}
4. Get information for device threats
Returns information about threats to devices, including classification, threat score, and more.
Base Command
cylance-protect-get-device-threats
Input
| Input Parameter | Description |
| id | Device ID |
| pageNumber | Page number, default is 1 |
| pageSize | Specifies if the command polls for the result of the analysis, default is 100, maximum is 200 |
Context Output
| Path | Description |
| File.Classification | Cylance threat classification assigned to the threat |
| File.CylanceScore | Cylance score assigned to the threat |
| File.DateFound | Date and time (in UTC) when the threat was found on the device |
| File.FilePath | File path where the threat was found on the device |
| File.FileStatus | Current status of the file on the device.
|
| File.Name | Threat name |
| File.Sha256 | SHA-256 has for the threat |
| File.SubClassification | Cylance threat sub-classification assigned to the threat |
| DBotScore.Indicator | Tested indicator |
| DBotScore.Type | Indicator type |
| DBotScore.Vendor | Vendor used to calculate the score |
| DbotScore.Score | Actual score |
Command Example
!cylance-protect-get-device-threats id=6033f7a1-e66c-4aef-9c7d-ed454457d071 pageNumber=2 pageSize=75
Raw Output
{
"classification":"Malware",
"cylance_score":-1,
"date_found":"2017-11-21T17:34:51",
"file_path":"C:\\$Recycle.Bin\\S-1-5-21-3378384064-522475393-1698893855-1001\\$RPJNCM8\\artifacts\\2017-08-12-Trickbot-binary-from-usdata.estoreseller.com.exe",
"file_status":"Default",
"name":"2017-08-12-Trickbot-binary-from-usdata.estoreseller.com.exe",
"sha256":"5DA547E87D6EF12349FB4DBBA9CF3146A358E284F72361DD07BBABFC95B0BAC3",
"sub_classification":"Trojan"
}
5. Get information for console policy resources
Returns information for console policy resources.
Base Command
cylance-protect-get-policies
Input
| Input Parameter | Description |
| pageNumber | Page number, default is 1 |
| pageItems | Number of items on a page, default is 100 |
Context Output
| Path | Description |
| CylanceProtect.Policies.DateAdded | Date and time (in UTC) when the Console policy resource was first created |
| CylanceProtect.Policies.DateModified | Date and time (in UTC) when the Console policy resource was last modified |
| CylanceProtect.Policies.DeviceCount | Number of devices assigned to this policy |
| CylanceProtect.Policies.Id | Unique ID for the policy resource |
| CylanceProtect.Policies.Name | Policy name |
| CylanceProtect.Policies.ZoneCount | Number of zones assigned to this policy |
Command Example
!cylance-protect-get-policies id=6033f7a1-e66c-4aef-9c7d-ed454457d071 pageNumber=2 pageSize=75
Raw Output
{
"date_added":"2018-03-05T12:29:02",
"date_modified":"2018-03-05T12:29:02",
"device_count":0,
"id":"7bcb0817-e9c9-444d-96e2-be9b59f429cb",
"name":"Test_Policy",
"zone_count":4
}
6. Create a zone
Creates a zone with a policy ID and criticality level.
Base Command
cylance-protect-create-zone
Input
| Input Parameter | Description |
| name | Zone name |
| policy_id | Unique ID for the policy assigned to the zone |
| criticality | Criticality value of the zone |
Context Output
There is no context output for this command.
Command Example
!cylance-protect-create-zone name=TestingZone3 criticality=High
Raw Output
{
"criticality":"High",
"date_created":"2018-03-13T11:38:52.2065082Z",
"id":"f15b2f79-c100-4146-b056-a8005c13b2de",
"name":"TestingZone3",
"policy_id":"7bcb0817-e9c9-444d-96e2-be9b59f429cb"
}
7. Get information for multiple zones
Returns information for multiple zones.
Base Command
cylance-protect-get-zones
Input
| Input Parameter | Description |
| pageNumber | Page number to request |
| pageItems | Number of zone records to retrieve for each page |
Context Output
| Path | Description |
| CylanceProtect.Zones.Criticality |
Zone value.
|
| CylanceProtect.Zones.DateCreated | Date and time (in UTC) when the zone was created |
| CylanceProtect.Zones.DateModified | Date and time (in UTC) when the zone was last modified |
| CylanceProtect.Zones.Id | Zone unique ID |
| CylanceProtect.Zones.Name | Zone name |
| CylanceProtect.Zones.PolicyId | Unique ID of the policy assigned to the zone |
| CylanceProtect.Zones.UpdateType | Update type for the zone |
| CylanceProtect.Zones.ZoneRuleId | Unique ID for the zone rule created for the zone |
Command Example
!cylance-protect-get-zones pageNumber=2 pageItems=10
Raw Output
{
"criticality":"High",
"date_created":"2018-03-13T11:38:52",
"date_modified":"2018-03-13T11:38:52",
"id":"f15b2f79-c100-4146-b056-a8005c13b2de",
"name":"TestingZone3",
"policy_id":"7bcb0817-e9c9-444d-96e2-be9b59f429cb",
"update_type":"Production",
"zone_rule_id":null
}
8. Get information for a single zone
Returns information for a single zone.
Base Command
cylance-protect-get-zone
Input
| Input Parameter | Description |
| id | Zone ID |
Context Output
| Path | Description |
| CylanceProtect.Zones.Criticality |
Zone value.
|
| CylanceProtect.Zones.DateCreated | Date and time (in UTC) when the zone was created |
| CylanceProtect.Zones.DateModified | Date and time (in UTC) when the zone was last modified |
| CylanceProtect.Zones.Id | Zone unique ID |
| CylanceProtect.Zones.Name | Zone name |
| CylanceProtect.Zones.PolicyId | Unique ID of the policy assigned to the zone |
| CylanceProtect.Zones.UpdateType | Update type for the zone |
| CylanceProtect.Zones.ZoneRuleId | Unique ID for the zone rule created for the zone |
Command Example
!cylance-protect-get-zone id=f15b2f79-c100-4146-b056-a8005c13b2de
Raw Output
{
"criticality":"High",
"date_created":"2018-03-13T11:38:52",
"date_modified":"2018-03-13T11:38:52",
"id":"f15b2f79-c100-4146-b056-a8005c13b2de",
"name":"TestingZone3",
"policy_id":"7bcb0817-e9c9-444d-96e2-be9b59f429cb",
"update_type":"Production",
"zone_rule_id":null
}
9. Update a zone
Updates a specified zone.
Base Command
cylance-protect-update-zone
Input
| Input Parameter | Description |
| id | Zone ID |
| name | Zone name |
| policyId | Unique ID for the policy assigned to the Zone |
| criticality | Criticality value of the zone |
Context Output
There is no context output for this command.
Command Example
!cylance-protect-update-zone id=f15b2f79-c100-4146-b056-a8005c13b2de
Raw Output
true
10. Get information for a threat
Returns information for a threat.
Base Command
cylance-protect-get-threat
Input
| Input Parameter | Description |
| sha256 | SHA-256 hash of the threat |
| theshold | Threat threshold |
Context Output
| Path | Description |
| File.AutoRun | Indicates if the file is set to automatically run on system startup |
| File.AvIndustry | The score provided by the Anti-Virus industry |
| File.CertIssuer | ID for the certificate issuer |
| File.CertPublisher | ID for the certificate publisher |
| File.CertTimestamp | Date and time (in UTC) when the file was signed using the certificate |
| File.Classification | Threat classification for the threat |
| File.CylanceScore | Cylance Score assigned to the threat |
| File.DetectedBy | Name of the Cylance module that detected the threat |
| File.FileSize | Size of the file |
| File.GlobalQuarantine | Identifies if the threat is on the Global Quarantine list |
| File.Md5 | MD5 hash for the threat |
| File.Name | Threat name |
| File.Running | Identifies if the threat is executing, or another executable loaded or called it |
| File.Safelisted | Identifies if the threat is on the Safe List |
| File.Sha256 | SHA-256 hash for the threat |
| File.Signed | Identifies the file as signed or not signed |
| File.SubClassification | The threat sub-classification for the threat |
| File.UniqueToCylance | The threat was identified by Cylance but not by other anti-virus sources |
| DBotScore.Indicator | The tested indicator |
| DBotScore.Type | Indicator type |
| DBotScore.Vendor | Vendor used to calculate the score |
| DBotScore.Score | The actual score |
Command Example
!cylance-protect-get-threat sha256=ED01EBFBC9EB5BBEA545AF4D01BF5F1071661840480439C6E5BABE8E080E41AA
Raw Output
{
"auto_run":false,
"av_industry":null,
"cert_issuer":"",
"cert_publisher":"",
"cert_timestamp":"0001-01-01T00:00:00",
"classification":"Malware",
"cylance_score":-1,
"detected_by":"File Watcher",
"file_size":3514368,
"global_quarantined":true,
"md5":"84C82835A5D21BBCF75A61706D8AB549",
"name":"wanncry.exe",
"running":false,
"safelisted":false,
"sha256":"ED01EBFBC9EB5BBEA545AF4D01BF5F1071661840480439C6E5BABE8E080E41AA",
"signed":false,
"sub_classification":"Ransom",
"unique_to_cylance":false
}
11. Get information for a threat device
Returns information for a threat device.
Base Command
cylance-protect-get-threat-devices
Input
| Input Parameter | Description |
| sha256 | SHA-256 hash of the threat |
Context Output
| Path | Description |
| CylanceProtect.Threat.Devices.ID | Device ID |
| CylanceProtect.Threat.Devices.DateFound | Date and time (in UTC) when the threat was found on the device |
| CylanceProtect.Threat.Devices.AgentVersion | Agent version installed on the device |
| CylanceProtect.Threat.Devices.FileStatus |
Current quarantine status of the file on the device.
|
| Endpoint.IPAddress | List of IP addresses for the device |
| Endpoint.MACAddress | List of MAC addresses for the device |
| Endpoint.Hostname | Device name for the device |
| CylanceProtect.Threat.Devices.PolicyID | Unique identifier of the policy assigned to the device, or null if no policy is assigned |
| CylanceProtect.Threat.Devices.State |
Device state.
|
| File.SHA256 | SHA-256 hash of the threat |
| File.Path | Path where the file was found on the device |
| CylanceProtect.Threat.Devices.Hostname | Device name for the device |
| CylanceProtect.Threat.Devices.IPAddress | List of IP addresses for the device |
| CylanceProtect.Threat.Devices.MACAddress | List of MAC addresses for the device |
Command Example
!cylance-protect-get-threat-devices sha256=ED01EBFBC9EB5BBEA545AF4D01BF5F1071661840480439C6E5BABE8E080E41AA
Raw Output
{
"agent_version":"2.0.1440",
"date_found":"2018-01-21T15:45:46",
"file_status":"Whitelisted",
"id":"652bbfa9-cf74-4e24-90f7-d01b16429701",
"ip_addresses":"172.31.31.110",
"mac_addresses":"06-F8-13-8B-16-C9",
"name":"WIN-0VJ9RO3P33Q",
"policy_id":null,
"state":"Online"
}
12. Generate a report for indicators
Generates a report for indicators.
Base Command
cylance-protect-get-indicators-report
Input
| Input Parameter | Description |
| token | Threat data report token |
Context Output
There is not context output for this command.
Command Example
cylance-protect-get-indicators-report token=As3424$%
Raw Output
There is no raw output for this command.
13. Get information for threats
Returns information for threats.
Base Command
cylance-protect-get-threats
Input
| Input Parameter | Description |
| page_size | Number of device records to retrieve for each page |
| page | Page number to request |
| threshold | Threat threshold |
Context Output
| Path | Description |
| File.Classification | Threat classification for the threat |
| File.SubClassification | Threat sub-classification for the threat |
| File.Sha256 | SHA-256 hash for the threat |
| File.Safelisted | Identifies if the threat is on the Safe List |
| File.Name | Threat name |
| File.LastFound | Date and time (in UTC) when the file was last found |
| File.CylanceScore | The Cylance Score assigned to the threat |
| File.GlobalQuarantine | Identifies if the threat is on the Global Quarantine list |
| File.UniqueToCylance | The threat was identified by Cylance but not by other anti-virus sources |
| File.FileSize | File size |
| File.Md5 | MD5 hash for the threat |
| DBotScore.Indicator | The tested indicator |
| DBotScore.Type | Indicator type |
| DBotScore.Vendor | Vendor used to calculate the score |
| DBotScore.Score | The actual score |
Command Example
!cylance-protect-get-threats page_size=4
Raw Output
{
"av_industry":null,
"classification":"Malware",
"cylance_score":-1,
"file_size":3514368,
"global_quarantined":true,
"last_found":"2018-01-21T15:45:46",
"md5":"84C82835A5D21BBCF75A61706D8AB549",
"name":"wanncry.exe",
"safelisted":false,
"sha256":"ED01EBFBC9EB5BBEA545AF4D01BF5F1071661840480439C6E5BABE8E080E41AA",
"sub_classification":"Ransom",
"unique_to_cylance":false
}
14. Update device threats
Updates multiple device threats.
Base Command
cylance-protect-update-device-threats
Input
| Input Parameter | Description |
| threat_id | SHA-256 of the convicted threat |
| event | Requested status update for the convicted threat |
| device_id | ID of the device to update |
Context Output
There is no context output for this command.
Command Example
!cylance-protect-update-device-threats threat_id=ED01EBFBC9EB5BBEA545AF4D01BF5F1071661840480439C6E5BABE8E080E41AA
Raw Output
true
15. Get a list for hashes
Returns a list for hashes.
Base Command
cylance-protect-get-list
Input
| Input Parameter | Description |
| listTypeId |
Type of list to retrieve hashes for.
|
| page_size | Number of device records to retrieve for each page |
| page | Page number to request |
| threshold | Threat threshold |
Context Output
| Path | Description |
| File.Added | Timestamp when the file was added to the list |
| File.AddedBy | Tenant user ID who added the file to the list |
| File.AvIndustry | The score provided by the Anti-Virus industry |
| File.Category | The category for the list specified (for the Global Safe list only) |
| File.Classification | Threat classification assigned by Cylance |
| File.CylanceScore | The Cylance score assigned to the threat |
| File.ListType | list type that the threat belongs to |
| File.Md5 | MD5 of the threat |
| File.Sha256 | SHA-256 of the threat |
| File.Name | Threat name |
| DBotScore.Indicator | The tested indicator |
| DBotScore.Type | Indicator type |
| DBotScore.Vendor | Vendor used to calculate the score |
| DBotScore.Score | The actual score |
Command Example
!cylance-protect-get-list listTypeId=0 page_size=4
Raw Output
{
"added":"2017-11-07T04:30:04",
"added_by":"3ff9b11e-b64e-4350-97ba-aeb0a099b8ee",
"av_industry":null,
"category":"",
"classification":"Malware",
"cylance_score":-1,
"list_type":"GlobalQuarantine",
"md5":"84C82835A5D21BBCF75A61706D8AB549",
"name":"wanncry.exe",
"reason":"Malicious File Found",
"sha256":"ED01EBFBC9EB5BBEA545AF4D01BF5F1071661840480439C6E5BABE8E080E41AA",
"sub_classification":"Ransom"
}
16. Download a threat
Downloads a threat attached to a specific SHA-256 hash.
Base Command
cylance-protect-download-threat
Input
| Argument Name | Description | Required |
|---|---|---|
| sha256 | The SHA-256 hash for the file you want to download | Required |
| threshold | File threshold to determine reputation | Optional |
| unzip | Check to return the file unzipped to the War Room | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| File.SHA256 | string | SHA-256 of the file |
| File.Name | string | File name |
| File.Size | number | File size |
| File.Safelisted | boolean | Safelisted |
| File.Timestamp | string | Timestamp |
| File.Md5 | string | MD5 |
| DBotScore.Indicator | string | The Indicator |
| DBotScore.Score | number | The DBot score |
| DBotScore.Type | string | The Indicator type |
| DBotScore.Vendor | string | The DBot score vendor |
| File.Malicious.Vendor | string | For malicious files, the vendor that made the decision |
| File.Malicious.Description | string | For malicious files, the reason that the vendor made the decision |
Command Example
!cylance-protect-download-threat sha256="0f427b33b824110427b2ba7be20740b45ea4da41bc1416dd55771edfb0c18f09" unzip="yes"
Context Example
DBotScore
{
"Indicator": "AutoitLocker.exe",
"Score": 3,
"Type": "file",
"Vendor": "Cylance Protect"
}
File
{
"DownloadURL": "https://cylanceephemeralfilestore.s3.amazonaws.com/0F/42/7B/33/0F427B33B824110427B2BA7BE20740B45EA4DA41BC1416DD55771EDFB0C18F09.zip? Signature=98kI7a19I2q%2BeE7Ef1un4BjSolQ%3D&Expires=1541875473&AWSAccessKeyId=AKIAIAD6JC2YTYVBFRFA",
"MD5": "2FC103D0D52466B63D44444CE12A5901",
"Malicious": {
"Description": "Score determined by get threat command",
"Vendor": "Cylance Protect"
},
"Name": "AutoitLocker.exe",
"SHA256": "0F427B33B824110427B2BA7BE20740B45EA4DA41BC1416DD55771EDFB0C18F09",
"Safelisted": false,
"Size": 405345,
"Timestamp": "0001-01-01T00:00:00"
}
Human Readable Output
17. Add a hash to a list
Adds an identified threat to either the Global Quarantine list or the Global Safe list for a particular Tenant.
Base Command
cylance-protect-add-hash-to-list
Input
| Argument Name | Description | Required |
|---|---|---|
| sha256 | SHA-256 hash to add to the Global Safe list | Required |
| listType | The list type the threat belongs to (GlobalQuarantine or GlobalSafe) | Required |
| reason | The reason why the file was added to the list | Optional |
| category | This field is required only if the list_type value is Global Safe. The value can be one ofthe following:• Admin Tool• Commercial Software• Drivers• Internal Application• Operating System• Security Software• None | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| File.SHA256 | string | The SHA-256 hash for the threat |
| File.Cylance.ListType | string | The list type the threat belongs to (GlobalQuarantine or GlobalSafe) |
| File.Cylance.Category | string | This field is required only if the list_type value is Global Safe. The value can be one of the following: • Admin Tool • Commercial Software • Drivers • Internal Application • Operating System • Security Software • None |
Command Example
!cylance-protect-add-hash-to-list sha256="9ACD45F5F3F2C7629E51FE3123D31296EF763F6ABC1F895CDD1BF1AFB9A7453B" listType="GlobalQuarantine"
Human Readable Output
18. Remove a threat from a list
Removes an identified threat from either the Global Quarantine list or the Global Safe list for a particular Tenant.
Base Command
cylance-protect-delete-hash-from-lists
Input
| Argument Name | Description | Required |
|---|---|---|
| sha256 | The SHA-256 hash for the threat | True |
| listType | The list type the threat belongs to (GlobalQuarantine or GlobalSafe) | True |
Context Output
| Path | Type | Description |
|---|---|---|
| File.SHA256 | string | SHA-256 of the file |
| File.Cylance.ListType | string | The list type the threat belongs to (GlobalQuarantine or GlobalSafe) |
Command Example
!cylance-protect-delete-hash-from-lists sha256="9ACD45F5F3F2C7629E51FE3123D31296EF763F6ABC1F895CDD1BF1AFB9A7453B" listType="GlobalQuarantine"
Human Readable Output
19. Get details for a policy
Gets details for a single policy.
Base Command
cylance-protect-get-policy-details
Input
| Argument Name | Description | Required |
|---|---|---|
| policyID | The Tenant policy ID to the service endpoint. | True |
Context Output
| Path | Type | Description |
|---|---|---|
| Cylance.Policy.ID | string | Policy ID |
| Cylance.Policy.Name | string | Policy name |
| Cylance.Policy.Timestamp | string | The date and time the policy was created, in UTC. |
20. Delete devices
Deletes one or more devices from an organization.
Base Command
cylance-protect-delete-devices
Input
| Argument Name | Description | Required |
|---|---|---|
| deviceIds | The unique identifiers for the devices to delete. The maximum number of Device IDs per request is 20. | Required |
| batch_size | The number of devices to delete per request (batch) | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Cylance.Device.Id | string | The unique identifier of the deletion request |
| Cylance.Device.Name | string | Device name |
| Cylance.Device.Deleted | string | Checks if the device was deleted (boolean) |
Command Example
!cylance-protect-get-policy-details policyID="7bcb0817-e9c9-444d-96e2-be9b59f429cb"
Context Example
Cylance
{
"Policy": {
"ID": "7bcb0817-e9c9-444d-96e2-be9b59f429cb",
"Name": "Test_Policy",
"Timestamp": "2018-03-05T12:29:03.000000+00:00"
}
}
Human Readable Output
cylance-protect-get-device-by-hostname
Allows a caller to request a specific device resource belonging to a Tenant by hostname
Base Command
cylance-protect-get-device-by-hostname
Input
| Argument Name | Description | Required |
|---|---|---|
| hostname | The hostname (DNS name). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CylanceProtect.Device.AgentVersion | String | The CylancePROTECT Agent version installed on the device. |
| CylanceProtect.Device.IPAddress | Unknown | The list of IP addresses for the device. |
| CylanceProtect.Device.MACAddress | Unknown | The list of MAC addresses for the device. |
| CylanceProtect.Device.Hostname | string | The hostname for the device. |
| CylanceProtect.Device.OSVersion | string | Device OS version. |
| CylanceProtect.Device.UpdateAvailable | boolean | If true, there is available update for the device. |
| CylanceProtect.Device.BackgroundDetection | boolean | If true, the Agent is currently running. |
| CylanceProtect.Device.DateFirstRegistered | date | The date and time (in UTC) when the device record was created. |
| CylanceProtect.Device.DateLastModified | date | The date and time (in UTC) when the device record was last modified. |
| CylanceProtect.Device.DateOffline | date | The date and time (in UTC) when the device last communicated with the Console. |
| CylanceProtect.Device.IsSafe | boolean | If true, there are no outstanding threats. |
| CylanceProtect.Device.LastLoggedInUser | string | Last logged in user. |
| CylanceProtect.Device.State | string | Machine state. |
| CylanceProtect.Device.ID | string | The unique identifier for the device. |
| CylanceProtect.Device.Name | string | Device name. |
| CylanceProtect.Device.UpdateType | string | Device update type. |
| CylanceProtect.Device.Policy.ID | string | Device policy ID. |
| CylanceProtect.Device.Policy.Name | string | Device policy name. |
| Endpoint.Hostname | string | Device hostname. |
| Endpoint.MACAddress | Unknown | The list of MAC addresses for the device. |
| Endpoint.IPAddress | Unknown | The list of IP addresses for the device. |
| Endpoint.OSVersion | string | Device OS version. |
Command Example
!cylance-protect-get-device-by-hostname hostname=WIN-5HMOGIEG6M5
Context Example
{
"CylanceProtect": {
"Device": {
"AgentVersion": "1.2.1418",
"BackgroundDetection": false,
"DateFirstRegistered": "2017-12-29T04:07:56",
"DateLastModified": null,
"DateOffline": "2020-02-07T02:25:34.151",
"Hostname": "WIN-5HMOGIEG6M5",
"ID": "b4eceeb0-8699-4d42-b853-155513042d6e",
"IPAddress": [
"127.0.0.1"
],
"IsSafe": true,
"LastLoggedInUser": "",
"MACAdress": [
"02-76-91-6B-0A-BB"
],
"Name": "WIN-5HMOGIEG6M5",
"OSVersion": "Microsoft Windows Server 2012 R2 Standard",
"Policy": {
"ID": "32e4aacd-7698-4ef0-93e8-3e6f1f5c6857",
"Name": "Default"
},
"State": "Offline",
"UpdateAvailable": false
}
},
"Endpoint": {
"Hostname": "WIN-5HMOGIEG6M5",
"IPAddress": [
"127.0.0.1"
],
"MACAdress": [
"02-76-91-6B-0A-BB"
],
"OSVersion": "Microsoft Windows Server 2012 R2 Standard"
}
}
Human Readable Output
Cylance Protect Device WIN-5HMOGIEG6M5
AgentVersion BackgroundDetection DateFirstRegistered DateOffline DlcmStatus HostName Id IpAddresses IsSafe MacAddresses Name OsKernelVersion OsVersion Policy Products State UpdateAvailable 1.2.1418 false 2017-12-29T04:07:56 2020-02-07T02:25:34.151 Unknown WIN-5HMOGIEG6M5 b4eceeb0-8699-4d42-b853-155513042d6e 127.0.0.1 true 02-76-91-6B-0A-BB WIN-5HMOGIEG6M5 6.3.0 Microsoft Windows Server 2012 R2 Standard Default {u’status’: u’Offline’, u’version’: u’1.2.1418’, u’name’: u’protect’} Offline false
21. Create a new Instaquery
cylance-optics-create-instaquery
Create a cylance InstaQuery
Base Command
cylance-optics-create-instaquery
Input
| Argument Name | Description | Required |
|---|---|---|
| name | InstaQuery name. | Required |
| description | InstaQuery description. | Required |
| artifact | InstaQuery artifact, select from the list. Possible values are: File, Process, NetworkConnection, RegistryKey. | Required |
| match_value_type | InstaQuery value type to match, select from the list. Possible values are: File.Path, File.Md5, File.Sha2, File.Owner, File.CreationDateTime, Process.Name, Process.Commandline, Process.PrimaryImagePath, Process.PrimaryImageMd5, Process.StartDateTime, NetworkConnection.DestAddr, NetworkConnection.DestPort, RegistryKey.ProcessName, RegistryKey.ProcessPrimaryImagePath, RegistryKey.ValueName, RegistryKey.FilePath, RegistryKey.FileMd5, RegistryKey.IsPersistencePoint. | Required |
| match_values | Value to search in InstaQuery. | Required |
| zone | Zone of the object. | Required |
| match_type | Match type fuzzy or exact. Possible values are: Fuzzy, Exact. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InstaQuery.New.id | string | The unique identifier of the created InstaQuery. |
| InstaQuery.New.created_at | date | The Date and Time that the InstaQuery was created. |
| InstaQuery.New.progress | string | The progress of the InstaQuery. |
Command Example
!cylance-optics-create-instaquery name="Test Insta continue" description="Test only" artifact="File" match_value_type="File.Path" match_values="exe" zone="6608ca0e-88c6-4647-b276-271cc5ea4295" match_type="Fuzzy"
Human Readable Output
| Result | |
|---|---|
| case_sensitive | false |
| artifact | File |
| created_at | 2022-05-05T05:52:36Z |
| description | Test only |
| id | 9E2CCDA5A93918C588E6865ED6FEEA70 |
| match_type | Fuzzy |
| match_value_type | Path |
| match_values | exe |
| name | Test Insta continue |
| progress | |
| results_available | false |
| zones | 6608CA0E88C64647B276271CC5EA4295 |
22. Get Instaquery result
cylance-optics-get-instaquery-result
Get a cylance InstaQuery search result
Base Command
cylance-optics-get-instaquery-result
Input
| Argument Name | Description | Required |
|---|---|---|
| query_id | InstaQuery ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InstaQuery.Results.result | string | The InstaQuery results. |
Command Example
!cylance-optics-get-instaquery-result query_id=9E2CCDA5A93918C588E6865ED6FEEA70
Human Readable Output
| Result | |
|---|---|
| id | 9E2CCDA5A93918C588E6865ED6FEEA70 |
| result | false |
| status | {u’@timestamp’: 1651729959.177779, u’HostName’: u’windows-server-‘, u’DeviceId’: u’ 65DB26864E364409B50DDC23291A3511 ‘, u’@version’: u’1’, u’CorrelationId’: u’ 9E2CCDA5A93918C588E6865ED6FEEA70 ‘, u’Result’: u’{“FirstObservedTime”: “1970-01-01T00:00:00.000Z”, “LastObservedTime”: “1970-01-01T00:00:00.000Z”, “Uid”: “dHrtLYQzbt9oJPxO8HaeyA==”, “Type”: “File”, “Properties”: {“Path”: “c:\program files\cylance\optics\ cyoptics.exe “, “CreationDateTime”: “2021-03-29T22:34:14.000Z”, “Md5”: “ A081D3268531485BF95DC1A15A5BC6B0 “, “Sha256”: “ 256809AABD3AB57949003B9AFCB556A9973222CDE81929982DAE7D306648E462 “, “Owner”: “NT AUTHORITY\SYSTEM”, “SuspectedFileType”: “Executable/PE”, “FileSignature”: “”, “Size”: “594104”, “OwnerUid”: “P3p6fdq3FlMsld6Rz95EOA==”}}’} |
23. List current Instaqueries
cylance-optics-list-instaquery
Get a list of InstaQuery
Base Command
cylance-optics-list-instaquery
Input
| Argument Name | Description | Required |
|---|---|---|
| page | number of page to collect. | Optional |
| page_size | number of items per page to collect. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InstaQuery.List | string | The list of InstaQuery |
Command Example
!cylance-optics-list-instaquery page_size="10"
Human Readable Output
| Result | |
|---|---|
| page_items | {u’match_type’: u’Fuzzy’, u’name’: u’Test Insta continue’, u’created_at’: u’2022-05-05T05:52:36Z’, u’artifact’: u’File’, u’case_sensitive’: False, u’zones’: [u’6608CA0E88C64647B276271CC5EA4295’], u’progress’: {u’queried’: 0, u’responded’: 0}, u’match_value_type’: u’Path’, u’results_available’: True, u’match_values’: [u’exe’], u’id’: u’9E2CCDA5A93918C588E6865ED6FEEA70’, u’description’: u’Test only’} |
| page_number | 1 |
| page_size | 10 |
| total_number_of_items | 8 |
| total_pages | 1 |
Configuration parameters
server— Server URL (required)app_id— Application IDapp_secret— Application Secrettid— Tenant API Keyapp_creds— Application IDapi_key—proxy— Use system proxy settingsunsecure— Trust any certificate (not secure)isFetch— Fetch incidentsincidentType— Incident typefile_threshold— File ThresholdincidentFetchInterval— Incidents Fetch Interval
Commands (24)
-
cylance-optics-create-instaqueryCreate a cylance InstaQuery to search for artifacts in one or multiple zones.
-
cylance-optics-get-instaquery-resultGet a cylance InstaQuery search result.
-
cylance-optics-list-instaqueryGet a list of all current InstaQueries.
-
cylance-protect-add-hash-to-listAdds a convicted threat for a particular tenant to either the Global Quarantine list or the Global Safe list.
-
cylance-protect-create-zoneCreates (adds) a zone to your Console.
-
cylance-protect-delete-devicesDelete one or more devices from an organization.
-
cylance-protect-delete-hash-from-listsRemoves a convicted threat for a particular tenant from either the Global Quarantine list or the Global Safe list.
-
cylance-protect-download-threatDownloads the threat (file) attached to a specific SHA256 hash.
-
cylance-protect-get-deviceAllows a caller to request a specific device resource belonging to a Tenant.
-
cylance-protect-get-device-by-hostnameAllows a caller to request a specific device resource belonging to a Tenant by hostname.
-
cylance-protect-get-device-threatsAllows a caller to request a page with a list of threats found on a specific device.
-
cylance-protect-get-devicesAllows a caller to request a page with a list of Console device resources that belongings to a tenant, sorted by registration (created) date in descending order (most recent device registered listed first).
-
cylance-protect-get-indicators-reportProduces a CSV threat data report of the indicators.
-
cylance-protect-get-listReturns a list of global list resources for a tenant.
-
cylance-protect-get-policiesAllows the caller to get a list of tenant policies.
-
cylance-protect-get-policy-detailsReturns details for a single policy.
-
cylance-protect-get-threatRequests threat details for a specific threat.
-
cylance-protect-get-threat-devicesAllows a caller to request a list of devices on a specific threat.
-
cylance-protect-get-threatsReturns information about Cylance Protect threats.
-
cylance-protect-get-zoneRequest zone information for a specific zone in your organization.
-
cylance-protect-get-zonesRequest zone information for your organization. This will return the top 100 records.
-
cylance-protect-update-deviceAllows a caller to update a specific Console device resource belonging to a Tenant.
-
cylance-protect-update-device-threatsUpdates the status of a convicted threat. Can be "Quarantine" or "Waive".
-
cylance-protect-update-zoneUpdates a zone in your organization.
import demistomock as demisto from CommonServerPython import * import jwt import uuid import requests import urllib3 import json import re import zipfile from io import BytesIO from datetime import datetime, timedelta # disable insecure warnings urllib3.disable_warnings() # CONSTANTS TOKEN_TIMEOUT = 300 # 5 minutes URI_AUTH = "auth/v2/token" URI_DEVICES = "devices/v2" URI_POLICIES = "policies/v2" URI_ZONES = "zones/v2" URI_THREATS = "threats/v2" URI_LISTS = "globallists/v2" URI_HOSTNAME = "devices/v2/hostname" URI_OPTICS = "instaqueries/v2" # Optics InstaQuery API Endpoint SCOPE_DEVICE_LIST = "device:list" SCOPE_DEVICE_READ = "device:read" SCOPE_DEVICE_UPDATE = "device:update" SCOPE_DEVICE_THREAT_LIST = "device:threatlist" SCOPE_POLICY_LIST = "policy:list" SCOPE_POLICY_READ = "policy:read" SCOPE_ZONE_CREATE = "zone:create" SCOPE_ZONE_LIST = "zone:list" SCOPE_ZONE_READ = "zone:read" SCOPE_ZONE_UPDATE = "zone:update" SCOPE_THREAT_READ = "threat:read" SCOPE_THREAT_DEVICE_LIST = "threat:devicelist" SCOPE_THREAT_UPDATE = "threat:update" SCOPE_GLOBAL_LIST = "globallist:list" SCOPE_THREAT_LIST = "threat:list" SCOPE_GLOBAL_LIST_CREATE = "globallist:create" SCOPE_GLOBAL_LIST_DELETE = "globallist:delete" SCOPE_OPTICS_LIST = "opticssurvey:list" # Get InstaQueries SCOPE_OPTICS_CREATE = "opticssurvey:create" # Create InstaQuery SCOPE_OPTICS_GET = "opticssurvey:read" # Read a InstaQuery # PREREQUISITES def load_server_url(): # pragma: no cover """Cleans and loads the server url from the configuration""" url = demisto.params()["server"] url = re.sub(r"/[\/]+$/", "", url) url = re.sub(r"\/$", "", url) return url # GLOBALS APP_ID = "" APP_SECRET = "" TID = "" SERVER_URL = "" FILE_THRESHOLD = "" USE_SSL = False # HELPERS def generate_jwt_times(): # pragma: no cover """ Generates the epoch time window in which the token will be valid Returns the current timestamp and the timeout timestamp (in that order) """ now = datetime.utcnow() timeout_datetime = now + timedelta(seconds=TOKEN_TIMEOUT) epoch_time = int((now - datetime(1970, 1, 1)).total_seconds()) epoch_timeout = int((timeout_datetime - datetime(1970, 1, 1)).total_seconds()) return epoch_time, epoch_timeout def api_call(uri, method="post", headers={}, body={}, params={}, accept_404=False, access_token=""): # pragma: no cover """ Makes an API call to the server URL with the supplied uri, method, headers, body and params """ if not headers: headers = {"Content-Type": "application/json", "Authorization": "Bearer " + access_token} url = f"{SERVER_URL}/{uri}" res = requests.request(method, url, headers=headers, data=json.dumps(body), params=params, verify=USE_SSL) if res.status_code < 200 or res.status_code >= 300: if res.status_code == 409 and str(res.content).find("already an entry for this threat") != -1: raise Warning(res.content) if res.status_code != 404 and not accept_404: return_error( "Got status code " + str(res.status_code) + " with body " + str(res.content) + " with headers " + str(res.headers) ) return json.loads(res.text) if res.text else res.ok def get_authentication_token(scope=None): """ Generates a JWT authorization token with an optional scope and queries the API for an access token Returns the received API access token """ # Generate token ID token_id = str(uuid.uuid4()) # Generate current time & token timeout epoch_time, epoch_timeout = generate_jwt_times() # Token claims claims = {"exp": epoch_timeout, "iat": epoch_time, "iss": "http://cylance.com", "sub": APP_ID, "tid": TID, "jti": token_id} if scope: claims["scp"] = scope # Encode the token encoded = jwt.encode(claims, APP_SECRET, algorithm="HS256") payload = {"auth_token": encoded} headers = {"Content-Type": "application/json; charset=utf-8"} res = api_call(method="post", uri=URI_AUTH, body=payload, headers=headers) return res["access_token"] def threat_to_incident(threat): incident = { "name": "Cylance Protect v2 threat " + threat["name"], "occurred": threat["last_found"] + "Z", "rawJSON": json.dumps(threat), } host_name = None devices = get_threat_devices_request(threat["sha256"], None, None)["page_items"] for device in devices: if device["date_found"] == threat["last_found"]: host_name = device["name"] labels = [ {"type": "Classification", "value": threat["classification"]}, {"type": "MD5", "value": threat["md5"]}, {"type": "SHA256", "value": threat["sha256"]}, {"type": "ThreatLastFound", "value": threat["last_found"]}, {"type": "HostName", "value": host_name}, ] incident["labels"] = labels return incident def normalize_score(score): # pragma: no cover """ Translates API raw float (-1 to 1) score to UI score (-100 to 100) """ return score * 100 def translate_score(score, threshold): # pragma: no cover if score > 0: dbot_score = 1 elif threshold <= score: dbot_score = 2 else: dbot_score = 3 return dbot_score # FUNCTIONS def test(): # pragma: no cover access_token = get_authentication_token() if not access_token: raise Exception("Unable to get access token") demisto.results("ok") def get_devices(): page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") result = get_devices_request(page, page_size) devices = result["page_items"] hr = [] devices_context = [] endpoint_context = [] for device in devices: current_device_context = { "AgentVersion": device["agent_version"], "DateFirstRegistered": device["date_first_registered"], "ID": device["id"], "IPAddress": device["ip_addresses"], "MACAdress": device["mac_addresses"], "Hostname": device["name"], "State": device["state"], } if device["policy"]: policy = {} if device["policy"]["id"]: policy["ID"] = device["policy"]["id"] if device["policy"]["name"]: policy["Name"] = device["policy"]["name"] if policy: current_device_context["Policy"] = policy devices_context.append(current_device_context) endpoint_context.append( {"IPAddress": device["ip_addresses"], "MACAdress": device["mac_addresses"], "Hostname": device["name"]} ) current_device = dict(device) current_device["ip_addresses"] = ", ".join(current_device["ip_addresses"]) current_device["mac_addresses"] = ", ".join(current_device["mac_addresses"]) current_device["policy"] = current_device["policy"]["name"] hr.append(current_device) ec = { "CylanceProtect.Device(val.ID && val.ID === obj.ID)": devices_context, "Endpoint(val.Hostname && val.Hostname === obj.Hostname)": endpoint_context, } entry = { "Type": entryTypes["note"], "Contents": devices, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("Cylance Protect Devices", hr, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } demisto.results(entry) def get_devices_request(page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_DEVICE_LIST) params = {} if page: params["page"] = page if page_size: params["page_size"] = page_size res = api_call(uri=URI_DEVICES, method="get", params=params, access_token=access_token) return res def get_device(): device_id = demisto.args()["id"] device = get_device_request(device_id) hr = [] if device: device_context = { "IPAddress": device["ip_addresses"], "MACAdress": device["mac_addresses"], "Hostname": device["host_name"], "OSVersion": device["os_version"], "UpdateAvailable": device["update_available"], "BackgroundDetection": device["background_detection"], "DateFirstRegistered": device["date_first_registered"], "DateLastModified": device["date_last_modified"], "DateOffline": device["date_offline"], "IsSafe": device["is_safe"], "LastLoggedInUser": device["last_logged_in_user"], "State": device["state"], "ID": device["id"], "Name": device["name"], } if device["update_type"]: device_context["UpdateType"] = device["update_type"] if device["policy"]: policy = {} if device["policy"]["id"]: policy["ID"] = device["policy"]["id"] if device["policy"]["name"]: policy["Name"] = device["policy"]["name"] if policy: device_context["Policy"] = policy endpoint_context = { "IPAddress": device["ip_addresses"], "MACAdress": device["mac_addresses"], "Hostname": device["host_name"], "OSVersion": device["os_version"], } ec = { "Endpoint(val.Hostname && val.Hostname === obj.Hostname)": endpoint_context, "CylanceProtect.Device(val.ID && val.ID === obj.ID)": device_context, } current_device = dict(device) current_device["ip_addresses"] = ", ".join(current_device["ip_addresses"]) current_device["mac_addresses"] = ", ".join(current_device["mac_addresses"]) current_device["policy"] = current_device["policy"]["name"] hr.append(current_device) else: ec = {} title = "Cylance Protect Device " + device_id entry = { "Type": entryTypes["note"], "Contents": device, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, hr, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } demisto.results(entry) def get_device_request(device_id): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_DEVICE_READ) uri = f"{URI_DEVICES}/{device_id}" res = api_call(uri=uri, method="get", access_token=access_token) return res def get_device_by_hostname(): hostname_id = demisto.args()["hostname"] device = get_hostname_request(hostname_id) title = "Cylance Protect Device " + hostname_id if device: device_context = { "AgentVersion": device["agent_version"], "IPAddress": device["ip_addresses"], "MACAdress": device["mac_addresses"], "Hostname": device["host_name"], "OSVersion": device["os_version"], "UpdateAvailable": device["update_available"], "BackgroundDetection": device["background_detection"], "DateFirstRegistered": device["date_first_registered"], "DateLastModified": device["date_last_modified"], "DateOffline": device["date_offline"], "IsSafe": device["is_safe"], "LastLoggedInUser": device["last_logged_in_user"], "State": device["state"], "ID": device["id"], "Name": device["name"], } if device["update_type"]: device_context["UpdateType"] = device["update_type"] if device["policy"]: policy = {} if device["policy"]["id"]: policy["ID"] = device["policy"]["id"] if device["policy"]["name"]: policy["Name"] = device["policy"]["name"] if policy: device_context["Policy"] = policy endpoint_context = { "IPAddress": device["ip_addresses"], "MACAdress": device["mac_addresses"], "Hostname": device["host_name"], "OSVersion": device["os_version"], } ec = { "Endpoint(val.Hostname && val.Hostname === obj.Hostname)": endpoint_context, "CylanceProtect.Device(val.ID && val.ID === obj.ID)": device_context, } current_device = dict(device) current_device["ip_addresses"] = ", ".join(current_device["ip_addresses"]) current_device["mac_addresses"] = ", ".join(current_device["mac_addresses"]) current_device["policy"] = current_device["policy"]["name"] hr = tableToMarkdown(title, [current_device], headerTransform=underscoreToCamelCase, removeNull=True) else: ec = {} hr = "### Device For Hostname " + hostname_id + " Was Not Found" entry = { "Type": entryTypes["note"], "Contents": device, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": ec, } demisto.results(entry) def get_hostname_request(hostname): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_DEVICE_READ) uri = f"{URI_HOSTNAME}/{hostname}" res = api_call(uri=uri, method="get", access_token=access_token) if not res: return None return res[0] def update_device(): device_id = demisto.args()["id"] name = demisto.args().get("name") policy_id = demisto.args().get("policyId") add_zones = demisto.args().get("addZones") remove_zones = demisto.args().get("removeZones") update_device_request(device_id, name, policy_id, add_zones, remove_zones) hr = {} if name: hr["Name"] = name if policy_id: hr["PolicyID"] = policy_id if add_zones: hr["AddedZones"] = add_zones if remove_zones: hr["RemovedZones"] = remove_zones device = hr.copy() device["id"] = device_id title = "Device " + device_id + " was updated successfully." entry = { "Type": entryTypes["note"], "Contents": device, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, [hr]), } demisto.results(entry) def update_device_request(device_id, name=None, policy_id=None, add_zones=None, remove_zones=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_DEVICE_UPDATE) body = {} if name: body["name"] = name if policy_id: body["policy_id"] = policy_id if add_zones: body["add_zone_ids"] = [add_zones] if remove_zones: body["remove_zone_ids"] = [remove_zones] # Do we have anything to update? if not body: raise Exception("No changes detected") uri = f"{URI_DEVICES}/{device_id}" res = api_call(uri=uri, method="put", access_token=access_token, body=body) return res def get_device_threats(): device_id = demisto.args()["id"] page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") device_threats = get_device_threats_request(device_id, page, page_size)["page_items"] dbot_score_array = [] for threat in device_threats: dbot_score = 0 score = threat.get("cylance_score", None) if score: threat["cylance_score"] = normalize_score(threat["cylance_score"]) threshold = demisto.args().get("threshold", FILE_THRESHOLD) dbot_score = translate_score(threat["cylance_score"], int(threshold)) dbot_score_array.append(create_dbot_score_entry(threat, dbot_score).to_context()) if device_threats: dbot_score_dict = {Common.DBotScore.get_context_path(): []} # type: Dict[str, List[Dict[str, str]]] for dbot_score_entry in dbot_score_array: for _key, value in list(dbot_score_entry.items()): dbot_score_dict[Common.DBotScore.get_context_path()].append(value) threats_context = createContext(data=device_threats, keyTransform=underscoreToCamelCase) threats_context = add_capitalized_hash_to_context(threats_context) ec = {"File": threats_context} ec.update(dbot_score_dict) title = "Cylance Protect Device Threat " + device_id demisto.results( { "Type": entryTypes["note"], "Contents": device_threats, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, device_threats, headerTransform=underscoreToCamelCase), "EntryContext": ec, } ) else: demisto.results("No threats found.") def get_device_threats_request(device_id, page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_DEVICE_THREAT_LIST) params = {} if page: params["page"] = page if page_size: params["page_size"] = page_size uri = f"{URI_DEVICES}/{device_id}/threats" res = api_call(uri=uri, method="get", access_token=access_token, params=params) return res def get_policies(): page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") policies = get_policies_request(page, page_size)["page_items"] context_policies = createContext(data=policies, keyTransform=underscoreToCamelCase) ec = {"CylanceProtect.Policies(val.id && val.id === obj.id)": context_policies} title = "Cylance Protect Policies" entry = { "Type": entryTypes["note"], "Contents": policies, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, policies, headerTransform=underscoreToCamelCase), "EntryContext": ec, } demisto.results(entry) def get_policies_request(page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_POLICY_LIST) params = {} if page: params["page"] = page if page_size: params["page_size"] = page_size res = api_call(uri=URI_POLICIES, method="get", access_token=access_token, params=params) return res def create_zone(): name = demisto.args()["name"] policy_id = demisto.args()["policy_id"] criticality = demisto.args()["criticality"] zone = create_zone_request(name, policy_id, criticality) title = "Zone " + name + " was created successfully." demisto.results( { "Type": entryTypes["note"], "Contents": zone, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, [zone], headerTransform=underscoreToCamelCase), } ) def create_zone_request(name, policy_id, criticality): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_ZONE_CREATE) body = {"name": name, "policy_id": policy_id, "criticality": criticality} res = api_call(uri=URI_ZONES, method="post", access_token=access_token, body=body) return res def get_zones(): page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") zones = get_zones_request(page, page_size)["page_items"] context_zones = createContext(data=zones, keyTransform=underscoreToCamelCase, removeNull=True) ec = {"CylanceProtect.Zones(val.Id && val.Id === obj.Id)": context_zones} title = "Cylance Protect Zones" demisto.results( { "Type": entryTypes["note"], "Contents": zones, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, zones, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) def get_zones_request(page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_ZONE_LIST) params = {} if page: params["page"] = page if page_size: params["page_size"] = page_size res = api_call(uri=URI_ZONES, method="get", access_token=access_token, params=params) return res def get_zone(): zone_id = demisto.args()["id"] zone = get_zone_request(zone_id) context_zone = createContext(data=zone, keyTransform=underscoreToCamelCase, removeNull=True) ec = {"CylanceProtect.Zones(val.Id && val.Id === obj.Id)": context_zone} title = "Cylance Protect Zone " + zone_id demisto.results( { "Type": entryTypes["note"], "Contents": zone, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, zone, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) def get_zone_request(zone_id): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_ZONE_READ) uri = f"{URI_ZONES}/{zone_id}" res = api_call(uri=uri, method="get", access_token=access_token) return res def update_zone(): zone_id = demisto.args()["id"] # Get current zone and fill in requires missing arguments current_zone = get_zone_request(zone_id) # Details to update name = demisto.args()["name"] if "name" in demisto.args() else current_zone["name"] policy_id = demisto.args()["policy_id"] if "policy_id" in demisto.args() else current_zone["policy_id"] criticality = demisto.args()["criticality"] if "criticality" in demisto.args() else current_zone["criticality"] zone = update_zone_request(zone_id, name, policy_id, criticality) hr = {} if name: hr["Name"] = name if policy_id: hr["PolicyID"] = policy_id if criticality: hr["Criticality"] = criticality title = "Zone was updated successfully." demisto.results( { "Type": entryTypes["note"], "Contents": zone, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, [hr]), } ) def update_zone_request(zone_id, name, policy_id, criticality): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_ZONE_UPDATE) body = {} if name: body["name"] = name if policy_id: body["policy_id"] = policy_id if criticality: body["criticality"] = criticality # Do we have anything to update? if not body: raise Exception("No changes detected") uri = f"{URI_ZONES}/{zone_id}" res = api_call(uri=uri, method="put", access_token=access_token, body=body) return res def get_threat(): sha256 = demisto.args().get("sha256") threat = get_threat_request(sha256) if threat: dbot_score = 0 score = threat.get("cylance_score", None) if score: threat["cylance_score"] = normalize_score(threat["cylance_score"]) threshold = demisto.args().get("threshold", FILE_THRESHOLD) dbot_score = translate_score(threat["cylance_score"], int(threshold)) context_threat = createContext(data=threat, keyTransform=underscoreToCamelCase, removeNull=True) context_threat = add_capitalized_hash_to_context(context_threat) ec = {"File": context_threat} ec.update(create_dbot_score_entry(threat, dbot_score).to_context()) title = "Cylance Protect Threat " + sha256 demisto.results( { "Type": entryTypes["note"], "Contents": threat, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, threat, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) else: demisto.results("Threat was not found.") def get_threat_request(sha256): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_THREAT_READ) uri = f"{URI_THREATS}/{sha256}" res = api_call(uri=uri, method="get", access_token=access_token, body={}, params={}, accept_404=False) return res def create_dbot_score_entry(threat, dbot_score): dbot_score_entry = Common.DBotScore( threat.get("sha256"), DBotScoreType.FILE, integration_name="Cylance Protect", score=dbot_score ) return dbot_score_entry def get_threats(): page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") threats = get_threats_request(page, page_size)["page_items"] dbot_score_array = [] for threat in threats: dbot_score = 0 score = threat.get("cylance_score", None) if score: threat["cylance_score"] = normalize_score(threat["cylance_score"]) threshold = demisto.args().get("threshold", FILE_THRESHOLD) dbot_score = translate_score(threat["cylance_score"], int(threshold)) dbot_score_array.append(create_dbot_score_entry(threat, dbot_score).to_context()) dbot_score_dict = {Common.DBotScore.get_context_path(): []} # type: Dict[str, List[Dict[str, str]]] for dbot_score_entry in dbot_score_array: for _key, value in list(dbot_score_entry.items()): dbot_score_dict[Common.DBotScore.get_context_path()].append(value) context_threat = createContext(data=threats, keyTransform=underscoreToCamelCase, removeNull=True) context_threat = add_capitalized_hash_to_context(context_threat) ec = {"File": context_threat} ec.update(dbot_score_dict) title = "Cylance Protect Threats" demisto.results( { "Type": entryTypes["note"], "Contents": threats, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, threats, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) def get_threats_request(page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_THREAT_LIST) params = {} if page in demisto.args(): params["page"] = demisto.args()["page"] if page_size in demisto.args(): params["page_size"] = demisto.args()["pageSize"] res = api_call(uri=URI_THREATS, method="get", access_token=access_token, params=params) return res def get_threat_devices(): threat_hash = demisto.args()["sha256"] page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") threats = get_threat_devices_request(threat_hash, page, page_size)["page_items"] if threats: threats_context = threats[:] for threat in threats: threat["ip_addresses"] = ", ".join(threat["ip_addresses"]) threat["mac_addresses"] = ", ".join(threat["mac_addresses"]) file_paths = [] endpoint_context = [] devices_context = [] for threat in threats_context: endpoint_context.append( {"Hostname": threat["name"], "IPAddress": threat["ip_addresses"], "MACAddress": threat["mac_addresses"]} ) current_device = { "Hostname": threat["name"], "IPAddress": threat["ip_addresses"], "MACAddress": threat["mac_addresses"], "AgentVersion": threat["agent_version"], "DateFound": threat["date_found"], "FilePath": threat["file_path"], "ID": threat["id"], "State": threat["state"], "FileStatus": threat["file_status"], } if threat["policy_id"]: current_device["PolicyID"] = threat["policy_id"] devices_context.append(current_device) file_path = threat.pop("file_path") file_paths.append({"FilePath": file_path}) file_context = {"SHA256": threat_hash, "Path": file_paths} ec = { "File": file_context, "Endpoint(val.Hostname && val.Hostname === obj.Hostname)": endpoint_context, "CylanceProtect.Threat(val.SHA256 && val.SHA256 === obj.SHA256)": {"SHA256": threat_hash, "Devices": devices_context}, } title = "Cylance Protect Threat " + threat_hash + " Devices" demisto.results( { "Type": entryTypes["note"], "Contents": threats, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, threats, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) else: demisto.results("No devices found on given threat.") def get_threat_devices_request(threat_hash, page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_THREAT_DEVICE_LIST) params = {} if page: params["page"] = page if page_size: params["page_size"] = page_size uri = f"{URI_THREATS}/{threat_hash}/devices" res = api_call(uri=uri, method="get", access_token=access_token, params=params) return res def get_list(): page = demisto.args().get("pageNumber") page_size = demisto.args().get("pageSize") lst = get_list_request(demisto.args()["listTypeId"], page, page_size)["page_items"] dbot_score_array = [] for threat in lst: dbot_score = 0 score = threat.get("cylance_score", None) if score: threat["cylance_score"] = normalize_score(threat["cylance_score"]) threshold = demisto.args().get("threshold", FILE_THRESHOLD) dbot_score = translate_score(threat["cylance_score"], int(threshold)) dbot_score_array.append(create_dbot_score_entry(threat, dbot_score).to_context()) if lst: dbot_score_dict = {Common.DBotScore.get_context_path(): []} # type: Dict[str, List[Dict[str, str]]] for dbot_score_entry in dbot_score_array: for _key, value in list(dbot_score_entry.items()): dbot_score_dict[Common.DBotScore.get_context_path()].append(value) context_list = createContext(data=lst, keyTransform=underscoreToCamelCase, removeNull=True) context_list = add_capitalized_hash_to_context(context_list) ec = {"File": context_list} ec.update(dbot_score_dict) title = "Cylance Protect Global List" demisto.results( { "Type": entryTypes["note"], "Contents": lst, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, lst, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) else: demisto.results("No list of this type was found.") def get_list_request(list_type_id, page=None, page_size=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_GLOBAL_LIST) params = {} if list_type_id == "GlobalQuarantine": params["listTypeId"] = 0 else: # List Type ID is GlobalSafe params["listTypeId"] = 1 if page: params["page"] = page if page_size: params["page_size"] = page_size res = api_call(uri=URI_LISTS, method="get", access_token=access_token, params=params) return res def get_list_entry_by_hash(sha256=None, list_type_id=None): if not sha256: sha256 = demisto.args()["sha256"] if not list_type_id: list_type_id = demisto.args()["listTypeId"] total_pages = 0 current_page = 0 found_hash = None while not found_hash and total_pages >= current_page: if not current_page: current_page = 1 lst = get_list_request(list_type_id, current_page, 200) if not total_pages: total_pages = lst["total_pages"] for i in lst["page_items"]: if i["sha256"] == sha256: found_hash = i break current_page += 1 if demisto.command() == "cylance-protect-get-list-entry": if found_hash: context_list = createContext(data=found_hash, keyTransform=underscoreToCamelCase, removeNull=True) ec = {"CylanceListSearch": context_list} title = "Cylance Protect Global List Entry" demisto.results( { "Type": entryTypes["note"], "Contents": found_hash, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(title, found_hash, headerTransform=underscoreToCamelCase, removeNull=True), "EntryContext": ec, } ) return None else: demisto.results("Hash not found") return None else: return found_hash def get_indicators_report(): # pragma: no cover url = "https://protect.cylance.com/Reports/ThreatDataReportV1/indicators/" + demisto.args()["token"] res = requests.request("GET", url, verify=USE_SSL) filename = "Indicators_Report.csv" demisto.results(fileResult(filename, res.content)) def update_device_threats(): # pragma: no cover device_id = demisto.args()["device_id"] threat_id = demisto.args()["threat_id"] event = demisto.args()["event"] update_device_threats_request(device_id, threat_id, event) demisto.results("Device threat was updated successfully.") def update_device_threats_request(device_id, threat_id, event): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_THREAT_UPDATE) body = {"threat_id": threat_id, "event": event} uri = f"{URI_DEVICES}/{device_id}/threats" res = api_call(uri=uri, method="post", access_token=access_token, body=body) return res def download_threat(): contents = {} context = {} dbot_score = 0 sha256 = demisto.args()["sha256"] threat_url = download_threat_request(sha256) threat_file = requests.get(threat_url, allow_redirects=True, verify=USE_SSL) if threat_file.status_code == 200: if demisto.args()["unzip"] == "yes": file_archive = BytesIO(threat_file.content) zip_file = zipfile.ZipFile(file_archive) file_data = zip_file.read(sha256.upper(), pwd=b"infected") demisto.results(fileResult(sha256, file_data)) else: demisto.results(fileResult(sha256, threat_file.content + b".zip")) else: return_error("Could not fetch the file") threat = get_threat_request(sha256) if threat: # add data about the threat if found if threat.get("cylance_score"): score = normalize_score(threat.get("cylance_score")) threshold = demisto.args().get("threshold", FILE_THRESHOLD) dbot_score = translate_score(score, int(threshold)) contents = { "Download URL": threat_url, "File Name": threat.get("name"), "File Size": threat.get("file_size"), "Detected By": threat.get("detected_by"), "GlobalQuarantine": threat.get("global_quarantined"), "Safelisted": threat.get("safelisted"), "Timestamp": threat.get("cert_timestamp"), } context[outputPaths["file"]] = { "DownloadURL": threat_url, "SHA256": threat.get("sha256"), "Name": threat.get("name"), "Size": threat.get("file_size"), "Safelisted": threat.get("safelisted"), "Timestamp": threat.get("cert_timestamp"), "MD5": threat.get("md5"), } if dbot_score == 3: context[outputPaths["file"]]["Malicious"] = { "Vendor": "Cylance Protect", "Description": "Score determined by get threat command", } context[outputPaths["dbotscore"]] = { "Indicator": threat.get("sha256"), "Type": "file", "Vendor": "Cylance Protect", "Score": dbot_score, } demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": contents, "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown( "Cylance Protect - Downloading threat attached to the following hash: " + sha256, contents ), "EntryContext": context, } ) def download_threat_request(hash): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_THREAT_READ) uri = "{}/{}/{}".format(URI_THREATS, "download", hash) res = api_call(uri=uri, method="get", access_token=access_token) if not isinstance(res, dict) or not res.get("url"): error_msg = f"No url was found - {res}" if isinstance(res, str) else "No url was found" return_error(error_msg) return res["url"] def add_hash_to_list(): context = {} sha256 = demisto.args().get("sha256") list_type = demisto.args().get("listType") reason = demisto.args().get("reason") category = demisto.args().get("category") if list_type == "GlobalSafe" and not category: return_error("Category argument is required for list type of Global Safe") add_hash = add_hash_to_list_request(sha256, list_type, reason, category) if not add_hash: return_error("Could not add hash to list") contents = {"Threat File SHA256": sha256, "List Type": list_type, "Category": category, "Reason": reason} context[outputPaths["file"]] = {"SHA256": sha256, "Cylance": {"ListType": list_type, "Category": category}} demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": contents, "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown( f"The requested threat has been successfully added to {list_type} hashlist.", contents ), "EntryContext": context, } ) def add_hash_to_list_request(sha256, list_type, reason, category=None): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_GLOBAL_LIST_CREATE) body = {"sha256": sha256, "list_type": list_type, "reason": reason} if category: body["category"] = category.replace(" ", "") res = api_call(uri=URI_LISTS, method="post", access_token=access_token, body=body) return res def delete_hash_from_lists(): sha256 = demisto.args().get("sha256") list_type = demisto.args().get("listType") context = {} delete_hash_from_lists_request(sha256, list_type) contents = {"Threat File SHA256": sha256, "Threat List Type": list_type} context[outputPaths["file"]] = {"SHA256": sha256, "Cylance": {"ListType": list_type}} demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": contents, "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown( f"The requested threat has been successfully removed from {list_type} hashlist.", contents ), "EntryContext": context, } ) def delete_hash_from_lists_request(sha256, list_type): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_GLOBAL_LIST_DELETE) body = {"sha256": sha256, "list_type": list_type} res = api_call(uri=URI_LISTS, method="delete", access_token=access_token, body=body) return res def delete_devices(): device_ids = demisto.args().get("deviceIds") device_ids_list = argToList(device_ids) contents = [] context_list = [] for device_id in device_ids_list: device = get_device_request(device_id) if not device: continue device_name = device.get("name") context_list.append({"Id": device_id, "Name": device_name, "Deleted": True}) contents.append({"Device Removed": device_id, "Device Name": device_name, "Deletion status": True}) batch_size = demisto.args().get("batch_size", 20) try: batch_size = int(batch_size) except ValueError: return_error("Error: Batch Size specified must represent an int.") for i in range(0, len(device_ids_list), batch_size): current_deleted_devices_batch = device_ids_list[i : i + batch_size] delete_devices_request(current_deleted_devices_batch) context = {"Cylance.Device(val.Id && val.Id == obj.Id)": context_list} demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": contents, "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown( "The requested devices have been successfully removed from your organization list.", contents ), "EntryContext": context, } ) def delete_devices_request(device_ids): # pragma: no cover access_token = get_authentication_token() body = {"device_ids": device_ids} res = api_call(uri=URI_DEVICES, method="delete", access_token=access_token, body=body) if not res or not res.get("request_id"): return_error("Delete response does not contain request id") return res def get_policy_details(): policy_id = demisto.args()["policyID"] contents = {} # type: Dict context = {} # type: Dict title = "Could not find policy details for that ID" filetype_actions_threat_contents = [] # type: list filetype_actions_suspicious_contents = [] # type: list safelist_contents = [] # type: list title_filetype_actions_threat = "Cylance Policy Details - FileType Actions Threat Files" title_filetype_actions_suspicious = "Cylance Policy Details - FileType Actions Suspicious Files" title_safelist = "Cylance Policy Details - File Exclusions - SafeList" title_memory_exclusion = ( "Cylance Policy Details - Memory Violation Actions \n" + "This table provides detailed information about the memory violation settings. \n" + "Memory protections Exclusion List :" ) title_memory_violation = "Memory Violation Settings: " title_additional_settings = ( "Cylance Policy Details - Policy Settings. \n" + "Various policy settings are contained within this section." ) policy_details = get_policy_details_request(policy_id) memory_violations_content = [] if policy_details: title = "Cylance Policy Details for: " + policy_id date_time = "" # timestamp in response comes back as bugged string, convert to actual timestamp. timestamp = policy_details.get("policy_utctimestamp") if timestamp: reg = re.search(r"\d{13}", timestamp) if reg: ts = float(reg.group()) date_time = datetime.fromtimestamp(ts / 1000).strftime("%Y-%m-%dT%H:%M:%S.%f+00:00") context = {"ID": policy_details.get("policy_id"), "Name": policy_details.get("policy_name"), "Timestamp": date_time} contents = {"Policy Name": policy_details.get("policy_name"), "Policy Created At": date_time} suspicious_files = policy_details.get("filetype_actions").get("suspicious_files") if suspicious_files: suspicious_files_list = [] for file in suspicious_files: suspicious_files_list.append({"Actions": file.get("actions"), "File Type": file.get("file_type")}) threat_files = policy_details.get("filetype_actions").get("threat_files") if threat_files: threat_files_list = [] for file in threat_files: threat_files_list.append({"Actions": file.get("actions"), "File Type": file.get("file_type")}) filetype_actions_suspicious_contents = suspicious_files_list filetype_actions_threat_contents = threat_files_list safelist = policy_details.get("file_exclusions") if safelist: file_exclusions_list = [] for file_exclusion in safelist: file_exclusions_list.append( { "Research Class ID": file_exclusion.get("research_class_id"), "Infinity": file_exclusion.get("infinity"), "File Type": file_exclusion.get("file_type"), "AV Industry": file_exclusion.get("av_industry"), "Cloud Score": file_exclusion.get("cloud_score"), "File Hash": file_exclusion.get("file_hash"), "Research Subclass ID": file_exclusion.get("research_subclass_id"), "Reason": file_exclusion.get("reason"), "File Name": file_exclusion.get("file_name"), "Category Id": file_exclusion.get("category_id"), "MD5": file_exclusion.get("md5"), } ) safelist_contents = file_exclusions_list memory_violations = policy_details.get("memoryviolation_actions").get("memory_violations") for memory_violation in memory_violations: memory_violations_content.append( {"Action": memory_violation.get("action"), "Violation Type": memory_violation.get("violation_type")} ) additional_settings = policy_details.get("policy") additional_settings_content = [] for additional_setting in additional_settings: additional_settings_content.append({"Name": additional_setting.get("name"), "Value": additional_setting.get("value")}) context.update(policy_details) results = CommandResults( outputs=context, outputs_prefix="Cylance.Policy", outputs_key_field="policy_id", readable_output=tableToMarkdown(title, contents) + tableToMarkdown(title_filetype_actions_suspicious, filetype_actions_suspicious_contents) + tableToMarkdown(title_filetype_actions_threat, filetype_actions_threat_contents) + tableToMarkdown(title_safelist, safelist_contents) + tableToMarkdown(title_memory_exclusion, policy_details.get("memory_exclusion_list")) + tableToMarkdown(title_memory_violation, memory_violations_content) + tableToMarkdown(title_additional_settings, memory_violations_content), raw_response=policy_details, ) return_results(results) def get_policy_details_request(policy_id): # pragma: no cover access_token = get_authentication_token(scope=SCOPE_POLICY_READ) uri = f"{URI_POLICIES}/{policy_id}" res = api_call(uri=uri, method="get", access_token=access_token) return res def create_instaquery_request(name, description, artifact, value_type, match_values, match_type, zone_list): # Create request data = { "name": name, "description": description, "artifact": artifact, "match_value_type": value_type, "match_values": match_values, "case_sensitive": False, "match_type": match_type, "zones": zone_list, } access_token = get_authentication_token([SCOPE_OPTICS_CREATE, SCOPE_OPTICS_GET]) headers = {"Content-Type": "application/json", "Authorization": "Bearer " + access_token} uri = URI_OPTICS res = api_call(uri=uri, method="post", body=data, headers=headers) return res def create_instaquery(): query_args = demisto.args() name = query_args.get("name") description = query_args.get("description") artifact = query_args.get("artifact") match_value_type = query_args.get("match_value_type") match_values = query_args.get("match_values").split(",") match_type = query_args.get("match_type") zones = "".join(query_args.get("zone").split("-")).upper() # Remove '-' and upper case zone_list = zones.split(",") # Process the match value if artifact in match_value_type: value_type = re.findall(r"(?<=\.).*", match_value_type)[0] # Remove the artifact prefix else: value_type = [] demisto.error("The value type is not suitable with the selected artifact") # Create request res = create_instaquery_request(name, description, artifact, value_type, match_values, match_type, zone_list) if res: # Return results to context and war room results = CommandResults(outputs=res, outputs_prefix="InstaQuery.New", outputs_key_field="id") return_results(results) def get_instaquery_result_request(query_id): # Create request access_token = get_authentication_token([SCOPE_OPTICS_GET, SCOPE_OPTICS_CREATE]) headers = {"Content-Type": "application/json", "Authorization": "Bearer " + access_token} # Endpoint format /instaqueries/v2/{queryID}/results uri = URI_OPTICS + "/" + query_id + "/results" res = api_call(uri=uri, method="get", headers=headers) return res def get_instaquery_result(): query_id = demisto.args().get("query_id") res = get_instaquery_result_request(query_id) if res["result"]: results_count = len(res.get("result")) result_title = ( str(results_count) + " results found, find more details in context. Here is the 1st result:" if results_count > 1 else "1 result found:" ) readable_results = tableToMarkdown(result_title, json.loads(res["result"][0]["Result"]).get("Properties")) else: readable_results = "### No result found" # Return results to context and war room results = CommandResults( outputs=res, outputs_prefix="InstaQuery.Results", outputs_key_field="id", readable_output=readable_results ) return_results(results) def list_instaquery_request(page, page_size): # Create request access_token = get_authentication_token([SCOPE_OPTICS_LIST, SCOPE_OPTICS_GET]) headers = {"Content-Type": "application/json", "Authorization": "Bearer " + access_token} # Endpoint format /instaqueries/v2/{queryID}/results uri = URI_OPTICS + "?page=" + page + "&page_size=" + page_size res = api_call(uri=uri, method="get", headers=headers) return res def list_instaquery(): page = demisto.args().get("page_number") page_size = demisto.args().get("page_size") res = list_instaquery_request(page, page_size) if res: # Return results to context and war room results = CommandResults( outputs=res, outputs_prefix="InstaQuery.List", ) return_results(results) def fetch_incidents(): now = datetime.utcnow() last_run = demisto.getLastRun().get("time") if last_run is None: now = now - timedelta(days=3) last_run = now else: last_run = datetime.strptime(last_run, "%Y-%m-%dT%H:%M:%S") # Converts string to datetime object current_run = last_run threats = get_threats_request().get("page_items", []) incidents = [] for threat in threats: last_found = datetime.strptime(threat["last_found"], "%Y-%m-%dT%H:%M:%S") if last_found > last_run: incident = threat_to_incident(threat) incidents.append(incident) if last_found > current_run: current_run = last_found demisto.incidents(incidents) demisto.setLastRun({"time": current_run.isoformat().split(".")[0]}) def add_capitalized_hash_to_context(threats_context): # pragma: no cover """Add capitalized hash keys to the context such as SHA256 and MD5, the keys are redundant since they are used for avoiding BC issues. Args: threats_context(list): list of dicts of context outputs for the threats of interest, each containing the key 'Sha256' (and possibly (Md5)). Returns: threats_context(list): list of dicts of context outputs for the threats of interest, each containing the key and value 'Sha256' (and possibly Md5) as well as the key and value 'SHA256' (and possible MD5). """ if not isinstance(threats_context, list): threats_context = [threats_context] for context_item in threats_context: if context_item.get("Sha256"): context_item["SHA256"] = context_item.get("Sha256") if context_item.get("Md5"): context_item["MD5"] = context_item.get("Md5") return threats_context # EXECUTION def main(): # pragma: no cover params = demisto.params() global APP_ID APP_ID = params.get("app_creds", {}).get("identifier") or params.get("app_id") global APP_SECRET APP_SECRET = params.get("app_creds", {}).get("password") or params.get("app_secret") global TID TID = params.get("api_key", {}).get("password") or params.get("tid") global SERVER_URL SERVER_URL = load_server_url() global FILE_THRESHOLD FILE_THRESHOLD = params["file_threshold"] global USE_SSL USE_SSL = not params.get("unsecure", False) command = demisto.command() if not all((APP_ID, APP_SECRET, TID)): raise DemistoException("Please fill out the Application ID, Application Secret and Tenant API Key") LOG(f"Command being called is {command}") try: handle_proxy() if demisto.command() == "test-module": test() if demisto.command() == "fetch-incidents": fetch_incidents() elif demisto.command() == "cylance-protect-get-devices": get_devices() elif demisto.command() == "cylance-protect-get-device": get_device() elif demisto.command() == "cylance-protect-get-device-by-hostname": get_device_by_hostname() elif demisto.command() == "cylance-protect-update-device": update_device() elif demisto.command() == "cylance-protect-get-device-threats": get_device_threats() elif demisto.command() == "cylance-protect-get-policies": get_policies() elif demisto.command() == "cylance-protect-create-zone": create_zone() elif demisto.command() == "cylance-protect-get-zones": get_zones() elif demisto.command() == "cylance-protect-get-zone": get_zone() elif demisto.command() == "cylance-protect-update-zone": update_zone() elif demisto.command() == "cylance-protect-get-threat": get_threat() elif demisto.command() == "cylance-protect-get-threats": get_threats() elif demisto.command() == "cylance-protect-get-threat-devices": get_threat_devices() elif demisto.command() == "cylance-protect-get-indicators-report": get_indicators_report() elif demisto.command() == "cylance-protect-update-device-threats": update_device_threats() elif demisto.command() == "cylance-protect-get-list": get_list() elif demisto.command() == "cylance-protect-get-list-entry": get_list_entry_by_hash() # new commands elif demisto.command() == "cylance-protect-download-threat": download_threat() elif demisto.command() == "cylance-protect-add-hash-to-list": add_hash_to_list() elif demisto.command() == "cylance-protect-delete-hash-from-lists": delete_hash_from_lists() elif demisto.command() == "cylance-protect-delete-devices": delete_devices() elif demisto.command() == "cylance-protect-get-policy-details": get_policy_details() # Optics InstaQuery command elif demisto.command() == "cylance-optics-create-instaquery": create_instaquery() elif demisto.command() == "cylance-optics-get-instaquery-result": get_instaquery_result() elif demisto.command() == "cylance-optics-list-instaquery": list_instaquery() except Warning as w: demisto.results({"Type": 11, "Contents": str(w), "ContentsFormat": formats["text"]}) except Exception as e: demisto.error("#### error in Cylance Protect v2: " + str(e)) if demisto.command() == "fetch-incidents": LOG.print_log() raise else: return_error(str(e)) if __name__ in ("__builtin__", "builtins", "__main__"): main()



