Abnormal Security
Abnormal Security detects the whole spectrum of email attacks, from vendor email compromise and spear-phishing to unwanted email spam and graymail. To stop these advanced attacks, Abnormal leverages the industry’s most advanced behavioral data science to baseline known good behavior and detects anomalies.
Data Enrichment & Threat Intelligence · Abnormal Security
Details
| ID | Abnormal Security |
|---|---|
| Provider | Abnormal Security |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Abnormal Security detects the whole spectrum of email attacks, from vendor email compromise and spear-phishing to unwanted email spam and graymail. To stop these advanced attacks, Abnormal leverages the industry’s most advanced behavioral data science to baseline known good behavior and detects anomalies.
This integration was integrated and tested with version 1.3.0 of Abnormal Security
Configure Abnormal Security in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g. https://api.abnormalplatform.com/v1) | True | |
| API Key | True | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch incidents | Retrieves incidents based on the customer’s selection from three categories: Threats, Account Takeover Cases, and Abuse Campaigns | False |
| Maximum incidents to fetch. | Maximum number of incidents per fetch. The default value is 200. | False |
| Fetch Threats | False | |
| Fetch Abuse Campaigns | False | |
| Fetch Account Takeover Cases | False | |
| First fetch time | First alert created date to fetch. e.g., “1 min ago”,”2 weeks ago”,”3 months ago” | False |
| Incident type | False | |
| Incidents Fetch Interval | False | |
| Polling Lag Time (in minutes) | Time in minutes to subtract from polling time window for data consistency (Default : 2 mins) | False |
| Maximum incidents pages to fetch | Maximum number of pages to fetch for incidents | False |
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.
abnormal-security-check-case-action-status
Check the status of an action requested on a case.
Base Command
abnormal-security-check-case-action-status
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | A string representing the email case. Can be retrieved by first running command to list cases. | Required |
| action_id | A string representing the email case. Can be retrieved from payload after performing an action on a case. | Required |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.ActionStatus.status | String | Status of the case after an action is performed |
| AbnormalSecurity.ActionStatus.description | String | Detailed description of the status |
Command Example
!abnormal-security-check-case-action-status case_id=12345 action_id=abcdefgh-1234-5678-ijkl-mnop9qrstuvwx
Context Example
{
"AbnormalSecurity": {
"ActionStatus": {
"description": "The request was completed successfully",
"status": "acknowledged"
}
}
}
Human Readable Output
Results
description status The request was completed successfully acknowledged
abnormal-security-check-threat-action-status
Check the status of an action requested on a threat.
Base Command
abnormal-security-check-threat-action-status
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | A UUID representing a threat campaign. Full list of threat IDs can be obtained by first running the command to list a threat. | Required |
| action_id | A UUID representing the action id for a threat. Can be obtained from payload after performing an action on the threat. | Required |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.ActionStatus.status | String | The status of a threat after performing an action on it |
| AbnormalSecurity.ActionStatus.description | String | The description of the status |
Command Example
!abnormal-security-check-threat-action-status threat_id=xwvutsrq-9pon-mlkj-i876-54321hgfedcba action_id=abcdefgh-1234-5678-ijkl-mnop9qrstuvwx
Context Example
{
"AbnormalSecurity": {
"ActionStatus": {
"description": "The request was completed successfully",
"status": "acknowledged"
}
}
}
Human Readable Output
Results
description status The request was completed successfully acknowledged
abnormal-security-download-threat-log-csv
Download data from Threat Log in .csv format
Base Command
abnormal-security-download-threat-log-csv
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Filter the results based on a filter key. Value must be of the format filter={FILTER KEY} gte YYYY-MM-DDTHH:MM:SSZ lte YYYY-MM-DDTHH:MM:SSZ. Supported keys - [receivedTime]. |
Optional |
| mock-data | Returns test data if set to True. |
Optional |
| source | Filters threats based on the source of detection. | Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
There is no context output for this command.
Command Example
!abnormal-security-download-threat-log-csv filter="receivedTime gte 2020-12-01T01:01:01Z"
Context Example
{
"File": {
"EntryID": "2294@2ef16ace-2149-42b9-8b0f-fb7620ba7d44",
"Extension": "csv",
"Info": "csv",
"MD5": "a981545ee72fe115888800725883ca8a",
"Name": "threat_log.csv",
"SHA1": "c3cbae11542dc7244e3bf04a0901d7063597d381",
"SHA256": "296463cad959803d64bfc94fbffa24e30c9438ba58827a100a9e7c219f26b382",
"SHA512": "21a53f61c7d22b533abd7181b16116bf9017b7a444c10e4d2336803794ef0d9dded56e65179f924252f0bf3231e35fa1b726c8d7723f10b2f08bae0b3bedddd1",
"SSDeep": "12:dB2XRzmZIm88Rvu8R7b7+I78RQC5+GUHwgfdvvq:dB2XRMrt/C5+GYw",
"Size": 449,
"Type": "ASCII text, with CRLF line terminators"
}
}
abnormal-security-list-abuse-mailbox-campaigns
Get a list of campaigns submitted to Abuse Mailbox
Base Command
abnormal-security-list-abuse-mailbox-campaigns
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Value must be of the format filter={FILTER KEY} gte YYYY-MM-DDTHH:MM:SSZ lte YYYY-MM-DDTHH:MM:SSZ. A {FILTER KEY} must be specified, and currently only the key lastReportedTime is supported for /abusecampaigns. At least one of gte/lte must be specified, with a datetime string following the YYYY-MM-DDTHH:MM:SSZ format. Do note that provided filter time is in UTC. |
Optional |
| page_size | Number of abuse campaigns shown on each page. Each page of data will have at most page_size abuse campaign IDs. | Optional |
| page_number | 1-indexed page number to get a particular page of threats. Has no effect if filter is not specified. | Optional |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.AbuseCampaign.campaigns.campaignId | String | An id which maps to an abuse campaign. |
| AbnormalSecurity.AbuseCampaign.pageNumber | Number | The current page number. |
| AbnormalSecurity.AbuseCampaign.nextPageNumber | Number | The next page number. |
Command Example
!abnormal-security-list-abuse-mailbox-campaigns filter="lastReportedTime gte 2020-12-01T01:01:01Z"
Context Example
{
"AbnormalSecurity": {
"AbuseCampaign": {
"campaigns": [
{
"campaignId": "fff51768-c446-34e1-97a8-9802c29c3ebd"
},
{
"campaignId": "07434ea5-df7b-3ff4-8d07-4a82df0c655d"
}
],
"pageNumber": 1
}
}
}
Human Readable Output
List of Abuse Mailbox Campaigns
Campaign IDs
campaignId fff51768-c446-34e1-97a8-9802c29c3ebd 07434ea5-df7b-3ff4-8d07-4a82df0c655d
abnormal-security-list-abnormal-cases
Get a list of Abnormal cases identified by Abnormal Security
Base Command
abnormal-security-list-abnormal-cases
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Value must be of the format filter={FILTER KEY} gte YYYY-MM-DDTHH:MM:SSZ lte YYYY-MM-DDTHH:MM:SSZ. A {FILTER KEY} must be specified, and currently the only key that is supported for /cases is lastModifiedTime. At least 1 of gte/lte must be specified, with a datetime string following the YYYY-MM-DDTHH:MM:SSZ format. |
Optional |
| page_size | Number of cases that are on each page. Each page of data will have at most page_size threats. Has no effect if filter is not specified. | Optional |
| page_number | 1-indexed page number to get a particular page of cases. Has no effect if filter is not specified. | Optional |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.inline_response_200_1.cases.caseId | String | A unique identifier for this case. |
| AbnormalSecurity.inline_response_200_1.cases.description | String | Description of the severity level for this case. |
| AbnormalSecurity.inline_response_200_1.pageNumber | Number | The current page number. Will not be be in the response if no filter query meter is passed in via the request. |
| AbnormalSecurity.inline_response_200_1.nextpageNumber | Number | The next page number. Will not be included in the response if there are no more pages of data or if no filter query meter is passed in via the request |
Command Example
!abnormal-security-list-abnormal-cases filter="lastModifiedTime gte 2020-12-01T01:01:01Z"
Context Example
{
"AbnormalSecurity": {
"inline_response_200_1": {
"cases": [
{
"caseId": 1234,
"description": "Potential Account Takeover"
}
],
"nextPageNumber": 2,
"pageNumber": 1
}
}
}
Human Readable Output
List of Cases
Case IDs
caseId description 1234 Potential Account Takeover
abnormal-security-list-threats
Get a list of threats
Base Command
abnormal-security-list-threats
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Value must be of the format filter={FILTER KEY} gte YYYY-MM-DDTHH:MM:SSZ lte YYYY-MM-DDTHH:MM:SSZ. A {FILTER KEY} must be specified, and currently the only key that is supported for /threats is receivedTime. At least 1 of gte/lte must be specified, with a datetime string following the YYYY-MM-DDTHH:MM:SSZ format. |
Optional |
| page_size | Number of threats per page. Each page will contain up to page_size threats. This has no effect if no filter is specified. | Optional |
| page_number | 1-indexed page number to get a particular page of threats. Has no effect if filter is not specified. | Optional |
| mock-data | Returns test data if set to True. |
Optional |
| source | Filters threats based on the source of detection. | Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.inline_response_200.threats.threatId | String | An id which maps to a threat campaign. A threat campaign might be received by multiple users. |
| AbnormalSecurity.inline_response_200.pageNumber | Number | The current page number. Will not be be in the response if no filter query meter is passed in via the request. |
| AbnormalSecurity.inline_response_200.nextpageNumber | Number | The next page number. Will not be included in the response if there are no more pages of data or if no filter query meter is passed in via the request |
Command Example
!abnormal-security-list-threats filter="receivedTime gte 2020-12-01T01:01:01Z"
Context Example
{
"AbnormalSecurity": {
"inline_response_200": {
"nextPageNumber": 2,
"pageNumber": 1,
"threats": [
{
"threatId": "184712ab-6d8b-47b3-89d3-a314efef79e2"
}
]
}
}
}
Human Readable Output
List of Threats
Threat IDs
threatId 184712ab-6d8b-47b3-89d3-a314efef79e2
abnormal-security-get-threat
Get details of a threat
Base Command
abnormal-security-get-threat
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | A UUID representing a threat campaign. Full list of threat IDs can be obtained by first running the command to list a threat. | Required |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
| page_size | Number of threats per page. Each page will contain up to page_size threats. This has no effect if no filter is specified. | Optional |
| page_number | 1-indexed page number to get a particular page of threats. Has no effect if filter is not specified. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.ThreatDetails.threatId | String | An id which maps to a threat campaign. |
| AbnormalSecurity.ThreatDetails.messages.abxMessageId | Number | A unique identifier for an individual message within a threat (i.e email campaign). |
| AbnormalSecurity.ThreatDetails.messages.abxPortalUrl | String | The URL at which the specific message details are viewable. |
| AbnormalSecurity.ThreatDetails.messages.attachmentCount | Number | The number of attachments in the email. |
| AbnormalSecurity.ThreatDetails.messages.attachmentNames | Array | List of the names of attachments in the email. |
| AbnormalSecurity.ThreatDetails.messages.attackStrategy | String | The attack strategy used in the threat. |
| AbnormalSecurity.ThreatDetails.messages.attackType | String | The type of threat the message represents. |
| AbnormalSecurity.ThreatDetails.messages.attackVector | String | The medium used for the attack. |
| AbnormalSecurity.ThreatDetails.messages.attackedParty | String | The party that was targeted by the attack. |
| AbnormalSecurity.ThreatDetails.messages.autoRemediated | Boolean | Whether the threat was automatically remediated. |
| AbnormalSecurity.ThreatDetails.messages.fromAddress | String | The email address of the sender. |
| AbnormalSecurity.ThreatDetails.messages.fromName | String | The display name of the sender. |
| AbnormalSecurity.ThreatDetails.messages.impersonatedParty | String | The party, if any, that was impersonated in the attack. |
| AbnormalSecurity.ThreatDetails.messages.internetMessageId | String | The Internet Message ID, per RFC 822. |
| AbnormalSecurity.ThreatDetails.messages.isRead | Boolean | Whether the email has been read. |
| AbnormalSecurity.ThreatDetails.messages.postRemediated | Boolean | Whether the threat was remediated after landing in the user’s mailbox. |
| AbnormalSecurity.ThreatDetails.messages.receivedTime | String | The timestamp at which this message arrived. |
| AbnormalSecurity.ThreatDetails.messages.recipientAddress | String | The email address of the user who actually received the message. |
| AbnormalSecurity.ThreatDetails.messages.remediationStatus | String | The status of remediation action. |
| AbnormalSecurity.ThreatDetails.messages.remediationTimestamp | String | The timestamp at which the message was remediated. |
| AbnormalSecurity.ThreatDetails.messages.sentTime | String | The timestamp at which this message was sent. |
| AbnormalSecurity.ThreatDetails.messages.subject | String | The subject of the email. |
| AbnormalSecurity.ThreatDetails.messages.threatId | String | An id which maps to a threat campaign. |
| AbnormalSecurity.ThreatDetails.messages.toAddresses | Array | All the email addresses to which the message was sent. |
| AbnormalSecurity.ThreatDetails.messages.ccEmails | Array | All the email addresses in CC. |
| AbnormalSecurity.ThreatDetails.messages.replyToEmails | Array | All the email addresses in the “Reply To” field. |
| AbnormalSecurity.ThreatDetails.messages.returnPath | String | The path where information is returned to the attacker. |
| AbnormalSecurity.ThreatDetails.messages.senderDomain | String | The domain of the sender. |
| AbnormalSecurity.ThreatDetails.messages.senderIpAddress | String | The IP address of the sender. |
| AbnormalSecurity.ThreatDetails.messages.summaryInsights | Array | Summary insights into the threat’s characteristics. |
| AbnormalSecurity.ThreatDetails.messages.urlCount | Number | The number of URLs contained in the email. |
| AbnormalSecurity.ThreatDetails.messages.urls | Array | List of all URLs contained in the email. |
Command Example
!abnormal-security-get-threat threat_id=xwvutsrq-9pon-mlkj-i876-54321hgfedcba
Context Example
{
"AbnormalSecurity": {
"ThreatDetails": {
"messages": [
{
"abxMessageId": 4551618356913732000,
"abxPortalUrl": "https://portal.abnormalsecurity.com/home/threat-center/remediation-history/4551618356913732076",
"attachmentCount": null,
"attachmentNames": ["attachment.pdf"],
"attackStrategy": "Name Impersonation",
"attackType": "Extortion",
"attackVector": "Text",
"attackedParty": "VIP",
"autoRemediated": true,
"ccEmails": ["cc@example.com"],
"fromAddress": "support@secure-reply.org",
"fromName": "",
"impersonatedParty": "None / Others",
"internetMessageId": "<5edfca1c.1c69fb81.4b055.8fd5@mx.google.com>",
"isRead": true,
"postRemediated": true,
"receivedTime": "2020-06-09T17:42:59Z",
"recipientAddress": "example@example.com",
"remediationTimestamp": "2020-06-09T17:42:59Z",
"replyToEmails": ["reply-to@example.com"],
"returnPath": "support@secure-reply.org",
"senderDomain": "",
"senderIpAddress": "100.101.102.103",
"sentTime": "2020-06-09T17:42:59Z",
"subject": "Phishing Email",
"summaryInsights": ["Bitcoin Topics", "Personal Information Theft", "Unusual Sender"],
"threatId": "184712ab-6d8b-47b3-89d3-a314efef79e2",
"toAddresses": "example@example.com, another@example.com",
"urlCount": 0,
"urls": ["https://www.google.com/"]
}
],
"threatId": "184712ab-6d8b-47b3-89d3-a314efef79e2"
}
}
}
Human Readable Output
Messages in Threat 184712ab-6d8b-47b3-89d3-a314efef79e2
subject fromAddress toAddresses recipientAddress receivedTime attackType attackStrategy returnPath – Phishing Email support@secure-reply.org example@example.com, another@example.com example@example.com 2020-06-09T17:42:59Z Extortion Name Impersonation support@secure-reply.org etc
abnormal-security-get-abnormal-case
Get details of an Abnormal case
Base Command
abnormal-security-get-abnormal-case
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | A string representing the email case. Can be retrieved by first running command to list cases. | Required |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.AbnormalCaseDetails.caseId | String | A unique identifier for this case. |
| AbnormalSecurity.AbnormalCaseDetails.severity | String | Description of the severity level for this case. |
| AbnormalSecurity.AbnormalCaseDetails.affectedEmployee | String | Which employee this case pertains to. |
| AbnormalSecurity.AbnormalCaseDetails.firstObserved | String | First time suspicious behavior was observed. |
| AbnormalSecurity.AbnormalCaseDetails.genai_summary | String | Gen AI summary for this case |
Command Example
!abnormal-security-get-abnormal-case case_id=12805
Context Example
{
"AbnormalSecurity": {
"AbnormalCaseDetails": {
"affectedEmployee": "FirstName LastName",
"analysis": "Mail Sent",
"caseId": 1234,
"case_status": "Action Required",
"firstObserved": "2020-06-09T17:42:59Z",
"remediation_status": "Not remediated",
"severity": "Potential Account Takeover",
"threatIds": ["184712ab-6d8b-47b3-89d3-a314efef79e2"],
"genai_summary": "Observed 2 lateral phishing emails sent internally from the user's account"
}
}
}
Human Readable Output
Details of Case 1234
caseId severity affectedEmployee firstObserved threatIds 1234 Potential Account Takeover FirstName LastName 2020-06-09T17:42:59Z 184712ab-6d8b-47b3-89d3-a314efef79e2
abnormal-security-get-abuse-mailbox-campaign
Get details of an Abuse Mailbox campaign
Base Command
abnormal-security-get-abuse-mailbox-campaign
Input
| Argument Name | Description | Required |
|---|---|---|
| campaign_id | A UUID representing the abuse campaign id. Can be Can be retrieved by first running command to list abuse mailbox campaigns. | Required |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.AbuseCampaign.campaignId | String | An id which maps to an abuse campaign. |
| AbnormalSecurity.AbuseCampaign.firstReported | String | Date abuse campaign was first reported. |
| AbnormalSecurity.AbuseCampaign.lastReported | String | Date abuse campaign was last reported. |
| AbnormalSecurity.AbuseCampaign.messageId | String | A unique identifier for the first message in the abuse campaign. |
| AbnormalSecurity.AbuseCampaign.subject | String | Subject of the first email in the abuse campaign. |
| AbnormalSecurity.AbuseCampaign.fromName | String | The display name of the sender. |
| AbnormalSecurity.AbuseCampaign.fromAddress | String | The email address of the sender. |
| AbnormalSecurity.AbuseCampaign.recipientName | String | The email address of the recipient. |
| AbnormalSecurity.AbuseCampaign.recipientAddress | String | The email address of the recipient. |
| AbnormalSecurity.AbuseCampaign.judgementStatus | String | Judgement status of message. |
| AbnormalSecurity.AbuseCampaign.overallStatus | String | Overall status of message. |
| AbnormalSecurity.AbuseCampaign.attackType | String | The type of threat the message represents. |
Command Example
!abnormal-security-get-abuse-mailbox-campaign campaign_id=xwvutsrq-9pon-mlkj-i876-54321hgfedcba
Context Example
{
"AbnormalSecurity": {
"AbuseCampaign": {
"campaigns": {
"attackType": "Attack Type: Spam",
"campaignId": "fff51768-c446-34e1-97a8-9802c29c3ebd",
"firstReported": "2020-11-11T13:11:40-08:00",
"fromAddress": "example@example.com",
"fromName": "Tom Dinkley",
"judgementStatus": "Malicious",
"lastReported": "2020-11-11T13:11:40-08:00",
"messageId": "12345678910",
"overallStatus": "Move attempted",
"recipientAddress": "example_phisher@example.com",
"recipientName": "Booker",
"subject": "Fwd: This is spam"
}
}
}
}
Human Readable Output
Results
attackType campaignId firstReported fromAddress fromName judgementStatus lastReported messageId overallStatus recipientAddress recipientName subject Attack Type: Spam fff51768-c446-34e1-97a8-9802c29c3ebd 2020-11-11T13:11:40-08:00 example@example.com Tom Dinkley Malicious 2020-11-11T13:11:40-08:00 12345678910 Move attempted example_phisher@example.com Booker Fwd: This is spam
abnormal-security-get-employee-identity-analysis
Get employee identity analysis (Genome) data
Base Command
abnormal-security-get-employee-identity-analysis
Input
| Argument Name | Description | Required |
|---|---|---|
| email_address | Email address of the employee you want to retrieve data for. | Required |
| mock-data | Returns test data if set to True. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.Employee.email | String | Employee email |
| AbnormalSecurity.Employee.histograms.key | String | Genome key name |
| AbnormalSecurity.Employee.histograms.name | String | Genome title |
| AbnormalSecurity.Employee.histograms.description | String | Description of genome object |
| AbnormalSecurity.Employee.histograms.values.value | String | Category value |
| AbnormalSecurity.Employee.histograms.values.percentage | Number | Ratio of this category relative to others |
| AbnormalSecurity.Employee.histograms.values.total_count | Number | Number of occurences for this category |
Command Example
!abnormal-security-get-employee-identity-analysis email_address="test@test.com"
Context Example
{
"AbnormalSecurity": {
"Employee": {
"email": "test@test.com",
"histograms": [
{
"description": "Common IP addresses for user logins",
"key": "ip_address",
"name": "Common IP Addresses",
"values": [
{
"ratio": 0.25,
"raw_count": 12,
"text": "ip-address-0"
},
{
"ratio": 0.25,
"raw_count": 12,
"text": "ip-address-1"
},
{
"ratio": 0.25,
"raw_count": 12,
"text": "ip-address-2"
},
{
"ratio": 0.25,
"raw_count": 12,
"text": "ip-address-3"
}
]
}
]
}
}
}
Human Readable Output
Analysis of test@test.com
description key name values Common IP addresses for user logins ip_address Common IP Addresses {‘text’: ‘ip-address-0’, ‘ratio’: 0.25, ‘raw_count’: 12},
{‘text’: ‘ip-address-1’, ‘ratio’: 0.25, ‘raw_count’: 12},
{‘text’: ‘ip-address-2’, ‘ratio’: 0.25, ‘raw_count’: 12},
{‘text’: ‘ip-address-3’, ‘ratio’: 0.25, ‘raw_count’: 12}
abnormal-security-get-employee-information
Get employee information
Base Command
abnormal-security-get-employee-information
Input
| Argument Name | Description | Required |
|---|---|---|
| email_address | Email address of the employee you want to retrieve data for. | Required |
| mock-data | Returns test data if set to True. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.Employee.name | String | Name of the employee. |
| AbnormalSecurity.Employee.email | String | Email of the employee. |
| AbnormalSecurity.Employee.title | String | Job title of the employee. |
| AbnormalSecurity.Employee.manager | String | Email address of the employee’s manager |
Command Example
!abnormal-security-get-employee-information email_address="test@test.com"
Context Example
{
"AbnormalSecurity": {
"Employee": {
"email": "testemail@email.com",
"manager": "testmanageremail@email.net",
"name": "test_name",
"title": "Test Operator"
}
}
}
Human Readable Output
Results
manager name title testemail@email.com testmanageremail@email.net test_name Test Operator
abnormal-security-get-employee-last-30-days-login-csv
Get employee login information for last 30 days in csv format
Base Command
abnormal-security-get-employee-last-30-days-login-csv
Input
| Argument Name | Description | Required |
|---|---|---|
| email_address | Email address of the employee you want to retrieve data for. | Required |
| mock-data | Returns test data if set to True. |
Optional |
Context Output
There is no context output for this command.
Command Example
!abnormal-security-get-employee-last-30-days-login-csv email_address="test@test.com"
Context Example
{
"File": {
"EntryID": "2338@2ef16ace-2149-42b9-8b0f-fb7620ba7d44",
"Extension": "csv",
"Info": "csv",
"MD5": "11afb4879c5026e25bd868dfcf23e811",
"Name": "employee_login_info_30_days.csv",
"SHA1": "345ea1d24b52c96baf6b0e4d892d13d4efcf666d",
"SHA256": "12620e0f576f4d74603b1f542919a3e5199e61435ffd99bcd68c26e02ed9c693",
"SHA512": "f0e788981ce70d9668100ae3f93d1f28660f0d8a9dfda02284a70f08ac14ca5a356872284f460d8fb7970791e314e0db4a6c84b0032c35046efce62368a00da5",
"SSDeep": "12:uR2xCC56aHoW2IY3zg05Eg05ng05Eg05V:uROjHn2IY3v5i5T5i5V",
"Size": 484,
"Type": "ASCII text, with CRLF line terminators"
}
}
abnormal-security-get-latest-threat-intel-feed
DEPRECATED. Get the latest threat intel feed.
Base Command
abnormal-security-get-latest-threat-intel-feed
Input
| Argument Name | Description | Required |
|---|---|---|
| mock-data | Returns test data if set to True. |
Optional |
Context Output
There is no context output for this command.
Command Example
!abnormal-security-get-latest-threat-intel-feed
Context Example
{
"File": {
"EntryID": "2314@2ef16ace-2149-42b9-8b0f-fb7620ba7d44",
"Extension": "json",
"Info": "application/json",
"MD5": "a00e919efc9e28f77b8f7b7523b1ffe8",
"Name": "threat_intel_feed.json",
"SHA1": "53bf3e6075f407b53c95d5dd2197b9be0dfa5ced",
"SHA256": "f842e7f6795fba081f2046617fce662c050b5a3c64cac9501f23fa7576788429",
"SHA512": "27af66eefb1ed7227b4f8ec1c663ac8ef47660bb34ffd1d5853a7a58e25caec68615c2e66bfbd66577749faa889894e792f53cab70a826818b4d627ad02bbb04",
"SSDeep": "49152:dY0GiMq58ZVhOH+sZwFp+h/s0pH6VRRxIGFe7V3dCLtJ/W7H8nsIdL0E:u",
"Size": 8007799,
"Type": "ASCII text"
}
}
abnormal-security-manage-threat
Manage a Threat identified by Abnormal Security
Base Command
abnormal-security-manage-threat
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | A UUID representing a threat campaign. Full list of threat IDs can be obtained by first running the command to list a threat. | Required |
| action | Action to perform on threat. | Required |
| mock-data | Returns test data if set to True. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.ThreatManageResults.action_id | String | ID of the action taken |
| AbnormalSecurity.ThreatManageResults.status_url | String | URL of the status of the action |
Command Example
!abnormal-security-manage-threat threat_id=xwvutsrq-9pon-mlkj-i876-54321hgfedcba action=remediate
Context Example
{
"AbnormalSecurity": {
"ThreatManageResults": {
"action_id": "a33a212a-89ff-461f-be34-ea52aff44a73",
"status_url": "https://api.abnormalplatform.com/v1/threats/184712ab-6d8b-47b3-89d3-a314efef79e2/actions/a33a212a-89ff-461f-be34-ea52aff44a73"
}
}
}
Human Readable Output
Results
action_id status_url a33a212a-89ff-461f-be34-ea52aff44a73 https://api.abnormalplatform.com/v1/threats/184712ab-6d8b-47b3-89d3-a314efef79e2/actions/a33a212a-89ff-461f-be34-ea52aff44a73
abnormal-security-manage-abnormal-case
Manage an Abnormal Case.
Base Command
abnormal-security-manage-abnormal-case
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | A string representing the email case. Can be retrieved by first running command to list cases. | Required |
| action | Action to perform on case. | Required |
| mock-data | Returns test data if set to True. |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.CaseManageResults.action_id | String | ID of the action taken |
| AbnormalSecurity.CaseManageResults.status_url | String | URL of the status of the action |
Command Example
!abnormal-security-manage-abnormal-case case_id=12805 action=action_required
Context Example
{
"AbnormalSecurity": {
"CaseManageResults": {
"action_id": "61e76395-40d3-4d78-b6a8-8b17634d0f5b",
"status_url": "https://api.abnormalplatform.com/v1/cases/1234/actions/61e76395-40d3-4d78-b6a8-8b17634d0f5b"
}
}
}
Human Readable Output
Results
action_id status_url 61e76395-40d3-4d78-b6a8-8b17634d0f5b https://api.abnormalplatform.com/v1/cases/1234/actions/61e76395-40d3-4d78-b6a8-8b17634d0f5b
abnormal-security-get-case-analysis-and-timeline
Provides the analysis and timeline details of a case
Base Command
abnormal-security-get-case-analysis-and-timeline
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | A string representing the email case. Can be retrieved by first running command to list cases. | Required |
| mock-data | Returns test data if set to True. |
Optional |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.CaseAnalysis.insights.signal | String | Insight signal or highlight of a case |
| AbnormalSecurity.CaseAnalysis.insights.description | String | Description of insight signal or highlight |
| AbnormalSecurity.CaseAnalysis.eventTimeline.event_timestamp | String | Time when event occurred |
| AbnormalSecurity.CaseAnalysis.eventTimeline.category | String | Type of event |
| AbnormalSecurity.CaseAnalysis.eventTimeline.title | String | Title of the event |
| AbnormalSecurity.CaseAnalysis.eventTimeline.ip_address | String | IP Address where user accessed mail from |
| AbnormalSecurity.CaseAnalysis.eventTimeline.field_labels | Unknown | Analysis labels associated with the fields in the timeline event |
Command Example
!abnormal-security-get-case-analysis-and-timeline case_id=12345
Context Example
{
"AbnormalSecurity": {
"CaseAnalysis": {
"eventTimeline": [
{
"category": "Risk Event",
"description": "Impossible Travel Event was observed for test@lamronba.com.",
"event_timestamp": "2021-07-14T22:41:54Z",
"ip_address": "127.0.0.1",
"location": {
"city": "Aldie",
"country": "US",
"state": "Virginia"
},
"prev_location": {
"city": "Atherton",
"country": "US",
"state": "California"
},
"title": "Impossible Travel"
},
{
"category": "Mail Rule",
"condition": "hasNoCondition",
"event_timestamp": "2021-07-14T22:41:54Z",
"flagging_detectors": "DELETE_ALL",
"rule_name": "Swag Voice Note",
"title": "Mail Rule Change"
},
{
"category": "Mail Sent",
"event_timestamp": "2021-07-14T22:41:54Z",
"recipient": "Recipient Name",
"sender": "test@lamronba.com",
"subject": "Spoof email subject",
"title": "Unusual Correspondence"
},
{
"application": "Microsoft Office 365 Portal",
"browser": "Chrome 79.0.3453",
"category": "Sign In",
"description": "Suspicious Failed Sign In Attempt for test@lamronba.com",
"device_trust_type": "None",
"event_timestamp": "2021-07-14T22:41:54Z",
"field_labels": {
"ip_address": ["rare", "proxy"],
"operating_system": ["legacy"]
},
"ip_address": "127.0.0.1",
"isp": "NGCOM",
"location": {
"country": "Ireland"
},
"operating_system": "Windows XP",
"protocol": "Browser",
"title": "Suspicious Failed Sign In Attempt"
}
],
"insights": [
{
"description": "There was a signin into test@lamronba.com from a location frequently used to launch attacks.",
"signal": "Risky Location"
}
]
}
}
}
Human Readable Output
Insights for 12345
signal description Risky Location There was a signin into test@lamronba.com from a location frequently used to launch attacks. Event Timeline for
event_timestamp category title field_labels ip_address description location sender subject title rule_name 2021-07-14T22:41:54Z Risk Event Impossible Travel 127.0.0.1 Impossible Travel Event was observed for test@lamronba.com. city: Aldie
state: Virginia
country: USImpossible Travel 2021-07-14T22:41:54Z Mail Rule Mail Rule Change Mail Rule Change Swag Voice Note 2021-07-14T22:41:54Z Mail Sent Unusual Correspondence test@lamronba.com Spoof email subject Unusual Correspondence 2021-07-14T22:41:54Z Sign In Suspicious Failed Sign In Attempt ip_address: rare,
proxy
operating_system: legacy127.0.0.1 Suspicious Failed Sign In Attempt for test@lamronba.com country: Ireland Suspicious Failed Sign In Attempt
[Deprecated] abnormal-security-submit-inquiry-to-request-a-report-on-misjudgement
Submit an Inquiry to request a report on misjudgement by Abnormal Security
Base Command
abnormal-security-submit-inquiry-to-request-a-report-on-misjudgement
Input
| Argument Name | Description | Required |
|---|---|---|
| mock-data | Returns test data if set to True. |
Optional |
| reporter | Email of the reporter. | Required |
| report_type | Type of misjudgement reported. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.SubmitInquiry.detail | String | Confirmation of inquiry sent |
Command Example
!abnormal-security-submit-inquiry-to-request-a-report-on-misjudgement reporter=abc@def.com report_type=false-positive
Context Example
{
"AbnormalSecurity": {
"SubmitInquiry": {
"detail": "Thank you for your feedback! We have sent your inquiry to our support staff."
}
}
}
Human Readable Output
Results
detail Thank you for your feedback! We have sent your inquiry to our support staff.
abnormal-security-submit-false-negative-report
Submit a False Negative Report
Base Command
abnormal-security-submit-false-negative-report
Input
| Argument Name | Description | Required |
|---|---|---|
| sender_email | Email address of the sender. | Required |
| recipient_email | Email address of the recipient. | Required |
| subject | Email subject. | Required |
Command Example
!abnormal-security-submit-false-negative-report recipient_email=abc@def.com sender_email=def@def.com subject=hello
Human Readable Output
Results
detail Thank you for your feedback! We have sent your inquiry to our support staff.
abnormal-security-submit-false-positive-report
Submit a False Positive Report
Base Command
abnormal-security-submit-false-positive-report
Input
| Argument Name | Description | Required |
|---|---|---|
| portal_link | URL link of threat log in abnormal security portal | Required |
Command Example
!abnormal-security-submit-false-positive-report portal_link=https://portal.abnormalsecurity.com/home/threat-center/remediation-history/123455667
Human Readable Output
Results
detail Thank you for your feedback! We have sent your inquiry to our support staff.
abnormal-security-list-vendors
Get a list of vendors
Base Command
abnormal-security-list-vendors
Input
| Argument Name | Description | Required |
|---|---|---|
| page_size | Number of vendors that are on each page. Each page of data will have at most page_size vendors. Has no effect if filter is not specified. | Optional |
| page_number | 1-indexed page number to get a particular page of vendors. Has no effect if filter is not specified. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.VendorsList | Unknown | List of vendors. |
| AbnormalSecurity.VendorsList.vendorDomain | String | The domain of the vendor. |
Command Example
!abnormal-security-list-vendors
Context Example
{
"AbnormalSecurity": {
"VendorsList": [
{
"vendorDomain": "test-domain-1.com"
},
{
"vendorDomain": "test-domain-2.com"
},
{
"vendorDomain": "test-domain-2.com"
}
]
}
}
Human Readable Output
List of Vendors
Vendor Domains
vendorDomain test-domain-1.com
abnormal-security-get-vendor-details
Get details of a vendor
Base Command
abnormal-security-get-vendor-details
Input
| Argument Name | Description | Required |
|---|---|---|
| vendor_domain | The domain name of the vendor in question. It should be formatted as a fully qualified domain name (e.g., example.com). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.VendorDetails.vendorDomain | String | The domain name of the vendor |
| AbnormalSecurity.VendorDetails.riskLevel | String | The risk level associated with the vendor. |
| AbnormalSecurity.VendorDetails.vendorContacts | Unknown | List of contacts related to the vendor. |
| AbnormalSecurity.VendorDetails.companyContacts | Unknown | List of contacts related to the company. |
| AbnormalSecurity.VendorDetails.vendorCountries | Unknown | List of countries associated with the vendor. |
| AbnormalSecurity.VendorDetails.analysis | Unknown | List of analyses associated with the vendor. |
| AbnormalSecurity.VendorDetails.vendorIpAddresses | Unknown | List of IP addresses associated with the vendor. |
Command Example
!abnormal-security-get-vendor-details vendor_domain="test-domain-1.com"
Context Example
{
"AbnormalSecurity": {
"VendorDetails": {
"vendorDomain": "test-domain-1.com",
"riskLevel": "High",
"vendorContacts": ["john.doe@test-domain-1.com"],
"companyContacts": ["john.doe@test-domain-2.com", "jane.doe@test-domain-2.com"],
"vendorCountries": ["USA"],
"analysis": ["Vendor Compromise Seen in Abnormal Community"],
"vendorIpAddresses": ["192.158. 1.38"]
}
}
}
Human Readable Output
Vendor Domain Details
vendorDomain riskLevel vendorContacts companyContacts vendorCountries analysis vendorIpAddresses test-domain-1.com High john.doe@test-domain-1.com john.doe@test-domain-2.com, jane.doe@test-domain-2.com USA Vendor Compromise Seen in Abnormal Community 192.158.1.38
abnormal-security-get-vendor-activity
Get details of a vendor
Base Command
abnormal-security-get-vendor-activity
Input
| Argument Name | Description | Required |
|---|---|---|
| vendor_domain | The domain name of the vendor in question. It should be formatted as a fully qualified domain name (e.g., example.com). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.VendorActivity.eventTimeline | Unknown | Event timeline for the vendor. |
| AbnormalSecurity.VendorActivity.eventTimeline.eventTimestamp | String | Timestamp of the event in the vendor’s activity timeline. |
| AbnormalSecurity.VendorActivity.eventTimeline.eventType | String | Type of event in the vendor’s activity timeline. |
| AbnormalSecurity.VendorActivity.eventTimeline.suspiciousDomain | String | Suspicious domain involved in the event. |
| AbnormalSecurity.VendorActivity.eventTimeline.domainIp | String | IP address of the suspicious domain. |
| AbnormalSecurity.VendorActivity.eventTimeline.ipGeolocation | String | Geolocation of the IP address. |
| AbnormalSecurity.VendorActivity.eventTimeline.attackGoal | String | The goal of the attack. |
| AbnormalSecurity.VendorActivity.eventTimeline.actionTaken | String | Action taken in response to the event. |
| AbnormalSecurity.VendorActivity.eventTimeline.hasEngagement | Boolean | Indicates whether the event involved any form of engagement. |
| AbnormalSecurity.VendorActivity.eventTimeline.recipient | Unknown | The recipient targeted by the event, if applicable. |
| AbnormalSecurity.VendorActivity.eventTimeline.threatId | String | Unique identifier for the threat. |
Command Example
!abnormal-security-get-vendor-activity vendor_domain="test-domain-1.com"
Context Example
{
"AbnormalSecurity": {
"VendorActivity": {
"eventTimeline": [
{
"eventTimestamp": "2023-07-28T16:20:05Z",
"eventType": "Federated Signal",
"suspiciousDomain": "test@test-domain.com",
"domainIp": "192.158.1.38",
"ipGeolocation": null,
"attackGoal": "Spam",
"actionTaken": "Remediation Triggered",
"hasEngagement": false,
"recipient": "jane@doe.com",
"threatId": "184712ab-6d8b-47b3-89d3-a314efef79e2"
}
]
}
}
}
Human Readable Output
Vendor Activity
eventTimestamp eventType suspiciousDomain domainIp ipGeolocation attackGoal actionTaken hasEngagement recipient threatId 2023-07-28T16:20:05Z Federated Signal Signal test@test-domain.com 192.158.1.38 null Spam Remediation Triggered false jane@doe.com 184712ab-6d8b-47b3-89d3-a314efef79e2
abnormal-security-list-vendor-cases
Get a list of vendor cases
Base Command
!abnormal-security-list-vendor-cases
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Value must be of the format filter={FILTER KEY} gte YYYY-MM-DDTHH:MM:SSZ lte YYYY-MM-DDTHH:MM:SSZ. A {FILTER KEY} must be specified, and currently the only keys that are supported are firstObservedTime and lastModifiedTime. At least 1 of gte/lte must be specified, with a datetime string following the YYYY-MM-DDTHH:MM:SSZ format. |
Optional |
| page_size | Number of cases that are on each page. | Optional |
| page_number | 1-indexed page number to get a particular page of cases. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.VendorCases | Unknown | List of vendor cases. |
| AbnormalSecurity.VendorCases.vendorCaseId | Number | The identifier of the vendor case. |
Command Example
!abnormal-security-list-vendor-cases filter="lastModifiedTime gte 2020-12-01T01:01:01Z"
Context Example
{
"AbnormalSecurity": {
"VendorCases": [
{
"vendorCaseId": 123
},
{
"vendorCaseId": 456
},
{
"vendorCaseId": 789
}
]
}
}
Human Readable Output
List of Cases
Vendor Case IDs
vendorCaseId 123
abnormal-security-get-vendor-case-details
Get details of a vendor case
Base Command
!abnormal-security-get-vendor-case-details
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | A string representing the email case. Can be retrieved by first running command to list cases. | Required |
| subtenant | Subtenant of the user (if applicable). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.VendorCaseDetails.vendorCaseId | String | The identifier of the vendor case. |
| AbnormalSecurity.VendorCaseDetails.vendorDomain | String | The vendor domain associated with the case. |
| AbnormalSecurity.VendorCaseDetails.firstObservedTime | String | The time the vendor case was first observed. |
| AbnormalSecurity.VendorCaseDetails.lastModifiedTime | String | The last time the vendor case was modified. |
| AbnormalSecurity.VendorCaseDetails.insights | Unknown | List of insights related to the vendor case. |
| AbnormalSecurity.VendorCaseDetails.timeline | Unknown | Timeline of events related to the vendor case. |
Command Example
!abnormal-security-get-vendor-case-details case_id=123
Context Example
{
"AbnormalSecurity": {
"AbnormalCaseDetails": {
"vendorCaseId": 123,
"vendorDomain": "some-domain.com",
"firstObservedTime": "2022-04-04T21:12:14Z",
"lastModifiedTime": "2022-04-05T14:40:11Z",
"insights": [
{
"highlight": "Inconsistent Sender Domain Registrars",
"description": "The suspicious sending domain, \"some-domain.com\", was registered in \"City, United States\" to \"unknown\" on 2022-02-07 with registrar \"ABCD\". The legitimate domain for \"some-domain.com\", was registered through \"Test, LLC\" in \"City, United States\" on 1999-12-02."
},
{
"highlight": "Look-a-like Sender Domain",
"description": "The sending domain of this message, \"some-domain.com\", is attempting to impersonate the legitimate domain of \"some-domain.com\"."
},
{
"highlight": "Young Sender Domain",
"description": "The sender domain \"some-domain.com\" was 65 days old when the first engagement in this case was observed, a suspicious signal for a financial email conversation."
}
],
"timeline": [
{
"eventTimestamp": "2022-04-04T21:12:14Z",
"senderAddress": "john-doe@some-domain.com",
"recipientAddress": "jane.doe@some-other-domain.com",
"subject": "Important Notice",
"markedAs": "Malicious",
"threatId": 1234
},
{
"eventTimestamp": "2022-04-04T21:12:14Z",
"senderAddress": "jand-doe@some-domain.com",
"recipientAddress": "john@some-other-domain.com",
"subject": "Important Notice",
"markedAs": "Malicious",
"threatId": 12345
}
]
}
}
}
Human Readable Output
Case Details
vendorCaseId vendorDomain firstObservedTime lastModifiedTime insights timeline 123 some-domain.com 2022-04-04T21:12:14Z 2022-04-05T14:40:11Z {“highlight”: “Inconsistent Sender Domain Registrars”,”description”: “The suspicious sending domain, "some-domain.com", was registered in "City, United States" to "unknown" on 2022-02-07 with registrar "ABCD". The legitimate domain for "some-domain.com", was registered through "Test, LLC" in "City, United States" on 1999-12-02.”}… {“eventTimestamp”: “2022-04-04T21:12:14Z”,”senderAddress”: “john-doe@some-domain.com”,”recipientAddress”: “jane.doe@some-other-domain.com”,”subject”: “Important Notice”,”markedAs”: “Malicious”,”threatId”: 123}..
abnormal-security-search-messages
Search for messages using filters across abnormal or quarantine sources.
Base Command
abnormal-security-search-messages
Input
| Argument Name | Description | Required |
|---|---|---|
| source | Message source (abnormal or quarantine). Possible values are: abnormal, quarantine. | Required |
| tenant_ids | Comma-separated list of tenant IDs. | Optional |
| start_time | Start time for the search in ISO 8601 format (e.g., 2024-01-01T00:00:00Z). | Required |
| end_time | End time for the search in ISO 8601 format (e.g., 2024-01-31T23:59:59Z). | Required |
| subject | Filter by email subject. | Optional |
| sender_email | Filter by sender email address. | Optional |
| sender_name | Filter by sender name. | Optional |
| recipient_email | Filter by recipient email address. | Optional |
| recipient_name | Filter by recipient name. | Optional |
| attachment_name | Filter by attachment name. | Optional |
| attachment_md5_hash | Filter by attachment MD5 hash. | Optional |
| internet_message_id | Filter by internet message ID. | Optional |
| body_link | Filter by body link/URL. | Optional |
| sender_ip | Filter by sender IP address. | Optional |
| judgement | Filter by judgement status. Possible values are: attack, borderline, spam, graymail, safe. | Optional |
| use_sender_regex | Use regex for sender filtering (default false). Possible values are: true, false. | Optional |
| use_recipient_regex | Use regex for recipient filtering (default false). Possible values are: true, false. | Optional |
| show_graymail | Show graymail messages (default false). Possible values are: true, false. | Optional |
| page_number | Page number (default 1). | Optional |
| page_size | Page size (default 100, max 1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.MessageSearch.results | Unknown | List of messages matching the search criteria. |
| AbnormalSecurity.MessageSearch.results.abnormal_message_id | String | Abnormal message identifier. |
| AbnormalSecurity.MessageSearch.results.subject | String | Email subject. |
| AbnormalSecurity.MessageSearch.results.sender | String | Sender email address. |
| AbnormalSecurity.MessageSearch.results.mailbox_name | String | Mailbox name where the message was received. |
| AbnormalSecurity.MessageSearch.results.received_time | String | Time when message was received. |
| AbnormalSecurity.MessageSearch.results.decision_category | String | Decision category (malicious, spam, safe, graymail). |
| AbnormalSecurity.MessageSearch.results.judgement | String | Judgement status (attack, borderline, spam, graymail, safe). |
| AbnormalSecurity.MessageSearch.total | Number | Total number of messages found. |
| AbnormalSecurity.MessageSearch.pageNumber | Number | Current page number. |
| AbnormalSecurity.MessageSearch.nextPageNumber | Number | Next page number. |
abnormal-security-remediate-messages
Remediate messages by performing actions like delete, move, or submit to Detection360.
Base Command
abnormal-security-remediate-messages
Input
| Argument Name | Description | Required |
|---|---|---|
| action | Action to perform on messages. Possible values are: delete, move_to_inbox, submit_to_d360, reclassify. | Required |
| tenant_ids | Comma-separated list of tenant IDs. | Optional |
| source | Message source (abnormal or quarantine). Possible values are: abnormal, quarantine. | Required |
| remediation_reason | Reason for remediation. Possible values are: false_negative, misdirected, unsolicited, other, groups_remediation, quarantine_release. | Required |
| messages | JSON string containing array of message objects to remediate. Required if remediate_all is false. | Optional |
| remediate_all | Whether to remediate all messages matching search filters (default false). Possible values are: true, false. | Optional |
| target_folder | Target folder for move actions (e.g., “Deleted Items”). | Optional |
| submit_d360_case | Whether to submit a Detection360 case (default false). Possible values are: true, false. | Optional |
| start_time | Start time for search filters when remediate_all is true (ISO 8601 format). | Optional |
| end_time | End time for search filters when remediate_all is true (ISO 8601 format). | Optional |
| subject | Subject filter when remediate_all is true. | Optional |
| sender_email | Sender email filter when remediate_all is true. | Optional |
| sender_name | Sender name filter when remediate_all is true. | Optional |
| recipient_email | Recipient email filter when remediate_all is true. | Optional |
| recipient_name | Recipient name filter when remediate_all is true. | Optional |
| attachment_name | Attachment name filter when remediate_all is true. | Optional |
| attachment_md5_hash | Attachment MD5 hash filter when remediate_all is true. | Optional |
| internet_message_id | Internet message ID filter when remediate_all is true. | Optional |
| body_link | Body link/URL filter when remediate_all is true. | Optional |
| sender_ip | Sender IP address filter when remediate_all is true. | Optional |
| judgement | Judgement filter when remediate_all is true. Possible values are: attack, borderline, spam, graymail, safe. | Optional |
| use_sender_regex | Use regex for sender filtering when remediate_all is true (default false). Possible values are: true, false. | Optional |
| use_recipient_regex | Use regex for recipient filtering when remediate_all is true (default false). Possible values are: true, false. | Optional |
| show_graymail | Show graymail messages when remediate_all is true (default false). Possible values are: true, false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.MessageRemediation.activity_log_id | Number | Activity log ID for tracking the remediation request. |
| AbnormalSecurity.MessageRemediation.metadata | Unknown | Metadata about the request. |
abnormal-security-list-activities
Get a list of activity logs for message search and remediation operations.
Base Command
abnormal-security-list-activities
Input
| Argument Name | Description | Required |
|---|---|---|
| tenant_ids | Comma-separated list of tenant IDs (will be sent as query parameters). | Optional |
| action | Filter by action type. Possible values are: search, remediation, csv_export. | Optional |
| page_number | Page number (default 1). | Optional |
| page_size | Page size (default 100, max 1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.Activities.activities | Unknown | List of activity logs. |
| AbnormalSecurity.Activities.activities.activity_id | Number | Activity log ID. |
| AbnormalSecurity.Activities.activities.action | String | Action type (search, remediate, csv_export). |
| AbnormalSecurity.Activities.activities.status | String | Activity status. |
| AbnormalSecurity.Activities.activities.performed_by | String | User who performed the action. |
| AbnormalSecurity.Activities.activities.timestamp | String | Timestamp of the activity. |
| AbnormalSecurity.Activities.activities.result_count | Number | Number of results from the activity. |
| AbnormalSecurity.Activities.total | Number | Total number of activities. |
| AbnormalSecurity.Activities.page | Number | Current page number. |
| AbnormalSecurity.Activities.size | Number | Page size. |
abnormal-security-get-activity-status
Get the status and details of a specific activity log entry.
Base Command
abnormal-security-get-activity-status
Input
| Argument Name | Description | Required |
|---|---|---|
| activity_log_id | Activity log ID to get status for. | Required |
| page | Page number (default 1). | Optional |
| size | Page size (default 100, max 1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.ActivityStatus.activity_id | Number | Activity log ID. |
| AbnormalSecurity.ActivityStatus.action | String | Action type. |
| AbnormalSecurity.ActivityStatus.status | String | Activity status. |
| AbnormalSecurity.ActivityStatus.performed_by | String | User who performed the action. |
| AbnormalSecurity.ActivityStatus.timestamp | String | Timestamp of the activity. |
| AbnormalSecurity.ActivityStatus.result_count | Number | Number of results. |
| AbnormalSecurity.ActivityStatus.remediation_details | Unknown | Detailed remediation information for each message. |
| AbnormalSecurity.ActivityStatus.total | Number | Total number of remediation details. |
abnormal-security-download-message-attachment
Download an attachment from a message found in search results. All required parameters can be obtained from the abnormal-security-search-messages command output.
Base Command
abnormal-security-download-message-attachment
Input
| Argument Name | Description | Required |
|---|---|---|
| message_id | Abnormal message ID (can be negative). Obtained from message search results. | Required |
| attachment_name | Name of the attachment to download. Obtained from message search results attachments field. | Required |
| tenant_id | Tenant ID for the message. Obtained from message search results. | Required |
| raw_message_id | Cloud provider message ID (O365/GSuite). Obtained from message search results as raw_message_id. | Required |
| native_user_id | Cloud provider user ID. Obtained from message search results as native_user_id. | Required |
| recipient_mailbox | Mailbox email address. Obtained from message search results as mailbox_name. | Required |
Context Output
There is no context output for this command.
abnormal-security-download-message-eml
Download a message in RFC822 EML format for forensic analysis. For quarantine messages, both quarantine_identity and recipient_mailbox parameters are required.
Base Command
abnormal-security-download-message-eml
Input
| Argument Name | Description | Required |
|---|---|---|
| cloud_message_id | The cloud_message_id from search results (format abx:CloudMessage:…). Use the cloud_message_id field from message search results. | Required |
| quarantine_identity | Quarantine identifier (required only for quarantine messages). Obtained from quarantine_info.identity field in search results. | Optional |
| recipient_mailbox | Recipient email address (required only for quarantine messages). Obtained from mailbox_name field in search results. | Optional |
Context Output
There is no context output for this command.
abnormal-security-list-unanalyzed-abuse-mailbox-campaigns
Get a list of unanalyzed Abuse Mailbox campaigns
Base Command
abnormal-security-list-unanalyzed-abuse-mailbox-campaigns
Input
| Argument Name | Description | Required |
|---|---|---|
| start | The start time for retrieving the list of unanalyzed abuse mailbox campaigns.. | Optional |
| end | The end time for retrieving the list of unanalyzed abuse mailbox campaigns. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.abx_message_id | Number | An id which maps to an abuse campaign. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.recipient.name | String | The name of the recipient. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.recipient.email | String | The email address of the recipient. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.reported_datetime | String | The datetime the report was made. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.reporter.email | String | The email address of the reporter. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.reporter.name | String | The name of the reporter. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.subject | String | The subject of the message. |
| AbnormalSecurity.UnanalyzedAbuseCampaigns.results.not_analyzed_reason | String | The reason the message was not analyzed. |
Command Example
!abnormal-security-list-unanalyzed-abuse-mailbox-campaigns
Context Example
{
"AbnormalSecurity": {
"AbuseCampaign": {
"results": [
{
"abx_message_id": 123456789,
"recipient": {
"name": "John Doe",
"email": "john.doe@some-domain.com"
},
"reported_datetime": "2023-06-15T00:17:31Z",
"reporter": {
"email": "info@some-domain.com",
"name": "Support"
},
"subject": "URGENT",
"not_analyzed_reason": "INVALID_SUBMISSION"
},
{
"abx_message_id": 987654321,
"recipient": {
"name": "Jane Doe",
"email": "jane.doe@some-domain.com"
},
"reported_datetime": "2023-06-14T06:23:31Z",
"reporter": {
"email": "info@some-domain.com",
"name": "support"
},
"subject": "Hello",
"not_analyzed_reason": "INVALID_SUBMISSION"
}
]
}
}
}
Human Readable Output
Unanalyzed Abuse Mailbox Campaigns
abx_message_id recipient reported_datetime reporter subject not_analyzed_reason 123456789 name: John Doe email: john.doe@some-domain.com 2023-06-15T00:17:31Z email : info@some-domain.com name: Support URGENT INVALID_SUBMISSION
Configuration parameters
url— Server URL (e.g. https://api.abnormalplatform.com/v1) (required)api_key— API Key (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsmax_fetch— Maximum incidents to fetch.fetch_threats— Fetch Threatsfetch_abuse_campaigns— Fetch Abuse Campaignsfetch_account_takeover_cases— Fetch Account Takeover Casesfirst_fetch— First fetch timeincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalpolling_lag— Polling Lag Time (in minutes)max_page_number— Maximum incidents pages to fetch
Commands (31)
-
abnormal-security-check-case-action-statusCheck the status of an action requested on a case.
-
abnormal-security-check-threat-action-statusCheck the status of an action requested on a threat.
-
abnormal-security-download-message-attachmentDownload an attachment from a message found in search results. All required parameters can be obtained from the abnormal-security-search-messages command output.
-
abnormal-security-download-message-emlDownload a message in RFC822 EML format for forensic analysis. For quarantine messages, both quarantine_identity and recipient_mailbox parameters are required.
-
abnormal-security-download-threat-log-csvDownload data from Threat Log in .csv format.
-
abnormal-security-get-abnormal-caseGet details of an Abnormal case.
-
abnormal-security-get-abuse-mailbox-campaignGet details of an Abuse Mailbox campaign.
-
abnormal-security-get-activity-statusGet the status and details of a specific activity log entry.
-
abnormal-security-get-case-analysis-and-timelineProvides the analysis and timeline details of a case.
-
abnormal-security-get-employee-identity-analysisGet employee identity analysis (Genome) data.
-
abnormal-security-get-employee-informationGet employee information.
-
abnormal-security-get-employee-last-30-days-login-csvGet employee login information for last 30 days in csv format.
-
abnormal-security-get-latest-threat-intel-feedDeprecatedGet the latest threat intel feed.
-
abnormal-security-get-threatGet details of a threat.
-
abnormal-security-get-vendor-activityGet the activity for a specific vendor.
-
abnormal-security-get-vendor-case-detailsGet the details of a vendor case.
-
abnormal-security-get-vendor-detailsGet the details of a specific vendor.
-
abnormal-security-list-abnormal-casesGet a list of Abnormal cases identified by Abnormal Security.
-
abnormal-security-list-abuse-mailbox-campaignsGet a list of campaigns submitted to Abuse Mailbox.
-
abnormal-security-list-activitiesGet a list of activity logs for message search and remediation operations.
-
abnormal-security-list-threatsGet a list of threats.
-
abnormal-security-list-unanalyzed-abuse-mailbox-campaignsGet a list of unanalyzed abuse mailbox campaigns.
-
abnormal-security-list-vendor-casesGet a list of vendor cases.
-
abnormal-security-list-vendorsGet a list of vendors.
-
abnormal-security-manage-abnormal-caseManage an Abnormal Case.
-
abnormal-security-manage-threatManage a Threat identified by Abnormal Security.
-
abnormal-security-remediate-messagesRemediate messages by performing actions like delete, move, or submit to Detection360.
-
abnormal-security-search-messagesSearch for messages using filters across abnormal or quarantine sources.
-
abnormal-security-submit-false-negative-reportSubmit a False Negative Report.
-
abnormal-security-submit-false-positive-reportSubmit a False Positive Report.
-
abnormal-security-submit-inquiry-to-request-a-report-on-misjudgementSubmit an Inquiry to request a report on misjudgement by Abnormal Security.
import logging from datetime import datetime, timedelta from typing import Any import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 urllib3.disable_warnings() DEFAULT_INTERVAL = 30 DEFAULT_TIMEOUT = 600 FETCH_LIMIT = 200 MAX_PAGE_SIZE = 100 XSOAR_SEVERITY_BY_AMP_SEVERITY = { "Low": IncidentSeverity.LOW, "Medium": IncidentSeverity.MEDIUM, "High": IncidentSeverity.HIGH, "Critical": IncidentSeverity.CRITICAL, } ISO_8601_FORMAT = "%Y-%m-%dT%H:%M:%SZ" TIME_FORMAT_WITHMS = "%Y-%m-%dT%H:%M:%S.%fZ" # 4xx status codes that indicate systemic issues and should NOT be skipped NON_SKIPPABLE_STATUS_CODES = {401, 403, 429} def _is_skippable_error(e: DemistoException) -> bool: """Check if a DemistoException from an API call is a 4xx error that can be safely skipped. Skippable errors are client errors (4xx) that are specific to a single entity (e.g., 404 Not Found, 410 Gone). Non-skippable errors indicate systemic issues (401 Unauthorized, 403 Forbidden, 429 Rate Limit) and should be raised. Args: e: The DemistoException raised by _http_request. Returns: True if the error can be safely skipped, False otherwise. """ status_code = None if e.res is not None and hasattr(e.res, "status_code"): status_code = e.res.status_code if status_code is None: return False return 400 <= status_code < 500 and status_code not in NON_SKIPPABLE_STATUS_CODES def try_str_to_datetime(time: str) -> datetime: """ Try to convert a string to a datetime object. """ try: return datetime.strptime(time, ISO_8601_FORMAT).astimezone(timezone.utc) except Exception as _: pass return datetime.strptime((time[:26] + "Z") if len(time) > 26 else time, TIME_FORMAT_WITHMS).astimezone(timezone.utc) def get_current_datetime() -> datetime: return datetime.utcnow().astimezone(timezone.utc) class FetchIncidentsError(Exception): """Raised when there's an error in fetching incidents.""" class Client(BaseClient): CASES = "cases" ABUSE_CAMPAIGNS = "abusecampaigns" THREATS = "threats" def __init__(self, server_url, verify, proxy, headers, auth): super().__init__(base_url=server_url, verify=verify, proxy=proxy, headers=headers, auth=auth, timeout=2400) def check_the_status_of_an_action_requested_on_a_case_request(self, case_id, action_id, subtenant): params = assign_params(subtenant) headers = self._headers response = self._http_request("get", f"cases/{case_id}/actions/{action_id}", params=params, headers=headers) return response def check_the_status_of_an_action_requested_on_a_threat_request(self, threat_id, action_id, subtenant): params = assign_params(subtenant) headers = self._headers response = self._http_request("get", f"threats/{threat_id}/actions/{action_id}", params=params, headers=headers) return response def download_data_from_threat_log_in_csv_format_request(self, filter_, source, subtenant): params = assign_params(filter=filter_, source=source, subtenant=subtenant) headers = self._headers response = self._http_request("get", "threats_export/csv", params=params, headers=headers, resp_type="response") return response def get_a_list_of_abnormal_cases_identified_by_abnormal_security_request( self, filter_="", page_size=None, page_number=None, subtenant=None ): params = assign_params(filter=filter_, pageSize=page_size, pageNumber=page_number, subtenant=subtenant) headers = self._headers response = self._http_request("get", "cases", params=params, headers=headers) return response def get_a_list_of_campaigns_submitted_to_abuse_mailbox_request( self, filter_="", page_size=None, page_number=None, subtenant=None, subject=None, sender=None, recipient=None, reporter=None, attackType=None, threatType=None, ): params = assign_params( filter=filter_, pageSize=page_size, pageNumber=page_number, subtenant=subtenant, subject=subject, sender=sender, recipient=recipient, reporter=reporter, attackType=attackType, threatType=threatType, ) headers = self._headers response = self._http_request("get", "abusecampaigns", params=params, headers=headers) return response def get_a_list_of_threats_request( self, filter_="", page_size=None, page_number=None, source=None, subtenant=None, subject=None, sender=None, recipient=None, topic=None, attackType=None, attackVector=None, ): params = assign_params( filter=filter_, pageSize=page_size, pageNumber=page_number, source=source, subtenant=subtenant, subject=subject, sender=sender, recipient=recipient, topic=topic, attackType=attackType, attackVector=attackVector, ) headers = self._headers response = self._http_request("get", "threats", params=params, headers=headers) return response def get_page_number_and_max_iterations(self, max_incidents_to_fetch): page_size = min(max_incidents_to_fetch, 100) max_iterations = (max_incidents_to_fetch // page_size) + 1 return page_size, max_iterations def get_paginated_cases_list(self, filter_="", max_incidents_to_fetch=FETCH_LIMIT): cases_response: dict[str, list[dict]] = {"cases": []} if max_incidents_to_fetch < 1: return cases_response page_number, current_iteration = 1, 1 page_size, max_iterations = self.get_page_number_and_max_iterations(max_incidents_to_fetch) while page_number is not None: response = self.get_a_list_of_abnormal_cases_identified_by_abnormal_security_request( filter_=filter_, page_size=page_size, page_number=page_number ) cases_response["cases"].extend(response.get("cases", [])) page_number = response.get("nextPageNumber", None) current_iteration += 1 if current_iteration > max_iterations: break return cases_response def get_paginated_threats_list(self, filter_="", max_incidents_to_fetch=FETCH_LIMIT): threats_response: dict[str, list[dict]] = {"threats": []} if max_incidents_to_fetch < 1: return threats_response page_number, current_iteration = 1, 1 page_size, max_iterations = self.get_page_number_and_max_iterations(max_incidents_to_fetch) while page_number is not None: response = self.get_a_list_of_threats_request(filter_=filter_, page_size=page_size, page_number=page_number) threats_response["threats"].extend(response.get("threats", [])) page_number = response.get("nextPageNumber", None) current_iteration += 1 if current_iteration > max_iterations: break return threats_response def get_paginated_abusecampaigns_list(self, filter_="", max_incidents_to_fetch=FETCH_LIMIT): campaigns_response: dict[str, list[dict]] = {"campaigns": []} if max_incidents_to_fetch < 1: return campaigns_response page_number, current_iteration = 1, 1 page_size, max_iterations = self.get_page_number_and_max_iterations(max_incidents_to_fetch) while page_number is not None: response = self.get_a_list_of_campaigns_submitted_to_abuse_mailbox_request( filter_=filter_, page_size=page_size, page_number=page_number ) campaigns_response["campaigns"].extend(response.get("campaigns", [])) page_number = response.get("nextPageNumber", None) current_iteration += 1 if current_iteration > max_iterations: break return campaigns_response def get_details_of_a_threat_request(self, threat_id, subtenant=None, page_size=None, page_number=None): """ Get details of a specific threat with pagination support. Args: threat_id (str): The ID of the threat to get details for subtenant (str, optional): The subtenant ID page_size (int, optional): The number of items per page page_number (int, optional): The page number (zero-based) Returns: dict: The threat details with pagination """ headers = self._headers params = assign_params(subtenant=subtenant, pageSize=page_size, pageNumber=page_number) response = self._http_request("get", f"threats/{threat_id}", params=params, headers=headers) return response def get_details_of_an_abnormal_case_request(self, case_id, subtenant=None): headers = self._headers params = assign_params(subtenant=subtenant) response = self._http_request("get", f"cases/{case_id}", params=params, headers=headers) return response def get_details_of_an_abuse_mailbox_campaign_request(self, campaign_id, subtenant=None): headers = self._headers params = assign_params(subtenant=subtenant) response = self._http_request("get", f"abusecampaigns/{campaign_id}", params=params, headers=headers) return response def get_employee_identity_analysis_genome_data_request(self, email_address): headers = self._headers response = self._http_request("get", f"employee/{email_address}/identity", headers=headers) return response def get_employee_information_request(self, email_address): headers = self._headers response = self._http_request("get", f"employee/{email_address}", headers=headers) return response def get_employee_login_information_for_last_30_days_in_csv_format_request(self, email_address): headers = self._headers response = self._http_request("get", f"employee/{email_address}/logins", headers=headers, resp_type="response") return response def get_the_latest_threat_intel_feed_request(self): headers = self._headers response = self._http_request("get", "threat-intel", headers=headers, timeout=120, resp_type="response") return response def manage_a_threat_identified_by_abnormal_security_request(self, threat_id, action): headers = self._headers json_data = {"action": action} response = self._http_request("post", f"threats/{threat_id}", json_data=json_data, headers=headers) return response def manage_an_abnormal_case_request(self, case_id, action): headers = self._headers json_data = {"action": action} response = self._http_request("post", f"cases/{case_id}", json_data=json_data, headers=headers) return response def provides_the_analysis_and_timeline_details_of_a_case_request(self, case_id, subtenant): params = assign_params(subtenant=subtenant) headers = self._headers response = self._http_request("get", f"cases/{case_id}/analysis", params=params, headers=headers) return response def submit_an_inquiry_to_request_a_report_on_misjudgement_by_abnormal_security_request(self, reporter, report_type): headers = self._headers json_data = { "reporter": reporter, "report_type": report_type, } response = self._http_request("post", "inquiry", json_data=json_data, headers=headers) return response def submit_false_negative_report_request(self, recipient_email, sender_email, subject): headers = self._headers json_data = { "report_type": "false-negative", "recipient_email": recipient_email, "sender_email": sender_email, "subject": subject, } response = self._http_request("post", "detection360/reports", json_data=json_data, headers=headers) return response def submit_false_positive_report_request(self, portal_link): headers = self._headers json_data = { "report_type": "false-positive", "portal_link": portal_link, } response = self._http_request("post", "detection360/reports", json_data=json_data, headers=headers) return response def get_a_list_of_vendors_request(self, page_size, page_number): params = assign_params(pageSize=page_size, pageNumber=page_number) headers = self._headers response = self._http_request("get", "vendors", params=params, headers=headers) response = self._remove_keys_from_response(response, ["pageNumber", "nextPageNumber"]) return response["vendors"] def get_the_details_of_a_specific_vendor_request(self, vendorDomain): headers = self._headers response = self._http_request("get", f"vendors/{vendorDomain}/details", headers=headers) return response def get_the_activity_of_a_specific_vendor_request(self, vendorDomain): headers = self._headers response = self._http_request("get", f"vendors/{vendorDomain}/activity", headers=headers) return response def get_a_list_of_vendor_cases_request(self, filter_, page_size, page_number): params = assign_params(filter=filter_, pageSize=page_size, pageNumber=page_number) headers = self._headers response = self._http_request("get", "vendor-cases", params=params, headers=headers) response = self._remove_keys_from_response(response, ["pageNumber", "nextPageNumber"]) return response["vendorCases"] def get_the_details_of_a_vendor_case_request(self, caseId): headers = self._headers response = self._http_request("get", f"vendor-cases/{caseId}", headers=headers) return response def get_a_list_of_unanalyzed_abuse_mailbox_campaigns_request(self, start, end): params = assign_params(start=start, end=end) headers = self._headers response = self._http_request("get", "abuse_mailbox/not_analyzed", params=params, headers=headers) return response def search_messages_request(self, source, tenant_ids, filters, page_number=None, page_size=None): """ Search for messages using the SOAR Message Search API. Args: source (str): Message source (abnormal|quarantine) tenant_ids (list): List of tenant IDs filters (dict): Search filters page_number (int, optional): Page number (default 1) page_size (int, optional): Page size (default 100, max 1000) Returns: dict: Search results with messages, pagination, and metadata """ params = assign_params(pageNumber=page_number, pageSize=page_size) headers = self._headers json_data = { "source": source, "tenant_ids": tenant_ids, "filters": filters, } response = self._http_request("post", "search", params=params, json_data=json_data, headers=headers) return response def remediate_messages_request( self, action, tenant_ids, source, remediation_reason, messages=None, remediate_all=False, search_filters=None, **kwargs ): """ Remediate messages using the SOAR Message Remediation API. Args: action (str): Action to perform (delete|move_to_inbox|submit_to_d360|reclassify) tenant_ids (list): List of tenant IDs source (str): Message source (abnormal|quarantine) remediation_reason (str): Reason for remediation messages (list, optional): List of message objects to remediate remediate_all (bool, optional): Whether to remediate all matching messages search_filters (dict, optional): Search filters when remediate_all=True **kwargs: Additional optional parameters (target_folder, submit_d360_case) Returns: dict: Remediation response with activity_log_id and metadata """ headers = self._headers json_data = { "action": action, "tenant_ids": tenant_ids, "source": source, "remediation_reason": remediation_reason, "remediate_all": remediate_all, } if messages: json_data["messages"] = messages if search_filters: json_data["search_filters"] = search_filters # Add optional parameters if "target_folder" in kwargs: json_data["target_folder"] = kwargs["target_folder"] if "submit_d360_case" in kwargs: json_data["submit_d360_case"] = kwargs["submit_d360_case"] response = self._http_request("post", "search/remediate", json_data=json_data, headers=headers) return response def get_activities_list_request(self, tenant_ids, action=None, page_number=None, page_size=None): """ Get list of activity logs using the SOAR Activity Logs API. Args: tenant_ids (list): List of tenant IDs (passed as query parameters) action (str, optional): Filter by action (search|remediation|csv_export) page_number (int, optional): Page number (default 1) page_size (int, optional): Page size (default 100, max 1000) Returns: dict: Activity logs with pagination and metadata """ params = assign_params(action=action, pageNumber=page_number, pageSize=page_size, tenant_ids=tenant_ids) headers = self._headers response = self._http_request("get", "search/activities", params=params, headers=headers) return response def get_activity_status_request(self, activity_log_id, page=None, size=None): """ Get status of a specific activity using the SOAR Activity Status API. Args: activity_log_id (str): Activity log ID page (int, optional): Page number (default 1) size (int, optional): Page size (default 100, max 1000) Returns: dict: Activity status with remediation details and metadata """ params = assign_params(page=page, size=size) headers = self._headers response = self._http_request("get", f"search/activities/{activity_log_id}/status", params=params, headers=headers) return response def download_message_attachment_request( self, message_id, attachment_name, tenant_id, raw_message_id, native_user_id, recipient_mailbox ): """ Download a message attachment using the SOAR Attachment Download API. Args: message_id (str): Abnormal message ID (can be negative) attachment_name (str): Name of the attachment to download tenant_id (int): Tenant ID for the message raw_message_id (str): Cloud provider message ID (O365/GSuite) native_user_id (str): Cloud provider user ID recipient_mailbox (str): Mailbox email address Returns: Response: HTTP response object containing the attachment file """ params = assign_params( message_id=message_id, attachment_name=attachment_name, tenant_id=tenant_id, raw_message_id=raw_message_id, native_user_id=native_user_id, recipient_mailbox=recipient_mailbox, ) headers = self._headers response = self._http_request( "get", "search/messages/attachments/download", params=params, headers=headers, resp_type="response" ) return response def download_message_eml_request(self, cloud_message_id, quarantine_identity=None, recipient_mailbox=None): """ Download a message in EML format using the SOAR EML Download API. Args: cloud_message_id (str): The cloud_message_id from search results (format: abx:CloudMessage:...) quarantine_identity (str, optional): Quarantine identifier (required for quarantine messages) recipient_mailbox (str, optional): Recipient email address (required for quarantine messages) Returns: Response: HTTP response object containing the EML file (RFC822 format) """ params = assign_params(quarantineIdentity=quarantine_identity, recipientMailbox=recipient_mailbox) headers = self._headers response = self._http_request( "get", f"search/messages/{cloud_message_id}/eml", params=params, headers=headers, resp_type="response" ) return response def _remove_keys_from_response(self, response, keys_to_remove): """Removes specified keys from the response.""" for key in keys_to_remove: response.pop(key, None) return response def check_the_status_of_an_action_requested_on_a_case_command(client, args): case_id = str(args.get("case_id", "")) action_id = str(args.get("action_id", "")) subtenant = args.get("subtenant", None) response = client.check_the_status_of_an_action_requested_on_a_case_request(case_id, action_id, subtenant) command_results = CommandResults( outputs_prefix="AbnormalSecurity.ActionStatus", outputs_key_field="", outputs=response, raw_response=response ) return command_results def check_the_status_of_an_action_requested_on_a_threat_command(client, args): threat_id = str(args.get("threat_id", "")) action_id = str(args.get("action_id", "")) subtenant = args.get("subtenant", None) response = client.check_the_status_of_an_action_requested_on_a_threat_request(threat_id, action_id, subtenant) command_results = CommandResults( outputs_prefix="AbnormalSecurity.ActionStatus", outputs_key_field="", outputs=response, raw_response=response ) return command_results def download_data_from_threat_log_in_csv_format_command(client, args): filter_ = str(args.get("filter", "")) source = str(args.get("source", "")) subtenant = args.get("subtenant", None) response = client.download_data_from_threat_log_in_csv_format_request(filter_, source, subtenant) filename = "threat_log.csv" file_content = response.text results = fileResult(filename, file_content) return results def get_a_list_of_abnormal_cases_identified_by_abnormal_security_command(client, args): filter_ = str(args.get("filter", "")) page_size = args.get("page_size", None) page_number = args.get("page_number", None) subtenant = args.get("subtenant", None) response = client.get_a_list_of_abnormal_cases_identified_by_abnormal_security_request( filter_, page_size, page_number, subtenant ) markdown = tableToMarkdown("Case IDs", response.get("cases", []), headers=["caseId", "description"], removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.inline_response_200_1", outputs_key_field="", outputs=response, raw_response=response, ) return command_results def get_a_list_of_campaigns_submitted_to_abuse_mailbox_command(client, args): filter_ = str(args.get("filter", "")) page_size = args.get("page_size", None) page_number = args.get("page_number", None) subtenant = args.get("subtenant", None) subject = args.get("subject", None) sender = args.get("sender", None) recipient = args.get("recipient", None) reporter = args.get("reporter", None) attackType = args.get("attackType", None) threatType = args.get("threatType", None) response = client.get_a_list_of_campaigns_submitted_to_abuse_mailbox_request( filter_, page_size, page_number, subtenant, subject, sender, recipient, reporter, attackType, threatType ) markdown = tableToMarkdown("Campaign IDs", response.get("campaigns", []), headers=["campaignId"], removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.AbuseCampaign", outputs_key_field="campaignId", outputs=response, raw_response=response, ) return command_results def get_a_list_of_threats_command(client, args): filter_ = str(args.get("filter", "")) page_size = args.get("page_size", None) page_number = args.get("page_number", None) source = str(args.get("source", "")) subtenant = args.get("subtenant", None) subject = args.get("subject", None) sender = args.get("sender", None) recipient = args.get("recipient", None) topic = args.get("topic", None) attackType = args.get("attackType", None) attackVector = args.get("attackVector", None) response = client.get_a_list_of_threats_request( filter_, page_size, page_number, source, subtenant, subject, sender, recipient, topic, attackType, attackVector ) markdown = tableToMarkdown("Threat IDs", response.get("threats"), headers=["threatId"], removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.inline_response_200", outputs_key_field="", outputs=response, raw_response=response, ) return command_results def get_details_of_a_threat_command(client, args): threat_id = str(args.get("threat_id", "")) subtenant = args.get("subtenant", None) page_size = args.get("page_size", None) page_number = args.get("page_number", None) response = client.get_details_of_a_threat_request(threat_id, subtenant, page_size, page_number) headers = [ "subject", "fromAddress", "fromName", "toAddresses", "recipientAddress", "receivedTime", "attackType", "attackStrategy", "abxMessageId", "abxPortalUrl", "attachmentCount", "attachmentNames", "attackVector", "attackedParty", "autoRemediated", "impersonatedParty", "internetMessageId", "isRead", "postRemediated", "remediationStatus", "remediationTimestamp", "sentTime", "threatId", "ccEmails", "replyToEmails", "returnPath", "senderDomain", "senderIpAddress", "summaryInsights", "urlCounturls", ] markdown = tableToMarkdown( f"Messages in Threat {response.get('threatId', '')}", response.get("messages", []), headers=headers, removeNull=True ) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.ThreatDetails", outputs_key_field="threatId", outputs=response, raw_response=response, ) return command_results def get_details_of_an_abnormal_case_command(client, args): case_id = str(args.get("case_id", "")) subtenant = args.get("subtenant", None) response = client.get_details_of_an_abnormal_case_request(case_id, subtenant) headers = ["caseId", "severity", "affectedEmployee", "firstObserved", "threatIds", "genai_summary"] markdown = tableToMarkdown(f"Details of Case {response.get('caseId', '')}", response, headers=headers, removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.AbnormalCaseDetails", outputs_key_field="", outputs=response, raw_response=response, ) return command_results def get_details_of_an_abuse_mailbox_campaign_command(client, args): campaign_id = str(args.get("campaign_id", "")) subtenant = args.get("subtenant", None) response = client.get_details_of_an_abuse_mailbox_campaign_request(campaign_id, subtenant) command_results = CommandResults( outputs_prefix="AbnormalSecurity.AbuseCampaign", outputs_key_field="campaignId", outputs=response, raw_response=response ) return command_results def get_employee_identity_analysis_genome_data_command(client, args): email_address = str(args.get("email_address", "")) response = client.get_employee_identity_analysis_genome_data_request(email_address) headers = ["description", "key", "name", "values"] markdown = tableToMarkdown(f"Analysis of {email_address}", response.get("data", []), headers=headers, removeNull=True) response["email"] = email_address command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.Employee", outputs_key_field="email", outputs=response, raw_response=response, ) return command_results def get_employee_information_command(client, args): email_address = str(args.get("email_address", "")) response = client.get_employee_information_request(email_address) command_results = CommandResults( outputs_prefix="AbnormalSecurity.Employee", outputs_key_field="email", outputs=response, raw_response=response ) return command_results def get_employee_login_information_for_last_30_days_in_csv_format_command(client, args): email_address = str(args.get("email_address", "")) response = client.get_employee_login_information_for_last_30_days_in_csv_format_request(email_address) filename = "employee_login_info_30_days.csv" file_content = response.text results = fileResult(filename, file_content) return results def get_the_latest_threat_intel_feed_command(client, args=None): response = client.get_the_latest_threat_intel_feed_request() filename = "threat_intel_feed.json" file_content = response.text results = fileResult(filename, file_content) return results def manage_a_threat_identified_by_abnormal_security_command(client, args): threat_id = str(args.get("threat_id", "")) action = str(args.get("action", "")) response = client.manage_a_threat_identified_by_abnormal_security_request(threat_id, action) command_results = CommandResults( outputs_prefix="AbnormalSecurity.ThreatManageResults", outputs_key_field="", outputs=response, raw_response=response ) return command_results def manage_an_abnormal_case_command(client, args): case_id = str(args.get("case_id", "")) action = str(args.get("action", "")) response = client.manage_an_abnormal_case_request(case_id, action) command_results = CommandResults( outputs_prefix="AbnormalSecurity.CaseManageResults", outputs_key_field="", outputs=response, raw_response=response ) return command_results def provides_the_analysis_and_timeline_details_of_a_case_command(client, args): case_id = str(args.get("case_id", "")) subtenant = args.get("subtenant", None) response = client.provides_the_analysis_and_timeline_details_of_a_case_request(case_id, subtenant) insight_headers = ["signal", "description"] markdown = tableToMarkdown(f"Insights for {case_id}", response.get("insights", []), headers=insight_headers, removeNull=True) timeline_headers = [ "event_timestamp", "category", "title", "field_labels", "ip_address", "description", "location", "sender", "subject", "title", "flagging detectors", "rule_name", ] markdown += tableToMarkdown( f"Event Timeline for {response.get('caseId', '')}", response.get("eventTimeline", []), headers=timeline_headers, removeNull=True, ) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.CaseAnalysis", outputs_key_field="caseId", outputs=response, raw_response=response, ) return command_results def submit_an_inquiry_to_request_a_report_on_misjudgement_by_abnormal_security_command(client, args): reporter = str(args.get("reporter", "")) report_type = str(args.get("report_type", "")) response = client.submit_an_inquiry_to_request_a_report_on_misjudgement_by_abnormal_security_request(reporter, report_type) command_results = CommandResults( outputs_prefix="AbnormalSecurity.SubmitInquiry", outputs_key_field="", outputs=response, raw_response=response ) return command_results def submit_false_negative_report_command(client, args): recipient_email = str(args.get("recipient_email", "")) sender_email = str(args.get("sender_email", "")) subject = str(args.get("subject", "")) response = client.submit_false_negative_report_request(recipient_email, sender_email, subject) command_results = CommandResults(readable_output=response, raw_response=response) return command_results def submit_false_positive_report_command(client, args): portal_link = str(args.get("portal_link", "")) response = client.submit_false_positive_report_request(portal_link) command_results = CommandResults(readable_output=response, raw_response=response) return command_results def get_a_list_of_vendors_command(client, args): page_size = str(args.get("page_size", "")) page_number = str(args.get("page_number", "")) response = client.get_a_list_of_vendors_request(page_size, page_number) markdown = tableToMarkdown("Vendor Domains", response, headers=["vendorDomain"], removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.VendorsList", outputs_key_field="vendorDomain", outputs=response, raw_response=response, ) return command_results def get_the_details_of_a_specific_vendor_command(client, args): vendor_domain: str = args["vendor_domain"] response = client.get_the_details_of_a_specific_vendor_request(vendor_domain) markdown = tableToMarkdown("Vendor Domain", response, removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.VendorDetails", outputs_key_field="vendorDomain", outputs=response, raw_response=response, ) return command_results def get_the_activity_of_a_specific_vendor_command(client, args): vendor_domain: str = args["vendor_domain"] response = client.get_the_activity_of_a_specific_vendor_request(vendor_domain) markdown = tableToMarkdown("Vendor Activity", response.get("eventTimeline"), removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.VendorActivity", outputs_key_field="", outputs=response, raw_response=response, ) return command_results def get_a_list_of_vendor_cases_command(client, args): filter_ = str(args.get("filter", "")) page_size = str(args.get("page_size", "")) page_number = str(args.get("page_number", "")) response = client.get_a_list_of_vendor_cases_request(filter_, page_size, page_number) markdown = tableToMarkdown("Vendor Case IDs", response, removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.VendorCases", outputs_key_field="vendorCaseId", outputs=response, raw_response=response, ) return command_results def get_the_details_of_a_vendor_case_command(client, args): case_id: str = args["case_id"] response = client.get_the_details_of_a_vendor_case_request(case_id) markdown = tableToMarkdown("Case Details", response, removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.VendorCaseDetails", outputs_key_field="vendorCaseId", outputs=response, raw_response=response, ) return command_results def get_a_list_of_unanalyzed_abuse_mailbox_campaigns_command(client, args): start = str(args.get("start", "")) end = str(args.get("end", "")) response = client.get_a_list_of_unanalyzed_abuse_mailbox_campaigns_request(start, end) markdown = tableToMarkdown("Unanalyzed Abuse Mailbox Campaigns", response.get("results", []), removeNull=True) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.UnanalyzedAbuseCampaigns", outputs_key_field="abx_message_id", outputs=response, raw_response=response, ) return command_results def search_messages_command(client, args): # pragma: no cover """ Search for messages using the SOAR Message Search API. """ source = str(args.get("source", "")) tenant_ids = argToList(args.get("tenant_ids", [])) page_number = arg_to_number(args.get("page_number")) page_size = arg_to_number(args.get("page_size")) # Build filters dictionary filters = {} if args.get("start_time"): filters["start_time"] = str(args.get("start_time")) if args.get("end_time"): filters["end_time"] = str(args.get("end_time")) if args.get("subject"): filters["subject"] = str(args.get("subject")) if args.get("sender_email"): filters["sender_email"] = str(args.get("sender_email")) if args.get("sender_name"): filters["sender_name"] = str(args.get("sender_name")) if args.get("recipient_email"): filters["recipient_email"] = str(args.get("recipient_email")) if args.get("recipient_name"): filters["recipient_name"] = str(args.get("recipient_name")) if args.get("attachment_name"): filters["attachment_name"] = str(args.get("attachment_name")) if args.get("attachment_md5_hash"): filters["attachment_md5_hash"] = str(args.get("attachment_md5_hash")) if args.get("internet_message_id"): filters["internet_message_id"] = str(args.get("internet_message_id")) if args.get("body_link"): filters["body_link"] = str(args.get("body_link")) if args.get("sender_ip"): filters["sender_ip"] = str(args.get("sender_ip")) if args.get("judgement"): filters["judgement"] = str(args.get("judgement")) if args.get("use_sender_regex") is not None: filters["use_sender_regex"] = argToBoolean(args.get("use_sender_regex")) if args.get("use_recipient_regex") is not None: filters["use_recipient_regex"] = argToBoolean(args.get("use_recipient_regex")) if args.get("show_graymail") is not None: filters["show_graymail"] = argToBoolean(args.get("show_graymail")) response = client.search_messages_request(source, tenant_ids, filters, page_number, page_size) headers = [ "abnormal_message_id", "subject", "sender", "mailbox_name", "received_time", "decision_category", "judgement", ] markdown = tableToMarkdown( f"Message Search Results (Total: {response.get('total', 0)})", response.get("results", []), headers=headers, removeNull=True, ) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.MessageSearch", outputs_key_field="abnormal_message_id", outputs=response, raw_response=response, ) return command_results def remediate_messages_command(client, args): # pragma: no cover """ Remediate messages using the SOAR Message Remediation API. """ action = str(args.get("action", "")) tenant_ids = argToList(args.get("tenant_ids", [])) source = str(args.get("source", "")) remediation_reason = str(args.get("remediation_reason", "")) remediate_all = argToBoolean(args.get("remediate_all", False)) # Optional parameters kwargs = {} if args.get("target_folder"): kwargs["target_folder"] = str(args.get("target_folder")) if args.get("submit_d360_case") is not None: kwargs["submit_d360_case"] = argToBoolean(args.get("submit_d360_case")) # Handle messages or search_filters messages = None search_filters = None if remediate_all: # Build search filters for remediate_all search_filters = {} if args.get("start_time"): search_filters["start_time"] = str(args.get("start_time")) if args.get("end_time"): search_filters["end_time"] = str(args.get("end_time")) if args.get("subject"): search_filters["subject"] = str(args.get("subject")) if args.get("sender_email"): search_filters["sender_email"] = str(args.get("sender_email")) if args.get("sender_name"): search_filters["sender_name"] = str(args.get("sender_name")) if args.get("recipient_email"): search_filters["recipient_email"] = str(args.get("recipient_email")) if args.get("recipient_name"): search_filters["recipient_name"] = str(args.get("recipient_name")) if args.get("attachment_name"): search_filters["attachment_name"] = str(args.get("attachment_name")) if args.get("attachment_md5_hash"): search_filters["attachment_md5_hash"] = str(args.get("attachment_md5_hash")) if args.get("internet_message_id"): search_filters["internet_message_id"] = str(args.get("internet_message_id")) if args.get("body_link"): search_filters["body_link"] = str(args.get("body_link")) if args.get("sender_ip"): search_filters["sender_ip"] = str(args.get("sender_ip")) if args.get("judgement"): search_filters["judgement"] = str(args.get("judgement")) if args.get("use_sender_regex") is not None: search_filters["use_sender_regex"] = argToBoolean(args.get("use_sender_regex")) if args.get("use_recipient_regex") is not None: search_filters["use_recipient_regex"] = argToBoolean(args.get("use_recipient_regex")) if args.get("show_graymail") is not None: search_filters["show_graymail"] = argToBoolean(args.get("show_graymail")) else: # Parse messages JSON messages_json = args.get("messages") if messages_json: try: messages = json.loads(messages_json) if isinstance(messages_json, str) else messages_json except json.JSONDecodeError as e: raise ValueError(f"Invalid JSON format for messages: {e}") response = client.remediate_messages_request( action, tenant_ids, source, remediation_reason, messages, remediate_all, search_filters, **kwargs ) markdown = f"## Message Remediation Initiated\n\n**Activity Log ID:** {response.get('activity_log_id', 'N/A')}" command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.MessageRemediation", outputs_key_field="activity_log_id", outputs=response, raw_response=response, ) return command_results def get_activities_list_command(client, args): """ Get list of activity logs using the SOAR Activity Logs API. """ tenant_ids = argToList(args.get("tenant_ids", [])) action = args.get("action") page_number = arg_to_number(args.get("page_number")) page_size = arg_to_number(args.get("page_size")) response = client.get_activities_list_request(tenant_ids, action, page_number, page_size) headers = ["activity_id", "action", "status", "performed_by", "timestamp", "result_count"] markdown = tableToMarkdown( f"Activity Logs (Total: {response.get('total', 0)})", response.get("activities", []), headers=headers, removeNull=True ) command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.Activities", outputs_key_field="activity_id", outputs=response, raw_response=response, ) return command_results def get_activity_status_command(client, args): """ Get status of a specific activity using the SOAR Activity Status API. """ activity_log_id = str(args.get("activity_log_id", "")) page = arg_to_number(args.get("page")) size = arg_to_number(args.get("size")) response = client.get_activity_status_request(activity_log_id, page, size) # Create markdown for activity summary summary_headers = ["activity_id", "action", "status", "performed_by", "timestamp", "result_count"] summary_data = { "activity_id": response.get("activity_id"), "action": response.get("action"), "status": response.get("status") or "In Progress", "performed_by": response.get("performed_by") or "N/A", "timestamp": response.get("timestamp") or "N/A", "result_count": response.get("result_count") if response.get("result_count") is not None else "N/A", } markdown = tableToMarkdown("Activity Status", [summary_data], headers=summary_headers) # Add metadata information if available if response.get("metadata"): metadata = response.get("metadata") markdown += f"\n**Trace ID:** {metadata.get('trace_id', 'N/A')}" markdown += f"\n**Response Time:** {metadata.get('response_time', 'N/A')}" # Add remediation details table if available if response.get("remediation_details"): detail_headers = [ "tenant_id", "subject", "sender", "mailbox_name", "status", "date_remediated", ] markdown += "\n\n" + tableToMarkdown( f"Remediation Details (Total: {response.get('total', 0)})", response.get("remediation_details", []), headers=detail_headers, removeNull=True, ) elif response.get("status") is None or response.get("result_count") is None: # Activity is likely still in progress markdown += "\n\n**Note:** Activity is in progress. Details will be available once the activity completes." command_results = CommandResults( readable_output=markdown, outputs_prefix="AbnormalSecurity.ActivityStatus", outputs_key_field="activity_id", outputs=response, raw_response=response, ) return command_results def download_message_attachment_command(client, args): """ Download a message attachment using the SOAR Attachment Download API. """ message_id = str(args.get("message_id", "")) attachment_name = str(args.get("attachment_name", "")) tenant_id = arg_to_number(args.get("tenant_id")) raw_message_id = str(args.get("raw_message_id", "")) native_user_id = str(args.get("native_user_id", "")) recipient_mailbox = str(args.get("recipient_mailbox", "")) response = client.download_message_attachment_request( message_id, attachment_name, tenant_id, raw_message_id, native_user_id, recipient_mailbox ) # Return the file to XSOAR file_content = response.content results = fileResult(attachment_name, file_content) return results def download_message_eml_command(client, args): """ Download a message in EML format using the SOAR EML Download API. """ cloud_message_id = str(args.get("cloud_message_id", "")) quarantine_identity = args.get("quarantine_identity") recipient_mailbox = args.get("recipient_mailbox") response = client.download_message_eml_request(cloud_message_id, quarantine_identity, recipient_mailbox) # Generate filename from cloud_message_id # Replace special characters to create a valid filename safe_filename = cloud_message_id.replace(":", "_").replace("/", "_") filename = f"{safe_filename}.eml" # Return the EML file to XSOAR file_content = response.content results = fileResult(filename, file_content) return results def generate_threat_incidents(client, threats, max_page_number, start_datetime, end_datetime): incidents = [] for threat in threats: page_number = 1 all_messages, all_filtered_messages = [], [] threat_details = None try: while page_number is not None: threat_details = client.get_details_of_a_threat_request(threat["threatId"], page_number=page_number) for message in threat_details["messages"]: all_messages.append(message) remediation_datetime = try_str_to_datetime(message.get("remediationTimestamp")) if remediation_datetime and start_datetime <= remediation_datetime <= end_datetime: all_filtered_messages.append(message) if remediation_datetime and remediation_datetime < start_datetime: break page_number = threat_details.get("nextPageNumber", None) if page_number is not None and page_number > max_page_number: break except DemistoException as e: if _is_skippable_error(e): demisto.debug(f"Threat {threat['threatId']} returned a skippable error, skipping: {e}") continue raise # Skip if we didn't get any threat details (shouldn't happen but defensive) if threat_details is None: continue received_time = "" threat_details["messages"] = all_filtered_messages or all_messages if threat_details.get("messages", []): received_time = threat_details["messages"][0].get("receivedTime") incident = { "dbotMirrorId": str(threat["threatId"]), "name": "Threat", "occurred": received_time[:26] if len(received_time) > 26 else received_time, "details": "Threat", "rawJSON": json.dumps(threat_details) if threat_details else {}, } incidents.append(incident) return incidents def generate_abuse_campaign_incidents(client, campaigns): incidents = [] for campaign in campaigns: try: campaign_details = client.get_details_of_an_abuse_mailbox_campaign_request(campaign["campaignId"]) except DemistoException as e: if _is_skippable_error(e): demisto.debug(f"Campaign {campaign['campaignId']} returned a skippable error, skipping: {e}") continue raise first_reported = campaign_details.get("firstReported", "") incident = { "dbotMirrorId": str(campaign.get("campaignId", "")), "name": "Abuse Campaign", "occurred": first_reported[:26] if len(first_reported) > 26 else first_reported, "details": "Abuse Campaign", "rawJSON": json.dumps(campaign_details) if campaign_details else {}, } incidents.append(incident) return incidents def generate_account_takeover_cases_incidents(client, cases): incidents = [] for case in cases: try: case_details = client.get_details_of_an_abnormal_case_request(case["caseId"]) except DemistoException as e: if _is_skippable_error(e): demisto.debug(f"Case {case['caseId']} returned a skippable error, skipping: {e}") continue raise incident = { "dbotMirrorId": str(case["caseId"]), "name": "Account Takeover Case", "occurred": case_details["firstObserved"], "details": case["description"], "genaiSummary": case_details["genai_summary"], "rawJSON": json.dumps(case_details) if case_details else {}, } incidents.append(incident) return incidents def fetch_incidents( client: Client, last_run: dict[str, Any], first_fetch_time: str, fetch_threats: bool, fetch_abuse_campaigns: bool, fetch_account_takeover_cases: bool, max_page_number: int = 8, max_incidents_to_fetch: int = FETCH_LIMIT, polling_lag: timedelta = timedelta(minutes=0), ): """ Fetch incidents from various sources (threats, abuse campaigns, and account takeovers). Parameters: - client (Client): Client object to interact with the API. - last_run (Dict[str, Any]): Dictionary containing details about the last time incidents were fetched. - first_fetch_time (str): ISO formatted string indicating the first time from which to start fetching incidents. - max_page_number (int): Maximum number of pages to fetch for incidents. - max_incidents_to_fetch (int, optional): Maximum number of incidents to fetch. Defaults to FETCH_LIMIT. - polling_lag (int, optional): Time in minutes to subtract from polling time window for data consistency. Defaults to 0. Returns: - Tuple[Dict[str, str], List[Dict]]: Tuple containing a dictionary with the `last_fetch` time and a list of fetched incidents. """ try: last_fetch = last_run.get("last_fetch", first_fetch_time) last_fetch = datetime.fromisoformat(last_fetch[:-1]).astimezone(timezone.utc) current_datetime = get_current_datetime() start_time = last_fetch + timedelta(milliseconds=1) # Not to overlap with previous polling window end_time = get_current_datetime() if polling_lag is not None: start_time = start_time - polling_lag end_time = end_time - polling_lag start_timestamp = start_time.strftime(ISO_8601_FORMAT) end_timestamp = end_time.strftime(ISO_8601_FORMAT) all_incidents = [] current_pending_incidents_to_fetch = max_incidents_to_fetch threat_incidents, abuse_campaign_incidents, account_takeover_cases_incidents = [], [], [] if fetch_threats and current_pending_incidents_to_fetch > 0: threats_filter = f"latestTimeRemediated gte {start_timestamp} and latestTimeRemediated lte {end_timestamp}" threats_response = client.get_paginated_threats_list( filter_=threats_filter, max_incidents_to_fetch=current_pending_incidents_to_fetch ) threat_incidents = generate_threat_incidents( client, threats_response.get("threats", []), max_page_number, start_time, end_time ) current_pending_incidents_to_fetch -= len(threat_incidents) if fetch_abuse_campaigns and current_pending_incidents_to_fetch > 0: abuse_campaigns_filter = f"lastReportedTime gte {start_timestamp} and lastReportedTime lte {end_timestamp}" abuse_campaigns_response = client.get_paginated_abusecampaigns_list( filter_=abuse_campaigns_filter, max_incidents_to_fetch=current_pending_incidents_to_fetch ) abuse_campaign_incidents = generate_abuse_campaign_incidents(client, abuse_campaigns_response.get("campaigns", [])) current_pending_incidents_to_fetch -= len(abuse_campaign_incidents) if fetch_account_takeover_cases and current_pending_incidents_to_fetch > 0: account_takeover_cases_filter = f"lastModifiedTime gte {start_timestamp} and lastModifiedTime lte {end_timestamp}" account_takeover_cases_response = client.get_paginated_cases_list( filter_=account_takeover_cases_filter, max_incidents_to_fetch=current_pending_incidents_to_fetch ) account_takeover_cases_incidents = generate_account_takeover_cases_incidents( client, account_takeover_cases_response.get("cases", []) ) all_incidents = threat_incidents + abuse_campaign_incidents + account_takeover_cases_incidents except Exception as e: logging.error(f"Failed fetching incidents: {e}") raise FetchIncidentsError(f"Error while fetching incidents: {e}") next_run = {"last_fetch": current_datetime.strftime(ISO_8601_FORMAT)} return next_run, all_incidents[:max_incidents_to_fetch] def test_module(client): # Run a sample request to retrieve mock data client.get_a_list_of_threats_request(None, None, None, None) demisto.results("ok") def main(): # pragma: nocover params = demisto.params() args = demisto.args() url = params.get("url") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) is_fetch = params.get("isFetch") headers = {} mock_data = str(args.get("mock-data", "")) if mock_data.lower() == "true": headers["Mock-Data"] = "True" headers["Authorization"] = f'Bearer {params["api_key"]}' headers["Soar-Integration-Origin"] = "Cortex XSOAR" command = demisto.command() demisto.debug(f"Command being called is {command}") try: client = Client(urljoin(url, ""), verify_certificate, proxy, headers=headers, auth=None) commands = { # Threat commands "abnormal-security-list-threats": get_a_list_of_threats_command, "abnormal-security-get-threat": get_details_of_a_threat_command, "abnormal-security-manage-threat": manage_a_threat_identified_by_abnormal_security_command, "abnormal-security-check-threat-action-status": check_the_status_of_an_action_requested_on_a_threat_command, "abnormal-security-download-threat-log-csv": download_data_from_threat_log_in_csv_format_command, # Case commands "abnormal-security-list-abnormal-cases": get_a_list_of_abnormal_cases_identified_by_abnormal_security_command, "abnormal-security-get-abnormal-case": get_details_of_an_abnormal_case_command, "abnormal-security-manage-abnormal-case": manage_an_abnormal_case_command, "abnormal-security-check-case-action-status": check_the_status_of_an_action_requested_on_a_case_command, "abnormal-security-get-case-analysis-and-timeline": provides_the_analysis_and_timeline_details_of_a_case_command, # Threat Intel commands "abnormal-security-get-latest-threat-intel-feed": get_the_latest_threat_intel_feed_command, # Abuse Mailbox commands "abnormal-security-list-abuse-mailbox-campaigns": get_a_list_of_campaigns_submitted_to_abuse_mailbox_command, "abnormal-security-get-abuse-mailbox-campaign": get_details_of_an_abuse_mailbox_campaign_command, "abnormal-security-list-unanalyzed-abuse-mailbox-campaigns": get_a_list_of_unanalyzed_abuse_mailbox_campaigns_command, # Employee commands "abnormal-security-get-employee-identity-analysis": get_employee_identity_analysis_genome_data_command, "abnormal-security-get-employee-information": get_employee_information_command, "abnormal-security-get-employee-last-30-days-login-csv": # noqa: E501 get_employee_login_information_for_last_30_days_in_csv_format_command, # Detection 360 commands "abnormal-security-submit-inquiry-to-request-a-report-on-misjudgement": # noqa: E501 submit_an_inquiry_to_request_a_report_on_misjudgement_by_abnormal_security_command, "abnormal-security-submit-false-negative-report": submit_false_negative_report_command, "abnormal-security-submit-false-positive-report": submit_false_positive_report_command, # Vendor commands "abnormal-security-list-vendors": get_a_list_of_vendors_command, "abnormal-security-get-vendor-details": get_the_details_of_a_specific_vendor_command, "abnormal-security-get-vendor-activity": get_the_activity_of_a_specific_vendor_command, # Vendor case commands "abnormal-security-list-vendor-cases": get_a_list_of_vendor_cases_command, "abnormal-security-get-vendor-case-details": get_the_details_of_a_vendor_case_command, # SOAR Message Search and Respond commands "abnormal-security-search-messages": search_messages_command, "abnormal-security-remediate-messages": remediate_messages_command, "abnormal-security-list-activities": get_activities_list_command, "abnormal-security-get-activity-status": get_activity_status_command, "abnormal-security-download-message-attachment": download_message_attachment_command, "abnormal-security-download-message-eml": download_message_eml_command, } if command == "test-module": # pragma: no cover headers["Mock-Data"] = "True" test_client = Client(urljoin(url, ""), verify_certificate, proxy, headers=headers, auth=None) test_module(test_client) elif command == "fetch-incidents" and is_fetch: # pragma: no cover max_incidents_to_fetch = arg_to_number(params.get("max_fetch", FETCH_LIMIT)) fetch_threats = params.get("fetch_threats", False) # Get the polling lag time parameter polling_lag_minutes = int(params.get("polling_lag", 2)) max_page_number = int(params.get("max_page_number", 8)) polling_lag_delta = timedelta(minutes=polling_lag_minutes) fetch_abuse_campaigns = params.get("fetch_abuse_campaigns", False) fetch_account_takeover_cases = params.get("fetch_account_takeover_cases", False) first_fetch_datetime = arg_to_datetime(arg=params.get("first_fetch"), arg_name="First fetch time", required=True) if first_fetch_datetime: first_fetch_time = first_fetch_datetime.strftime(ISO_8601_FORMAT) else: first_fetch_time = datetime.now().strftime(ISO_8601_FORMAT) next_run, incidents = fetch_incidents( client=client, last_run=demisto.getLastRun(), first_fetch_time=first_fetch_time, max_incidents_to_fetch=max_incidents_to_fetch or FETCH_LIMIT, fetch_threats=fetch_threats, fetch_abuse_campaigns=fetch_abuse_campaigns, fetch_account_takeover_cases=fetch_account_takeover_cases, max_page_number=max_page_number, polling_lag=polling_lag_delta, ) demisto.setLastRun(next_run) demisto.incidents(incidents) elif command in commands: return_results(commands[command](client, args)) # type: ignore else: raise NotImplementedError(f"{command} command is not implemented.") except Exception as e: return_error(str(e)) if __name__ in ["__main__", "builtin", "builtins"]: main()