PhishLabs IOC
Get indicators of compromise from PhishLabs.
Data Enrichment & Threat Intelligence · PhishLabs
Details
| ID | PhishLabs IOC |
|---|---|
| Provider | Fortra |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
PhishLabs’ three 24/7 Security Operations Centers enables enterprise security teams to rapidly detect and respond to the email-based threats that reach the inboxes of end users.
In Cortex XSOAR, PhishLabs IOC can be used to retrieve indicators from the global feed or fetch email based incidents from the user feed.
PhishLabs IOC Playbooks


Use Cases
- Retrieve and populate indicators from the PhishLabs IOC global feed
- Fetch and retrieve indicators for email based incidents in the PhishLabs IOC user feed
Configure PhishLabs IOC on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for PhishLabs IOC.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Server URL (e.g., https://ioc.phishlabs.com)
- Credentials
- Source Reliability. Reliability of the source providing the intelligence data. (The default value is: B - Usually reliable)
- Trust any certificate (not secure)
- Use system proxy settings
- Fetch incidents
- Fetch for this time period, e.g., “1d”, “1h”, “10m”. The default is 1h.
- Number of incidents to fetch each time
- Incident type
- Click Test to validate the URLs, token, and connection.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.
1. Get the global IOC feed
Retrieves the global IOC feed from PhishLabs.
Base Command
phishlabs-global-feed
Input
| Argument Name | Description | Required |
|---|---|---|
| since | Duration (from now) for which to pull updated data, for example, “1d”, “1h” or “10m”. | Optional |
| limit | Maximum number of results to return. | Optional |
| indicator_type | Filter the data by indicator type. | Optional |
| remove_protocol | Removes the protocol part from indicators, when the rule can be applied. | Optional |
| remove_query | Removes the query string part from indicators, when the rules can be applied. | Optional |
| false_positive | Whether the indicator is a false positive. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| URL.Data | String | URL address. |
| URL.Malicious.Vendor | String | Vendor reporting the malicious status. |
| URL.Malicious.Description | String | Description of the malicious URL. |
| PhishLabs.URL.Data | String | URL address. |
| PhishLabs.URL.ID | String | URL PhishLabs ID. |
| PhishLabs.URL.CreatedAt | Date | URL creation time, in PhishLabs. |
| PhishLabs.URL.UpdatedAt | Date | URL update time, in PhishLabs. |
| PhishLabs.URL.Attribute.Name | String | URL attribute name. |
| PhishLabs.URL.Attribute.Value | String | URL attribute value. |
| PhishLabs.URL.Attribute.CreatedAt | Date | URL attribute creation time. |
| PhishLabs.URL.FalsePositive | Boolean | Whether this URL is a false positive. |
| Domain.Name | String | Domain name. |
| Domain.Malicious.Vendor | String | Vendor reporting the malicious status. |
| Domain.Malicious.Description | String | Description of the malicious domain. |
| PhishLabs.Domain.Name | String | Domain name. |
| PhishLabs.Domain.ID | String | Domain PhishLabs ID. |
| PhishLabs.Domain.CreatedAt | Date | Domain creation time, in PhishLabs. |
| PhishLabs.Domain.UpdatedAt | Date | Domain update time, in PhishLabs. |
| PhishLabs.Domain.Attribute.Name | String | Domain attribute name. |
| PhishLabs.Domain.Attribute.Value | String | Domain attribute value. |
| PhishLabs.Domain.Attribute.CreatedAt | Date | Domain attribute creation time. |
| PhishLabs.Domain.FalsePositive | Boolean | Whether this domain is a false positive. |
| File.Name | String | Full filename. |
| File.MD5 | String | MD5 hash of the file. |
| File.Type | String | File type. |
| PhishLabs.File.ID | String | File PhishLabs ID. |
| PhishLabs.File.Name | String | Full filename. |
| PhishLabs.File.MD5 | String | MD5 hash of the file. |
| PhishLabs.File.Type | String | File type. |
| PhishLabs.File.CreatedAt | Date | File creation time, in PhishLabs. |
| PhishLabs.File.UpdatedAt | Date | File update time, in PhishLabs. |
| PhishLabs.File.Attribute.Name | String | File attribute name. |
| PhishLabs.File.Attribute.Value | String | File attribute value. |
| PhishLabs.File.Attribute.CreatedAt | Date | File attribute creation time. |
| PhishLabs.File.FalsePositive | Boolean | Whether this file is a false positive. |
| DBotScore.Indicator | string | The indicator that was tested. |
| DBotScore.Type | string | Indicator type. |
| DBotScore.Vendor | string | Vendor used to calculate the score. |
| DBotScore.Score | number | The actual score. |
Command Example
phishlabs-global-feed since=30d indicator_type=Domain remove_protocol=true limit=10
Context Example
{
"Domain": [
{
"Malicious": {
"Vendor": "PhishLabs",
"Description": "Domain in PhishLabs feed"
},
"Name": "malicious1.tk/"
},
{
"Malicious": {
"Vendor": "PhishLabs",
"Description": "Domain in PhishLabs feed"
},
"Name": "malicious2.tk/"
},
{
"Malicious": {
"Vendor": "PhishLabs",
"Description": "Domain in PhishLabs feed"
},
"Name": "malicious3.tk/"
},
{
"Malicious": {
"Vendor": "PhishLabs",
"Description": "Domain in PhishLabs feed"
},
"Name": "malicious4.com/"
}
],
"DBotScore": [
{
"type": "domain",
"Indicator": "malicious1.tk/",
"Score": 3,
"Vendor": "PhishLabs"
},
{
"type": "domain",
"Indicator": "malicious2.tk/",
"Score": 3,
"Vendor": "PhishLabs"
},
{
"type": "domain",
"Indicator": "malicious3.tk/",
"Score": 3,
"Vendor": "PhishLabs"
},
{
"type": "domain",
"Indicator": "malicious4.com/",
"Score": 3,
"Vendor": "PhishLabs"
}
],
"PhishLabs.Domain": [
{
"ID": "009d2062-bc79-4836-a649-80286612199e",
"CreatedAt": "2019-05-17T03:29:54Z",
"Name": "malicious1.tk/"
},
{
"ID": "80c16ebb-afe1-4898-91a6-c4b39d50a14f",
"CreatedAt": "2019-05-17T03:29:54Z",
"Name": "malicious2.tk/"
},
{
"ID": "a435f1e4-1e92-4921-8a5f-12bc1a7a67ce",
"CreatedAt": "2019-05-17T03:29:54Z",
"Name": "malicious3.tk/"
},
{
"ID": "f3923b6c-0445-40ef-998c-8cf57adb391d",
"CreatedAt": "2019-05-15T19:57:43Z",
"Name": "malicious4.com/"
}
]
}
Human Readable Output
PhishLabs Global Feed
| Indicator | Type | Created At | False Positive |
|---|---|---|---|
| malicious1.tk/ | Domain | 2019-05-17T03:29:54Z | false |
| malicious2.tk/ | Domain | 2019-05-17T03:29:54Z | false |
| malicious3.tk/ | Domain | 2019-05-17T03:29:54Z | false |
| malicious4.com/ | Domain | 2019-05-15T19:57:43Z | false |
2. Get indicators for an incident
Retrieves indicators from a specified PhishLabs incident. To fetch incidents to Cortex XSOAR, enable fetching incidents.
Base Command
phishlabs-get-incident-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | PhishLabs incident reference ID, for example, “INC123456”. | Required |
| since | Duration (from now) for which to pull updated data, for example, “1d”, “1h” or “10m”. | Optional |
| limit | Maximum number of results to return. | Optional |
| indicator_type | Filter the data by indicator type. | Optional |
| indicators_classification | How to classify indicators from the feed. | Optional |
| remove_protocol | Removes the protocol part from indicators, when the rule can be applied. | Optional |
| remove_query | Removes the query string part from indicators, when the rules can be applied. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| URL.Data | String | URL address. |
| URL.Malicious.Vendor | String | Vendor reporting the malicious status. |
| URL.Malicious.Description | String | Description of the malicious URL. |
| PhishLabs.URL.Data | String | URL address. |
| PhishLabs.URL.CreatedAt | Date | URL creation time, in PhishLabs |
| PhishLabs.URL.UpdatedAt | Date | URL update time, in PhishLabs. |
| PhishLabs.URL.Attribute.Name | String | URL attribute name. |
| PhishLabs.URL.Attribute.Value | String | URL attribute value. |
| PhishLabs.URL.Attribute.CreatedAt | Date | URL attribute creation time. |
| PhishLabs.URL.FalsePositive | Boolean | Whether this URL is a false positive. |
| Domain.Name | String | Domain name. |
| Domain.Malicious.Vendor | String | Vendor reporting the malicious status. |
| Domain.Malicious.Description | String | Description of the malicious domain. |
| PhishLabs.Domain.Name | String | Domain name |
| PhishLabs.Domain.CreatedAt | Date | Domain creation time, in PhishLabs. |
| PhishLabs.Domain.UpdatedAt | Date | Domain update time, in PhishLabs. |
| PhishLabs.Domain.Attribute.Name | String | Domain attribute name. |
| PhishLabs.Domain.Attribute.Value | String | Domain attribute value. |
| PhishLabs.Domain.Attribute.CreatedAt | Date | Domain attribute creation time. |
| PhishLabs.Domain.FalsePositive | Boolean | Whether this domain is a false positive. |
| Email.To | String | Recipient of the email. |
| Email.From | String | Sender of the email. |
| Email.Body | String | Body of the email. |
| Email.Subject | String | Subject of the email. |
| PhishLabs.Email.ID | String | Email PhishLabs ID. |
| PhishLabs.Email.To | String | Recipient of the email. |
| PhishLabs.Email.From | String | Sender of the email. |
| PhishLabs.Email.Body | String | Body of the email. |
| PhishLabs.Email.Subject | String | Subject of the email. |
| PhishLabs.Email.CreatedAt | Date | Email creation time, in PhishLabs. |
| PhishLabs.Email.UpdatedAt | Date | Email update time, in PhishLabs. |
| PhishLabs.Email.Attribute.Name | String | Email attribute name. |
| PhishLabs.Email.Attribute.Value | String | Email attribute value. |
| PhishLabs.Email.Attribute.CreatedAt | Date | Email attribute creation time. |
| File.Name | String | Full filename. |
| File.MD5 | String | MD5 hash of the file. |
| File.Type | String | File type. |
| PhishLabs.File.ID | String | File PhishLabs ID. |
| PhishLabs.File.Name | String | Full filename. |
| PhishLabs.File.MD5 | String | MD5 hash of the file. |
| PhishLabs.File.Type | String | File type. |
| PhishLabs.File.CreatedAt | Date | File creation time, in PhishLabs. |
| PhishLabs.File.UpdatedAt | Date | File update time, in PhishLabs. |
| PhishLabs.File.Attribute.Name | String | File attribute name. |
| PhishLabs.File.Attribute.Value | String | File attribute value. |
| PhishLabs.File.Attribute.CreatedAt | Date | File attribute creation time. |
| PhishLabs.File.FalsePositive | Boolean | Whether this file is a false positive. |
| DBotScore.Indicator | string | The indicator that was tested. |
| DBotScore.Type | string | Indicator type. |
| DBotScore.Vendor | string | Vendor used to calculate the score. |
| DBotScore.Score | number | The actual score. |
Command Example
phishlabs-get-incident-indicators incident_id=INC0037375 indicators_classification=Suspicious since=7d
Context Example
{
"URL": [
{
"Data": "https://malicious1?email=dbot@demisto.com"
},
{
"Data": "http://malicious2/index.html?l=_JeHFUq_VJOXK0QWHtoGYDw1774256418&fid.13InboxLight.aspxn.1774256418&fid.125289964252813InboxLight99642_Product-user&user=#dbot@demisto.com"
}
],
"PhishLabs.Email": [
{
"Body": "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><table border=\"0\" style=\"font-family: calibri; font-size: 16px; background-color: rgb(255, 255, 255);\" width=\"100%\">\n\t <tbody><tr><td align=\"center\">\n\t <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" height=\"100%\" style=\"min-width: 600px;\" width=\"100%\">\n\t <tbody><tr align=\"center\"><td>\n\t <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width: 600px;\">\n\t <tbody><tr><td>\n <tbody></tbody></table></td></tr><tr height=\"16\"></tr><tr><td>\n <tbody><tr><td colspan=\"3\" height=\"69px\"> </td></tr><tr><td width=\"28px\"> </td>\n\t <td style=\"font-family: Roboto-Regular, Helvetica, Arial, sans-serif; font-size: 57px; color: rgb(255, 255, 255); line-height: 0.25;\">\n <span style=\"color:#f95316;\">Office-365 Password</span></a></td><td width=\"32px\"> </td></tr>\n <tr><td colspan=\"3\" height=\"18px\"> </td></tr></tbody></table></td></tr>\n <table bgcolor=\"#fff\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"min-width: 600px; max-width: 900px; border-width: 0px 1px 1px; border-right-style: solid; border-left-style: solid; border-right-color: rgb(240, 240, 240); border-left-color: rgb(240, 240, 240); border-bottom-style: solid; border-bottom-color: rgb(192, 192, 192); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px;\" width=\"100%\">\n\t \n\n\t <tbody><tr height=\"16px\"><td rowspan=\"3\" width=\"32px\"> </td><td> </td>\n\t <td rowspan=\"3\" width=\"32px\"> </td></tr><tr><td><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"min-width: 300px;\">\n\t <tbody><tr><td style=\"font-family: Roboto-Regular, Helvetica, Arial, sans-serif; font-size: 13px; color: rgb(32, 32, 32); line-height: 1.5;\">\n <a href=\"https://bestiankelly.com/kp/index.php?email=dbot@demisto.com\" style=\"text-decoration: none;\">\n <span style=\"color:#f95316;\"> </span> .\n<br> <br>\n\n <a href=\"https://bestiankelly.com/kp/index.php?email=dbot@demisto.com\" style=\"text-decoration: none;\">\n <span style=\"color:#000000;\">\n Dear <b>dbot</b><div> </div>\n\n\n\t Your account password is due for expiration today\n<br><br>\t\n<body> Please kindly use the below to continue with same password.\n\n<body> \n<br>\n</head>\n<body style=\"margin: 0.4em;\">\n<p><font color=\"#ffffff\" size=\"4\" style=\"background-color: rgb(38, 136, 217);\"><u><strong>Keep Same password </strong></u></font></p>\n\n\n\n<br>\n \n<br>\n\t <p>Security Team <br></span></a><br> </p></td>\n\n </tr></tbody></table></td></tr></tbody></table></td></tr>\n\t <tr height=\"16\"></tr><tr><td style=\"max-width: 900px; font-family: Roboto-Regular, Helvetica, Arial, sans-serif; font-size: 10px; color: rgb(188, 188, 188); line-height: 1.5;\"> </td></tr><tr><td>\n <table style=\"font-family: Roboto-Regular, Helvetica, Arial, sans-serif; font-size: 10px; color: rgb(102, 102, 102); line-height: 18px; padding-bottom: 10px;\">\n\t <tbody><tr><td><span style=\"color:#424242;\">This notification was sent to <b>dbot@demisto.com</b> of Microsoft.com.</span></a></td></tr></tbody></table></td></tr></tbody></table></td>\n\t <td width=\"32px\"> </td></tr></tbody></table></td></tr></tbody>\n</table>",
"From": [
"Microsoft update <onme@www1079.sakura.ne.jp>"
],
"Attribute": [
{
"Value": "Microsoft update <onme@www1079.sakura.ne.jp>",
"Type": null,
"Name": "from",
"CreatedAt": "2019-05-23T16:56:59Z"
},
{
"Value": "<dbot@demisto.com>",
"Type": null,
"Name": "to",
"CreatedAt": "2019-05-23T16:56:59Z"
},
{
"Value": "<tbody><tr><td><span style=\"color:#424242;\">This notification was sent to <b>dbot@demisto.com</b> of Microsoft.com.</span></a></td></tr></tbody></table></td></tr></tbody></table></td>\n\t <td width=\"32px\"> </td></tr></tbody></table></td></tr></tbody>\n</table>",
"Type": null,
"Name": "email-body",
"CreatedAt": "2019-05-23T16:56:59Z"
}
],
"To": [
"<dbot@demisto.com>"
],
"ID": "cdb80cf5-d012-4b8d-86a7-7956ed026835",
"CreatedAt": "2019-05-23T16:56:59Z",
"Subject": "[[ Account Password Reset]]"
}
],
"DBotScore": [
{
"type": "url",
"Indicator": "malicious1?email=dbot@demisto.com",
"Score": 2,
"Vendor": "PhishLabs"
},
{
"type": "url",
"Indicator": "http://malicious2/index.html?l=_JeHFUq_VJOXK0QWHtoGYDw1774256418&fid.13InboxLight.aspxn.1774256418&fid.125289964252813InboxLight99642_Product-user&user=#dbot@demisto.com",
"Score": 2,
"Vendor": "PhishLabs"
}
],
"Email": [
{
"Body": " <tbody><tr><td><span style=\"color:#424242;\">This notification was sent to <b>dbot@demisto.com</b> of Microsoft.com.</span></a></td></tr></tbody></table></td></tr></tbody></table></td>\n\t <td width=\"32px\"> </td></tr></tbody></table></td></tr></tbody>\n</table>",
"To": "<dbot@demisto.com>",
"From": "Microsoft update <onme@www1079.sakura.ne.jp>",
"Subject": "[[ Account Password Reset]]"
}
],
"PhishLabs.URL": [
{
"Data": "https://malicious1/index.php?email=dbot@demisto.com",
"ID": "04e38909-53d8-4e4a-8593-8d1fd5a10261",
"CreatedAt": "2019-05-23T16:56:59Z"
},
{
"Data": "http://malicious2/index.html?l=_JeHFUq_VJOXK0QWHtoGYDw1774256418&fid.13InboxLight.aspxn.1774256418&fid.125289964252813InboxLight99642_Product-user&user=#dbot@demisto.com",
"ID": "354bbeb8-9fea-4ccd-85bb-ce68cd364113",
"CreatedAt": "2019-05-23T16:56:59Z"
}
]
}
Human Readable Output
Indicators for incident INC0037375
Indicator
| Indicator | Type | Created At | False Positive |
|---|---|---|---|
| malicious1/index.php?email=dbot@demisto.com | URL | 2019-05-23T16:56:59Z | false |
No attributes for this indicator
Indicator
| Indicator | Type | Created At | False Positive |
|---|---|---|---|
| malicious2/index.html?l=_JeHFUq_VJOXK0QWHtoGYDw1774256418&fid.13InboxLight.aspxn.1774256418&fid.125289964252813InboxLight99642_Product-user&user=#dbot@demisto.com | URL | 2019-05-23T16:56:59Z | false |
No attributes for this indicator
Indicator
| Indicator | Type | Created At | False Positive |
|---|---|---|---|
| [[ Account Password Reset]] | 2019-05-23T16:56:59Z | false |
Attributes
| Name | Value | Created At |
|---|---|---|
| from | Microsoft update onme@www1079.sakura.ne.jp | 2019-05-23T16:56:59Z |
| to | dbot@demisto.com | 2019-05-23T16:56:59Z |
| email-body | ||
| This notification was sent to dbot@demisto.comof Microsoft.com. |
2019-05-23T16:56:59Z
Additional Information
The IOC feed in PhishLabs is divided into two endpoints:
Global Feed
This is the PhishLabs global database for malicious indicators.
This feed consists of indicators that are classified as malicious by PhishLabs -
URLs, domains, and attachments (MD5 hashes). All the indicators from this feed are classified as malicious in Cortex XSOAR.
To populate indicators from PhishLabs in Cortex XSOAR, use the PhishLabsPopulateIndicators script/playbooks.
User Feed
This feed is exclusive for the user and consists of emails that were sent to PhishLabs and were classified as malicious emails. For each malicious email, an incident is created that contains the email details and the extracted indicators. These indicators are not necessarily malicious though. In Cortex XSOAR,
the user can choose whether to classify those indicators as malicious or suspicious. Incidents can be fetched by enabling fetch incidents in the integration configuration.
Known Limitations
The PhishLabs IOC API is on version 0.1.0, it may be subject to change.
Troubleshooting
Retrieving indicators for an incident - if the incident was fetched to Cortex XSOAR but wasn’t found by the command, try running it with a longer duration, for example, since=30d.
Possible error codes from the API:
400 Bad Request - Unsupported request format
401 Unauthorized - Incorrect credentials provided
403 Forbidden - Insufficient permissions
404 Not Found - Requested resource was not found
.
Configuration parameters
url— Server URL (e.g., https://ioc.phishlabs.com) (required)credentials— Username (required)integrationReliability— Source Reliability (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsfetch_time— Fetch for this time period, e.g., "1d", "1h", "10m". The default is 1h.fetch_limit— Number of incidents to fetch each timeincidentType— Incident typeincidentFetchInterval— Incidents Fetch Interval
Commands (2)
-
phishlabs-get-incident-indicatorsRetrieves indicators from a speicifed PhishLabs incident. To fetch incidents to Demisto, enable fetching incidents.
-
phishlabs-global-feedRetrieves the global IOC feed from PhishLabs.
import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * """ IMPORTS """ import json from collections.abc import Callable import requests # Disable insecure warnings requests.packages.urllib3.disable_warnings() # type: ignore """ GLOBALS/PARAMS """ HEADERS: dict = {"Content-Type": "application/json", "Accept": "application/json"} NONE_DATE: str = "0001-01-01T00:00:00Z" RAISE_EXCEPTION_ON_ERROR: bool = False SEC_IN_DAY: int = 86400 class Client: def __init__(self, base_url: str, user_name: str, password: str, use_ssl: bool, reliability: str = DBotScoreReliability.B): self.base_url = base_url self.user_name = user_name self.password = password self.use_ssl = use_ssl self.reliability = reliability @logger def http_request(self, method: str, path: str, params: dict = None, data: dict = None) -> dict: """ Sends an HTTP request using the provided arguments :param method: HTTP method :param path: URL path :param params: URL query params :param data: Request body :return: JSON response """ params: dict = params if params is not None else {} data: dict = data if data is not None else {} try: res: requests.Response = requests.request( method, self.base_url + path, auth=(self.user_name, self.password), verify=self.use_ssl, params=params, data=json.dumps(data), headers=HEADERS, ) except requests.exceptions.SSLError: ssl_error = "Could not connect to PhishLabs IOC Feed: Could not verify certificate." if RAISE_EXCEPTION_ON_ERROR: raise Exception(ssl_error) return return_error(ssl_error) except ( requests.exceptions.ConnectionError, requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.RequestException, ) as e: connection_error = f"Could not connect to PhishLabs IOC Feed: {e!s}" if RAISE_EXCEPTION_ON_ERROR: raise Exception(connection_error) return return_error(connection_error) if res.status_code < 200 or res.status_code > 300: status: int = res.status_code message: str = res.reason try: error_json: dict = res.json() message = error_json.get("error", "") except Exception: pass error_message: str = f"Error in API call to PhishLabs IOC API, status code: {status}" if status == 401: error_message = "Could not connect to PhishLabs IOC Feed: Wrong credentials" if message: error_message += ", reason:" + message if RAISE_EXCEPTION_ON_ERROR: raise Exception(error_message) else: return return_error(error_message) try: return res.json() except Exception: error_message = f"Failed parsing the response from PhishLabs IOC API: {res.content!r}" if RAISE_EXCEPTION_ON_ERROR: raise Exception(error_message) else: return return_error(error_message) """ HELPER FUNCTIONS """ @logger def populate_context( dbot_scores: list, domain_entries: list, file_entries: list, url_entries: list, email_entries: list = None ) -> dict: """ Populate the context object with entries as tuples - the first element contains global objects and the second contains PhishLabs objects :param dbot_scores: Indicator DBotScore :param domain_entries: Domain indicators :param file_entries: File indicators :param url_entries: URL indicators :param email_entries: Email indicators :return: The context object """ context: dict = {} if url_entries: context[outputPaths["url"]] = createContext([u[0] for u in url_entries]) context["PhishLabs.URL(val.ID && val.ID === obj.ID)"] = createContext([u[1] for u in url_entries], removeNull=True) if domain_entries: context[outputPaths["domain"]] = createContext([d[0] for d in domain_entries]) context["PhishLabs.Domain(val.ID && val.ID === obj.ID)"] = createContext([d[1] for d in domain_entries], removeNull=True) if file_entries: context[outputPaths["file"]] = createContext([f[0] for f in file_entries]) context["PhishLabs.File(val.ID && val.ID === obj.ID)"] = createContext([f[1] for f in file_entries], removeNull=True) if email_entries: context["Email"] = createContext([e[0] for e in email_entries]) context["PhishLabs.Email(val.ID && val.ID === obj.ID)"] = createContext([e[1] for e in email_entries], removeNull=True) if dbot_scores: context[outputPaths["dbotscore"]] = dbot_scores return context @logger def get_file_properties(indicator: dict) -> tuple: """ Extract the file properties from the indicator attributes. Example: Indicator: { "attributes": [ { "createdAt": "2019-05-14T13:03:45Z", "id": "xyz", "name": "md5", "value": "c8092abd8d581750c0530fa1fc8d8318" # guardrails-disable-line }, { "createdAt": "2019-05-14T13:03:45Z", "id": "abc", "name": "filetype", "value": "application/zip" }, { "createdAt": "2019-05-14T13:03:45Z", "id": "qwe", "name": "name", "value": "Baycc.zip" } ], "createdAt": "2019-05-14T13:03:45Z", "falsePositive": false, "id": "def", "type": "Attachment", "updatedAt": "0001-01-01T00:00:00Z", "value": "c8092abd8d581750c0530fa1fc8d8318" # guardrails-disable-line } Return values: c8092abd8d581750c0530fa1fc8d8318, Baycc.zip, application/zip :param indicator: The file indicator :return: File MD5, name and type """ file_name_attribute: list = list(filter(lambda a: a.get("name") == "name", indicator.get("attributes", []))) file_name: str = file_name_attribute[0].get("value") if file_name_attribute else "" file_type_attribute: list = list(filter(lambda a: a.get("name") == "filetype", indicator.get("attributes", []))) file_type: str = file_type_attribute[0].get("value") if file_type_attribute else "" file_md5_attribute: list = list(filter(lambda a: a.get("name") == "md5", indicator.get("attributes", []))) file_md5: str = file_md5_attribute[0].get("value") if file_md5_attribute else "" return file_md5, file_name, file_type @logger def get_email_properties(indicator: dict) -> tuple: """ Extract the email properties from the indicator attributes. Example: Indicator: { "attributes": [ { "createdAt": "2019-05-13T16:54:18Z", "id": "abc", "name": "email-body", "value": "\r\n\r\n-----Original Message-----\r\nFrom: A \r\nSent: Monday, May 13, 2019 12:22 PM\r\nTo: }, { "createdAt": "2019-05-13T16:54:18Z", "id": "def", "name": "from", "value": "foo@test.com" }, { "createdAt": "2019-05-13T16:54:18Z", "id": "cf3182ca-92ec-43b6-8aaa-429802a99fe5", "name": "to", "value": "example@gmail.com" } ], "createdAt": "2019-05-13T16:54:18Z", "falsePositive": false, "id": "ghi", "type": "E-mail", "updatedAt": "0001-01-01T00:00:00Z", "value": "FW: Task" } Return values: :param indicator: The email indicator :return: Email body, To and From """ email_to_attribute: list = list(filter(lambda a: a.get("name") == "to", indicator.get("attributes", []))) email_to: str = email_to_attribute[0].get("value") if email_to_attribute else "" email_from_attribute: list = list(filter(lambda a: a.get("name") == "from", indicator.get("attributes", []))) email_from: str = email_from_attribute[0].get("value") if email_from_attribute else "" email_body_attribute: list = list(filter(lambda a: a.get("name") == "email-body", indicator.get("attributes", []))) email_body: str = email_body_attribute[0].get("value") if email_body_attribute else "" return email_body, email_to, email_from @logger def create_domain_context(indicator: dict, classification: str) -> dict: """ Create a domain context object :param indicator: The domain indicator :param classification: The indicator classification :return: The domain context object """ domain_object = {"Name": indicator.get("value")} if classification == "Malicious": domain_object["Malicious"] = {"Vendor": "PhishLabs", "Description": "Domain in PhishLabs feed"} return domain_object @logger def create_url_context(indicator: dict, classification: str) -> dict: """ Create a URL context object :param indicator: The URL indicator :param classification: The indicator classification :return: The URL context object """ url_object: dict = {"Data": indicator.get("value")} if classification == "Malicious": url_object["Malicious"] = {"Vendor": "PhishLabs", "Description": "URL in PhishLabs feed"} return url_object @logger def create_phishlabs_object(indicator: dict) -> dict: """ Create the context object for the PhishLabs path :param indicator: The indicator :return: The context object """ return { "ID": indicator.get("id"), "CreatedAt": indicator.get("createdAt"), "UpdatedAt": indicator["updatedAt"] if indicator.get("updatedAt", NONE_DATE) != NONE_DATE else "", "Attribute": [ {"Name": a.get("name"), "Type": a.get("type"), "Value": a.get("value"), "CreatedAt": a.get("createdAt")} for a in indicator.get("attributes", []) ], } def indicator_type_finder(indicator_data: dict): """Find the indicator type of the given indicator Args: indicator_data(dict): The data about the indicator Returns: str. The indicator type """ indicator = indicator_data.get("value") # PhishLabs IOC does not classify Email indicators correctly giving them typing of "ReplayTo", "HeaderReplyTo" # "ReturnPath" and so on - to combat that we find the Email indicator type by regex # returned URLs could fit the email regex at some cases so we exclude them if re.match(str(emailRegex), str(indicator)) and str(indicator_data.get("type")).lower() != "url": return "Email" else: return indicator_data.get("type") @logger def create_indicator_content(indicator: dict) -> dict: """ Create content for the human readable object :param indicator: The indicator :return: The object to return to the War Room """ return { "ID": indicator.get("id"), "Indicator": indicator.get("value"), "Type": indicator_type_finder(indicator), "CreatedAt": indicator.get("createdAt"), "UpdatedAt": indicator["updatedAt"] if indicator.get("updatedAt", NONE_DATE) != NONE_DATE else "", "FalsePositive": indicator.get("falsePositive"), } """ COMMANDS""" def test_module(client: Client): """ Performs basic get request to get item samples """ get_global_feed_request(client, limit="1") demisto.results("ok") def get_global_feed_command(client: Client): """ Gets the global feed data using the provided arguments """ indicator_headers: list = ["Indicator", "Type", "CreatedAt", "UpdatedAt", "FalsePositive"] contents: list = [] url_entries: list = [] domain_entries: list = [] file_entries: list = [] dbot_scores: list = [] context: dict = {} since: str = demisto.args().get("since") limit: str = demisto.args().get("limit") indicator: list = argToList(demisto.args().get("indicator_type", [])) remove_protocol: str = demisto.args().get("remove_protocol") remove_query: str = demisto.args().get("remove_query") false_positive: str = demisto.args().get("false_positive") feed: dict = get_global_feed_request(client, since, limit, indicator, remove_protocol, remove_query, false_positive) results: list = feed.get("data", []) if feed else [] if results: if not isinstance(results, list): results = [results] for result in results: contents.append(create_indicator_content(result)) indicator_false_positive = result.get("falsePositive", False) indicator_type: str = result.get("type") phishlabs_object: dict = create_phishlabs_object(result) dbot_score: dict = { "Indicator": result.get("value"), "Vendor": "PhishLabs", "Score": 3 if not indicator_false_positive else 1, "Reliability": client.reliability, } if indicator_type == "URL": context_object = create_url_context(result, "Malicious" if not indicator_false_positive else "Good") phishlabs_object["Data"] = result.get("value") dbot_score["type"] = "url" url_entries.append((context_object, phishlabs_object)) elif indicator_type == "Domain": context_object = create_domain_context(result, "Malicious" if not indicator_false_positive else "Good") phishlabs_object["Name"] = result.get("value") dbot_score["type"] = "domain" domain_entries.append((context_object, phishlabs_object)) elif indicator_type == "Attachment": file_md5, file_name, file_type = get_file_properties(result) context_object = {"Name": file_name, "Type": file_type, "MD5": file_md5} phishlabs_object["Name"] = file_name phishlabs_object["Type"] = file_type phishlabs_object["MD5"] = file_md5 file_entries.append((context_object, phishlabs_object)) dbot_score["type"] = "file" dbot_scores.append(dbot_score) context = populate_context(dbot_scores, domain_entries, file_entries, url_entries) human_readable: str = tableToMarkdown( "PhishLabs Global Feed", contents, headers=indicator_headers, removeNull=True, headerTransform=pascalToSpace ) else: human_readable = "No indicators found" return_outputs(human_readable, context, feed) @logger def get_global_feed_request( client: Client, since: str = None, limit: str = None, indicator: list = None, remove_protocol: str = None, remove_query: str = None, false_positive: str = None, ) -> dict: """ Sends a request to PhishLabs global feed with the provided arguments :param client: The client with the http request :param since: Data updated within this duration of time from now :param limit: Limit the number of rows to return :param indicator: Indicator type filter :param remove_protocol: Removes the protocol part from indicators when the rule can be applied. :param remove_query: Removes the query string part from indicators when the rules can be applied. :param false_positive: Filter by indicators that are false positives. :return: Global feed indicators """ path: str = "globalfeed" params: dict = {} if since: params["since"] = since if limit: params["limit"] = int(limit) if indicator: params["indicator"] = indicator if remove_protocol: params["remove_protocol"] = remove_protocol if remove_query: params["remove_query"] = remove_query if false_positive: params["false_positive"] = false_positive response = client.http_request("GET", path, params) return response def get_incident_indicators_command(client: Client): """ Gets the indicators for the specified incident """ indicator_headers: list = ["Indicator", "Type", "CreatedAt", "UpdatedAt", "FalsePositive"] attribute_headers: list = ["Name", "Type", "Value", "CreatedAt"] url_entries: list = [] domain_entries: list = [] file_entries: list = [] email_entries: list = [] dbot_scores: list = [] context: dict = {} incident_id: str = demisto.args()["incident_id"] since: str = demisto.args().get("since") limit: str = demisto.args().get("limit") indicator: list = argToList(demisto.args().get("indicator_type", [])) classification: str = demisto.args().get("indicators_classification", "Suspicious") remove_protocol: str = demisto.args().get("remove_protocol") remove_query: str = demisto.args().get("remove_query") human_readable: str = "## Indicators for incident " + incident_id + "\n" feed: dict = get_feed_request(client, since, indicator=indicator, remove_protocol=remove_protocol, remove_query=remove_query) results: list = feed.get("data", []) if feed else [] if results: if not isinstance(results, list): results = [results] results = list(filter(lambda f: f.get("referenceId", "") == incident_id, results)) if results: indicators = results[0].get("indicators", []) if limit: indicators = indicators[: int(limit)] for result in indicators: human_readable += tableToMarkdown( "Indicator", create_indicator_content(result), headers=indicator_headers, removeNull=True, headerTransform=pascalToSpace, ) phishlabs_object = create_phishlabs_object(result) if phishlabs_object.get("Attribute"): human_readable += tableToMarkdown( "Attributes", phishlabs_object["Attribute"], headers=attribute_headers, removeNull=True, headerTransform=pascalToSpace, ) else: human_readable += "No attributes for this indicator\n" indicator_type: str = result.get("type") dbot_score: dict = { "Indicator": result.get("value"), "Vendor": "PhishLabs", "Score": 3 if classification == "Malicious" else 2, "Reliability": client.reliability, } if indicator_type == "URL": context_object = create_url_context(result, classification) phishlabs_object["Data"] = result.get("value") dbot_score["type"] = "url" url_entries.append((context_object, phishlabs_object)) elif indicator_type == "Domain": context_object = create_domain_context(result, classification) phishlabs_object["Name"] = result.get("value") dbot_score["type"] = "domain" domain_entries.append((context_object, phishlabs_object)) elif indicator_type == "Attachment": file_md5, file_name, file_type = get_file_properties(result) context_object = {"Name": file_name, "Type": file_type, "MD5": file_md5} phishlabs_object["Name"] = file_name phishlabs_object["Type"] = file_type phishlabs_object["MD5"] = file_md5 file_entries.append((context_object, phishlabs_object)) dbot_score["type"] = "file" elif indicator_type == "E-mail": email_body, email_to, email_from = get_email_properties(result) context_object = {"To": email_to, "From": email_from, "Body": email_body, "Subject": result.get("value")} phishlabs_object["To"] = (email_to,) phishlabs_object["From"] = (email_from,) phishlabs_object["Body"] = email_body phishlabs_object["Subject"] = result.get("value") email_entries.append((context_object, phishlabs_object)) if indicator_type != "E-mail": # We do not know what we have for an email dbot_scores.append(dbot_score) context = populate_context(dbot_scores, domain_entries, file_entries, url_entries, email_entries) else: human_readable = "Incident not found" else: human_readable = "No incidents found" return_outputs(human_readable, context, feed) @logger def get_feed_request( client: Client, since: str = None, limit: str = None, indicator: list = None, remove_protocol: str = None, remove_query: str = None, offset: str = None, sort: bool = False, ) -> dict: """ Sends a request to PhishLabs user feed with the provided arguments :param client: The client with the http request :param since: Data updated within this duration of time from now :param limit: Limit the number of rows to return :param indicator: Indicator type filter :param remove_protocol: Removes the protocol part from indicators when the rule can be applied. :param remove_query: Removes the query string part from indicators when the rules can be applied. :param offset: Number of incidents to skip :param sort: If true, the incidents will be sorted by their creation time in ascending order. :return: User feed """ path: str = "feed" params: dict = {} if since: params["since"] = since if limit: params["limit"] = int(limit) if offset: params["offset"] = int(offset) if indicator: params["indicator"] = indicator if remove_query: params["remove_query"] = remove_query if remove_protocol: params["remove_protocol"] = remove_protocol if sort: params["sort"] = "created_at" params["direction"] = "asc" response = client.http_request("GET", path, params) return response def get_sec_time_delta(last_fetch_time): # try in UTC first fetch_delta = datetime.utcnow() - last_fetch_time fetch_delta_in_sec = fetch_delta.seconds # if negative then try in current time if fetch_delta_in_sec < 0: fetch_delta = datetime.now() - last_fetch_time fetch_delta_in_sec = fetch_delta.seconds # if negative default to 1 day if fetch_delta_in_sec < 0: fetch_delta_in_sec = SEC_IN_DAY return str(fetch_delta_in_sec) + "s" def fetch_incidents(client: Client, fetch_time, fetch_limit): """ Fetches incidents from the PhishLabs user feed. :return: Demisto incidents """ last_run: dict = demisto.getLastRun() last_fetch: str = last_run.get("time", "") if last_run else "" last_fetch_time: datetime = ( datetime.strptime(last_fetch, "%Y-%m-%dT%H:%M:%SZ") if last_fetch else datetime.strptime(NONE_DATE, "%Y-%m-%dT%H:%M:%SZ") ) incidents: list = [] count: int = 1 limit = int(fetch_limit) if not last_fetch: feed: dict = get_feed_request(client, since=fetch_time) else: feed = get_feed_request(client, since=get_sec_time_delta(last_fetch_time)) max_time: datetime = last_fetch_time results: list = feed.get("data", []) if feed else [] if results: results = sorted(results, key=lambda r: datetime.strptime(r.get("createdAt", NONE_DATE), "%Y-%m-%dT%H:%M:%SZ")) if not isinstance(results, list): results = [results] for result in results: if count > limit: break incident_time: datetime = datetime.strptime(result.get("createdAt", NONE_DATE), "%Y-%m-%dT%H:%M:%SZ") if last_fetch_time and incident_time <= last_fetch_time: continue incident: dict = { "name": "PhishLabs IOC Incident " + result.get("referenceId", ""), "occurred": datetime.strftime(incident_time, "%Y-%m-%dT%H:%M:%SZ"), "rawJSON": json.dumps(result), } incidents.append(incident) if max_time < incident_time: max_time = incident_time count += 1 demisto.setLastRun({"time": datetime.strftime(max_time, "%Y-%m-%dT%H:%M:%SZ")}) demisto.incidents(incidents) """ MAIN""" def main(): """ Main function """ params = demisto.params() server: str = params.get("url")[:-1] if (params.get("url") and params.get("url").endswith("/")) else params.get("url") reliability = demisto.params().get("integrationReliability") reliability = reliability if reliability else DBotScoreReliability.B if DBotScoreReliability.is_valid_type(reliability): reliability = DBotScoreReliability.get_dbot_score_reliability_from_str(reliability) else: raise Exception("Please provide a valid value for the Source Reliability parameter.") client = Client( base_url=f"{server!s}/api/v1/", user_name=params.get("credentials", {}).get("identifier"), password=params.get("credentials", {}).get("password"), use_ssl=not params.get("insecure", False), reliability=reliability, ) global RAISE_EXCEPTION_ON_ERROR LOG(f"Command being called is {demisto.command()}") handle_proxy() command_dict = { "test-module": test_module, "fetch-incidents": fetch_incidents, "phishlabs-global-feed": get_global_feed_command, "phishlabs-get-incident-indicators": get_incident_indicators_command, } try: command_func: Callable = command_dict[demisto.command()] # type:ignore[assignment] if demisto.command() == "fetch-incidents": RAISE_EXCEPTION_ON_ERROR = True command_func(client, params.get("fetch_time", "").strip(), params.get("fetch_limit", "10")) else: command_func(client) except Exception as e: if RAISE_EXCEPTION_ON_ERROR: LOG(str(e)) LOG.print_log() raise else: return_error(str(e)) if __name__ in ["__main__", "__builtin__", "builtins"]: main()