Details
| ID | Synapse |
|---|---|
| Provider | Synapse Financial Technologies |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.0.0 |
| Docker Image | demisto/py3-tools:1.0.0.117220 |
| Supported Modules | Agentix XSIAM |
README
Synapse intelligence analysis platform.
This integration was integrated and tested with version 2.7.0 of Synapse
Configure Synapse in Cortex
| Parameter | Description | Required |
|---|---|---|
| url | Server URL (e.g. https://synapse.vertex.link) | True |
| port | REST API Port (default is 4443). | True |
| credentials | Username and password to user to authenticate to Synapse. | True |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | False |
| timezone | Timezone (optional) | False |
| bad_tag | Malicious Tag | False |
| good_tag | Benign Tag | False |
| use_optic | Synapse is running Optic | 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.
ip
Returns IP information and reputation.
Base Command
ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | List of IPs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.IP.ip | String | The IP address of the indicator. |
| Synapse.IP.tags | String | The tags applied to the IP address. |
| DBotScore.Indicator | String | The value assigned by DBot for the indicator. |
| DBotScore.Type | String | The type assigned by DBot for the indicator. |
| DBotScore.Score | Number | The score assigned by DBot for the indicator. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| IP.Address | string | The IP address of the indicator. |
| IP.Malicious.Vendor | string | For malicious IP addresses, the vendor that made the decision. |
| IP.Malicious.Description | string | For malicious IP addresses, the full description. |
Command Example
!ip ip="1.2.3.4"
Context Example
{
"DBotScore": {
"Indicator": "1.2.3.4",
"Score": 3,
"Type": "ip",
"Vendor": "Synapse"
},
"IP": {
"Address": "1.2.3.4",
"Malicious": {
"Description": "Synapse returned reputation tag: mal",
"Vendor": "Synapse"
}
},
"Synapse": {
"IP": {
"ip": "1.2.3.4",
"tags": [
"mal",
"test"
]
}
}
}
Human Readable Output
IP List
ip tags 1.2.3.4 mal,
test
url
Returns URL information and reputation.
Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.
Base Command
url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | List of URLs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.URL.url | String | The data of the URL indicator. |
| Synapse.URL.tags | String | The tags applied to the url. |
| DBotScore.Indicator | String | The value assigned by DBot for the indicator. |
| DBotScore.Type | String | The type assigned by DBot for the indicator. |
| DBotScore.Score | Number | The score assigned by DBot for the indicator. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| URL.Data | string | The data of the URL indicator. |
| URL.Malicious.Vendor | string | For malicious URLs, the vendor that made the decision. |
| URL.Malicious.Description | string | For malicious URLs, the full description. |
Command Example
!url url="https://google.com"
Context Example
{
"DBotScore": {
"Indicator": "https://google.com",
"Score": 0,
"Type": "url",
"Vendor": "Synapse"
},
"Synapse": {
"URL": {
"tags": [],
"url": "https://google.com"
}
},
"URL": {
"Data": "https://google.com"
}
}
Human Readable Output
URL List
tags url https://google.com
domain
Returns Domain information and reputation.
Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.
Base Command
domain
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | List of Domains. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Domain.domain | String | The fully qualified domain name. |
| Synapse.Domain.tags | String | The tags applied to the domain. |
| DBotScore.Indicator | String | The value assigned by DBot for the indicator. |
| DBotScore.Type | String | The type assigned by DBot for the indicator. |
| DBotScore.Score | Number | The score assigned by DBot for the indicator. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| Domain.Name | string | The name of the domain. |
| Domain.Malicious.Vendor | string | For malicious domains, the vendor that made the decision. |
| Domain.Malicious.Description | string | For malicious domains, the full description. |
Command Example
!domain domain="foobar.com"
Context Example
{
"DBotScore": {
"Indicator": "foobar.com",
"Score": 3,
"Type": "domain",
"Vendor": "Synapse"
},
"Domain": {
"Malicious": {
"Description": "Synapse returned reputation tag: mal",
"Vendor": "Synapse"
},
"Name": "foobar.com"
},
"Synapse": {
"Domain": {
"domain": "foobar.com",
"tags": [
"mal"
]
}
}
}
Human Readable Output
Domain List
domain tags foobar.com mal
file
Returns File information and reputation.
Base Command
file
Input
| Argument Name | Description | Required |
|---|---|---|
| file | List of File Hashes (accepts MD5, SHA1, SHA256, SHA512). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.File.hash | String | The queried file hash. |
| Synapse.File.MD5 | String | The MD5 hash of the file. |
| Synapse.File.SHA1 | String | The SHA1 hash of the file. |
| Synapse.File.SHA256 | String | The SHA256 hash of the file. |
| Synapse.File.SHA512 | String | The SHA256 hash of the file. |
| Synapse.File.query | String | The formatted query in storm syntax. |
| File.MD5 | String | The MD5 hash of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Malicious.Vendor | String | For malicious files, the vendor that made the decision. |
| File.Malicious.Description | String | For malicious files, the full description. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!file file="9e0c442ee3157d3f3aa2be30a1d24d81"
Context Example
{
"DBotScore": {
"Indicator": "9e0c442ee3157d3f3aa2be30a1d24d81",
"Score": 3,
"Type": "file",
"Vendor": "Synapse"
},
"File": {
"MD5": "9e0c442ee3157d3f3aa2be30a1d24d81",
"Malicious": {
"Description": "Synapse returned reputation tag: mal",
"Vendor": "Synapse"
},
"SHA1": "e7b03ed4dbdfb79477c49942d5796d3dfc78ac7e",
"SHA256": "290f64a315850c5bccc907f79cbeabd79345719df738ee5d02dc3447d04675b3",
"SHA512": "53e6baa124f54462786f1122e98e38ff1be3de82fe2a96b1849a8637043fd847eec7e0f53307bddf7a066565292d500c36c941f1f3bb9dcac807b2f4a0bfce1b"
},
"Synapse": {
"File": {
"MD5": "9e0c442ee3157d3f3aa2be30a1d24d81",
"SHA1": "e7b03ed4dbdfb79477c49942d5796d3dfc78ac7e",
"SHA256": "290f64a315850c5bccc907f79cbeabd79345719df738ee5d02dc3447d04675b3",
"SHA512": "53e6baa124f54462786f1122e98e38ff1be3de82fe2a96b1849a8637043fd847eec7e0f53307bddf7a066565292d500c36c941f1f3bb9dcac807b2f4a0bfce1b",
"hash": "9e0c442ee3157d3f3aa2be30a1d24d81",
"query": "file:bytes:md5=9e0c442ee3157d3f3aa2be30a1d24d81",
"tags": [
"mal"
]
}
}
}
Human Readable Output
File List
MD5 SHA1 SHA256 SHA512 hash query tags 9e0c442ee3157d3f3aa2be30a1d24d81 e7b03ed4dbdfb79477c49942d5796d3dfc78ac7e 290f64a315850c5bccc907f79cbeabd79345719df738ee5d02dc3447d04675b3 53e6baa124f54462786f1122e98e38ff1be3de82fe2a96b1849a8637043fd847eec7e0f53307bddf7a066565292d500c36c941f1f3bb9dcac807b2f4a0bfce1b 9e0c442ee3157d3f3aa2be30a1d24d81 file:bytes:md5=9e0c442ee3157d3f3aa2be30a1d24d81 mal
synapse-storm-query
Execute a Synapse Storm query.
Base Command
synapse-storm-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Synapse storm query (i.e. “inet:ipv4=1.2.3.4”) | Required |
| limit | Limit the number of results returned. Default is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Nodes.created | String | Timestamp when the node was first created in the Synapse Cortex. |
| Synapse.Nodes.form | String | The type of node (i.e. “inet:ipv4” for an IP address). |
| Synapse.Nodes.tags | String | The tags associated with the resulting node. |
| Synapse.Nodes.valu | String | The node primary value (i.e. “1.2.3.4” for an IP). |
Command Example
!synapse-storm-query query="inet:ipv4=1.2.3.5" limit=1
Context Example
{
"Synapse": {
"Nodes": {
"created": "2020/09/12 10:07:17 EDT",
"form": "inet:ipv4",
"tags": [
"test.foo",
"test.testing"
],
"valu": "1.2.3.5"
}
}
}
Human Readable Output
Synapse Query Results:
inet:ipv4=1.2.3.5
form valu created tags inet:ipv4 1.2.3.5 2020/09/12 10:07:17 EDT test.foo,
test.testing
Synapse Node Properties
.created type 1599919637048 unicast
synapse-list-users
Lists current users in Synapse Cortex.
Base Command
synapse-list-users
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Users.Admin | Boolean | True/False whether the Synapse user is an admin. |
| Synapse.Users.Email | String | The email address of the Synapse user. |
| Synapse.Users.Iden | String | The unique identifier of the Synapse user. |
| Synapse.Users.Name | String | The user’s Synapse username. |
| Synapse.Users.Roles | String | The roles applied to the Synapse user. |
| Synapse.Users.Rules | String | The rules applied to the Synapse user. |
Command Example
#### Context Example
```json
{
"Synapse": {
"Users": [
{
"Admin": true,
"Email": null,
"Iden": "9e4fe25a281f3f65aff2fa192d54c705",
"Name": "root",
"Roles": [],
"Rules": []
},
{
"Admin": false,
"Email": null,
"Iden": "a2bfead4c16b0354af2a92aa05588fc9",
"Name": "testuser",
"Roles": [
"xsoar-role",
"all"
],
"Rules": []
},
{
"Admin": false,
"Email": null,
"Iden": "eec037c730f0976a1b742b9f9773a52e",
"Name": "xsoartesting",
"Roles": [
"all"
],
"Rules": []
}
]
}
}
Human Readable Output
Synapse Users
Name Admin Rules Roles root true testuser false xsoar-role,
allxsoartesting false all
synapse-list-roles
Lists current roles in Synapse Cortex.
Base Command
synapse-list-roles
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Roles.Iden | String | The unique identifier of the Synapse Role. |
| Synapse.Roles.Name | String | The name of the Synapse Role. |
| Synapse.Roles.Rules | String | The rules applied to the Synapse Role. |
Command Example
#### Context Example
```json
{
"Synapse": {
"Roles": [
{
"Iden": "bcf176a4cbe240ae1dcf9fbebdffa680",
"Name": "xsoar-role",
"Rules": []
},
{
"Iden": "c486fa9eb8d50a8c35a60687f12dc4c9",
"Name": "all",
"Rules": []
},
{
"Iden": "e7e6ee238bc5bceeff96d10f100142ae",
"Name": "xsoartestingrole",
"Rules": []
}
]
}
}
Human Readable Output
Synapse Roles
Name Iden Rules xsoar-role bcf176a4cbe240ae1dcf9fbebdffa680 all c486fa9eb8d50a8c35a60687f12dc4c9 xsoartestingrole e7e6ee238bc5bceeff96d10f100142ae
synapse-create-user
Create a new Synapse user.
Base Command
synapse-create-user
Input
| Argument Name | Description | Required |
|---|---|---|
| username | New username to be created. | Required |
| password | Optionally set the new user’s password. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Users.Admin | Boolean | True/False whether the Synapse user is an admin. |
| Synapse.Users.Email | String | The email address of the Synapse user. |
| Synapse.Users.Iden | String | The unique identifier of the Synapse user. |
| Synapse.Users.Name | String | The user’s Synapse username. |
| Synapse.Users.Roles | String | The roles applied to the Synapse user. |
| Synapse.Users.Rules | String | The rules applied to the Synapse user. |
Command Example
!synapse-create-user username="xsoardemo" password="secret"
Context Example
{
"Synapse": {
"Users": {
"Admin": false,
"Email": null,
"Iden": "f1ac5126df0e7407a0804fc6bd41534d",
"Name": "xsoardemo",
"Roles": [
"all"
],
"Rules": []
}
}
}
Human Readable Output
Synapse New User
Name Admin Rules Roles xsoardemo false all
synapse-create-role
Create a new Synapse role.
Base Command
synapse-create-role
Input
| Argument Name | Description | Required |
|---|---|---|
| role | New role to create in Synapse. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Roles.Iden | String | The unique identifier of the Synapse Role. |
| Synapse.Roles.Name | String | The name of the Synapse Role. |
| Synapse.Roles.Rules | String | The rules applied to the Synapse Role. |
Command Example
!synapse-create-role role="xsoar-role-demo"
Context Example
{
"Synapse": {
"Roles": {
"Iden": "029019964000fef6ccd2be428f496423",
"Name": "xsoar-role-demo",
"Rules": []
}
}
}
Human Readable Output
Synapse New Role
Name Iden Rules xsoar-role-demo 029019964000fef6ccd2be428f496423
synapse-grant-user-role
Grants a user access to role based perrmissions.
Base Command
synapse-grant-user-role
Input
| Argument Name | Description | Required |
|---|---|---|
| user | User’s “iden” property - not the username. | Required |
| role | Role’s “iden” property - not the name of the role. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Users.Admin | Boolean | True/False whether the Synapse user is an admin. |
| Synapse.Users.Email | String | The email address of the Synapse user. |
| Synapse.Users.Iden | String | The unique identifier of the Synapse user. |
| Synapse.Users.Name | String | The user’s Synapse username. |
| Synapse.Users.Roles | String | The roles applied to the Synapse user. |
| Synapse.Users.Rules | String | The rules applied to the Synapse user. |
Command Example
!synapse-grant-user-role user="a2bfead4c16b0354af2a92aa05588fc9" role="bcf176a4cbe240ae1dcf9fbebdffa680"
Context Example
{
"Synapse": {
"Users": {
"Admin": false,
"Email": null,
"Iden": "a2bfead4c16b0354af2a92aa05588fc9",
"Name": "testuser",
"Roles": [
"xsoar-role",
"all"
],
"Rules": []
}
}
}
Human Readable Output
Synapse New User Role
Name Admin Rules Roles testuser false xsoar-role,
all
synapse-query-model
Query the Synapse data model and return details for given type or form (i.e. “inet:ipv4” for an IPv4 IP address).
Base Command
synapse-query-model
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Type/Form query (i.e. “inet:ipv4” or “inet”fqdn”) | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Synapse.Model.Doc | String | The docstring associated with the particular Synapse model element. |
| Synapse.Model.Example | String | An example of the given Synapse element. |
| Synapse.Model.Form | String | A form is the definition of an object in the Synapse data model (node). |
| Synapse.Model.Properties | String | The unique properties associated with the given Synapse object. |
| Synapse.Model.Type | String | A Type is the definition of a data element within the data model. |
| Synapse.Model.Valu | String | The given value of the Synapse object type. |
Command Example
!synapse-query-model query="file:bytes"
Context Example
{
"Synapse": {
"Model": {
"Doc": "The file bytes type with SHA256 based primary property.",
"Example": "N/A",
"Form": "file:bytes",
"Properties": {
".created": "The time the node was created in the cortex.",
".seen": "The time interval for first/last observation of the node.",
"md5": "The md5 hash of the file.",
"mime": "The \"best\" mime type name for the file.",
"mime:pe:compiled": "The compile time of the file according to the PE header.",
"mime:pe:exports:libname": "The export library name according to the PE.",
"mime:pe:exports:time": "The export time of the file according to the PE.",
"mime:pe:imphash": "The PE import hash of the file as calculated by pefile; https://github.com/erocarrera/pefile .",
"mime:pe:pdbpath": "The PDB string according to the PE.",
"mime:pe:richhdr": "The sha256 hash of the rich header bytes.",
"mime:pe:size": "The size of the executable file according to the PE file header.",
"mime:x509:cn": "The Common Name (CN) attribute of the x509 Subject.",
"name": "The best known base name for the file.",
"sha1": "The sha1 hash of the file.",
"sha256": "The sha256 hash of the file.",
"sha512": "The sha512 hash of the file.",
"size": "The file size in bytes."
},
"Type": "file:bytes",
"Valu": "file:bytes"
}
}
}
Human Readable Output
Synapse Model Type
Type Doc Example file:bytes The file bytes type with SHA256 based primary property. N/A
Synapse
file:bytesForm Properties
.seen .created size md5 sha1 sha256 sha512 name mime mime:x509:cn mime:pe:size mime:pe:imphash mime:pe:compiled mime:pe:pdbpath mime:pe:exports:time mime:pe:exports:libname mime:pe:richhdr The time interval for first/last observation of the node. The time the node was created in the cortex. The file size in bytes. The md5 hash of the file. The sha1 hash of the file. The sha256 hash of the file. The sha512 hash of the file. The best known base name for the file. The “best” mime type name for the file. The Common Name (CN) attribute of the x509 Subject. The size of the executable file according to the PE file header. The PE import hash of the file as calculated by pefile; https://github.com/erocarrera/pefile . The compile time of the file according to the PE header. The PDB string according to the PE. The export time of the file according to the PE. The export library name according to the PE. The sha256 hash of the rich header bytes.
Configuration parameters
url— Server URL (e.g. https://synapse.vertex.link) (required)port— REST API Port (default is 4443). (required)credentials— Username and password to user to authenticate to Synapse. (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingstimezone— Timezone (optional)bad_tag— Malicious Taggood_tag— Benign Taguse_optic— Use Synapse OpticintegrationReliability— Source ReliabilityfeedExpirationPolicy—feedExpirationInterval—
Commands (11)
-
domainReturns Domain information and reputation.
-
fileReturns File information and reputation.
-
ipReturns IP information and reputation.
-
synapse-create-roleCreate a new Synapse role.
-
synapse-create-userCreate a new Synapse user.
-
synapse-grant-user-roleGrants a user access to role based perrmissions.
-
synapse-list-rolesLists current roles in Synapse Cortex.
-
synapse-list-usersLists current users in Synapse Cortex.
-
synapse-query-modelQuery the Synapse data model and return details for given type or form (i.e. "inet:ipv4" for an IPv4 IP address).
-
synapse-storm-queryExecute a Synapse Storm query.
-
urlReturns URL information and reputation.
import asyncio import ipaddress import json import aiohttp import pytz import urllib3 from aiohttp import TCPConnector from CommonServerPython import * # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y/%m/%d %H:%M:%S %Z" TIMEZONE = demisto.params().get("timezone", None) """ CLIENT """ class Client(BaseClient): """ Client class to interact with the service API """ def __init__(self, base_url, username, password, proxy, **kwargs): self._base_url = base_url self.username = username self.password = password self.proxy = proxy self.auth = (username, password) self.aio_auth = aiohttp.BasicAuth(username, password) super().__init__(base_url, **kwargs) def _check_for_error(self, resp): """ Checks for custom synapse error message in JSON response. Raises error if so. """ if resp.get("status") != "ok": code = resp.get("code") mesg = resp.get("mesg") raise Exception(f"Synapse API Error: ({code}): {mesg}") def login(self): """ Login to Synapse and validate credentials. """ resp = self._http_request(method="POST", url_suffix="/login", json_data={"user": self.username, "passwd": self.password}) self._check_for_error(resp) return resp def add_user(self, new_user, new_passwd): """ Adds a new user to Synapse with given password. """ resp = self._http_request( method="POST", url_suffix="/auth/adduser", json_data={"name": new_user, "passwd": new_passwd}, auth=self.auth ) self._check_for_error(resp) return resp def add_role(self, new_role): """ Adds a new user role to Synapse. """ resp = self._http_request(method="POST", url_suffix="/auth/addrole", json_data={"name": new_role}, auth=self.auth) self._check_for_error(resp) return resp def grant_role(self, user, role): """ Adds a new user role to Synapse. """ resp = self._http_request(method="POST", url_suffix="/auth/grant", json_data={"user": user, "role": role}, auth=self.auth) self._check_for_error(resp) return resp def list_users(self): """ Collects all users in Synapse. """ resp = self._http_request(method="GET", url_suffix="/auth/users", auth=self.auth) self._check_for_error(resp) return resp def list_roles(self): """ Collects all user roles in Synapse. """ resp = self._http_request(method="GET", url_suffix="/auth/roles", auth=self.auth) self._check_for_error(resp) return resp def get_model(self): """ Queries and returns full model json. """ resp = self._http_request(method="GET", url_suffix="/model", auth=self.auth) self._check_for_error(resp) return resp async def synapse_get_nodes(self, data, limit=100): """ Async function to query for Synapse nodes via storm. """ my_query = f"{data} | limit {limit}" nodes = [] # Nodes to return async with aiohttp.ClientSession(connector=TCPConnector(ssl=self._verify), trust_env=self.proxy) as sess: address = urljoin(self._base_url, "/storm") query = {"query": my_query} async with sess.get(address, json=query, auth=self.aio_auth) as resp: async for byts, _x in resp.content.iter_chunks(): if not byts: break mesg = json.loads(byts) if mesg[0] == "node": nodes.append(mesg[1]) return nodes """ HELPER FUNCTIONS """ def validate_timezone_helper(TIMEZONE): """ Validates Timezone format is correct before assuming it is. """ if TIMEZONE not in pytz.all_timezones: return_error(f'Error: Timezone format "{TIMEZONE}" invalid') else: tz = pytz.timezone(TIMEZONE) return tz # pylint: disable=E0606 def convert_raw_into_nodes_helper(results): """ Accepts raw node json and returns formatted list of dicts (nodes). """ nodes = [] for item in results: t_stamp = convert_epoch_timestamp_helper(item[1]["props"].get(".created")) node = {"form": item[0][0], "created": t_stamp, "tags": get_full_tags_helper(item[1].get("tags"))} if item[0][0] == "inet:ipv4": node["valu"] = ipaddress.ip_address(item[0][1]).__str__() else: node["valu"] = item[0][1] nodes.append(node) return nodes def get_full_tags_helper(data): """ Accepts raw REST Response for tags key and returns list of longest heirarchical tags. """ tags = [] temp_tags = list(data.keys()) if temp_tags: tags.append(temp_tags.pop(0)) else: return tags for _i in range(len(temp_tags)): if temp_tags: temp = temp_tags.pop(0) else: break for tag in tags: if temp in tag: continue elif tag in temp: tags.remove(tag) tags.append(temp) else: tags.append(temp) return tags def convert_epoch_timestamp_helper(timestamp): """ Accepts Epoch timestamp and localizes to UTC (timestamps per Synapse are all in UTC). """ raw_date = datetime.fromtimestamp(float(timestamp) / 1000.0) utc_date = raw_date.replace(tzinfo=pytz.UTC) if TIMEZONE: tz = validate_timezone_helper(TIMEZONE) current_date = utc_date.astimezone(tz) return current_date.strftime(DATE_FORMAT) else: return utc_date.strftime(DATE_FORMAT) def model_query_helper(model, query): """ Accepts model (full json) and a node type (str) to query. Returns properties for given node type. Raises error not found if not present. """ parsed_data = {"query": query} mod_types = list(model["types"].keys()) mod_forms = list(model["forms"].keys()) if (query not in mod_types) and (query not in mod_forms): raise Exception(f'Error: Query "{query}" not found in model. Try adjusting syntax (i.e. "inet:ipv4").') parsed_data["type"] = model["types"].get(query) if query in mod_forms: parsed_data["form"] = model["forms"].get(query) return parsed_data def model_query_properties_helper(form): """ Accepts JSON of cortex form and returns formatted properties. """ data = {} for prop, valu in form.get("props").items(): data[prop] = valu.get("doc", "N/A") return data def user_roles_helper(client, roles): """ Accepts list of role identifiers and converts them to named roles. Return list of names. """ data = client.list_roles() named_roles = [] for role in data.get("result"): if role.get("iden") in roles: named_roles.append(role.get("name")) return named_roles def user_rules_helper(rules): """ Accepts REST rules response and returns list of joined rules. """ new_rules = [] if not rules: return rules for rule in rules: if rule[0]: new_rules.append(".".join(rule[1])) return new_rules def file_regex_helper(hash): """ Accepts a raw hash and regex matches to determine what type. """ if re.match(md5Regex, hash): file_query = f"file:bytes:md5={hash}" elif re.match(sha1Regex, hash): file_query = f"file:bytes:sha1={hash}" elif re.match(sha256Regex, hash): file_query = f"file:bytes:sha256={hash}" elif re.match(sha512Regex, hash): file_query = f"file:bytes:sha512={hash}" else: raise ValueError(f'Value "{hash}" is not a valid File Hash.') return file_query def file_context_builder_helper(file, data): """ Accepts hash and parsed query response. Adds MD5, SHA1, SHA256, SHA512. """ file_context = {"hash": file, "tags": get_full_tags_helper(data[0][1].get("tags"))} if data[0][1]["props"].get("md5"): file_context["MD5"] = data[0][1]["props"].get("md5") if data[0][1]["props"].get("sha1"): file_context["SHA1"] = data[0][1]["props"].get("sha1") if data[0][1]["props"].get("sha256"): file_context["SHA256"] = data[0][1]["props"].get("sha256") if data[0][1]["props"].get("sha512"): file_context["SHA512"] = data[0][1]["props"].get("sha512") return file_context """ COMMAND FUNCTIONS """ def test_module(client): """ Tests API connectivity and authentication' """ client.login() return "ok" def ip_reputation_command(client, args, good_tag, bad_tag) -> List[CommandResults]: """ Returns IP Reputation for a list of IPs. """ ips = argToList(args.get("ip")) if len(ips) == 0: raise ValueError("IP(s) not specified") for ip in ips: if not re.match(ipv4Regex, ip): raise ValueError(f'Value "{ip}" is not a valid IP address.') command_results: List[CommandResults] = [] for ip in ips: query = f"inet:ipv4={ip}" data = asyncio.run(client.synapse_get_nodes(query)) if not data: continue ip_data = {"ip": ip, "tags": get_full_tags_helper(data[0][1].get("tags"))} score = Common.DBotScore.NONE # unknown reputation = {"tag": "N/A"} if bad_tag in ip_data["tags"]: score = Common.DBotScore.BAD # bad reputation["tag"] = bad_tag elif good_tag in ip_data["tags"]: score = Common.DBotScore.GOOD # good reputation["tag"] = good_tag # Create the DBotScore structure first using the Common.DBotScore class. dbot_score = Common.DBotScore( indicator=ip, indicator_type=DBotScoreType.IP, integration_name="Synapse", score=score, malicious_description=f'Synapse returned reputation tag: {reputation["tag"]}', reliability=demisto.params().get("integrationReliability"), ) # Create the IP Standard Context structure using Common.IP and add # dbot_score to it. ip_standard_context = Common.IP(ip=ip, dbot_score=dbot_score) command_results.append( CommandResults( readable_output=tableToMarkdown("IP Details", ip_data), outputs_prefix="Synapse.IP", outputs_key_field="ip", outputs=ip_data, indicator=ip_standard_context, ) ) return command_results def domain_reputation_command(client, args, good_tag, bad_tag) -> List[CommandResults]: """ Returns Domain Reputation for a list of Domains. """ domains = argToList(args.get("domain")) if len(domains) == 0: raise ValueError("Domain(s) not specified") command_results: List[CommandResults] = [] for domain in domains: query = f"inet:fqdn={domain}" data = asyncio.run(client.synapse_get_nodes(query)) if not data: continue domain_data = {"domain": domain, "tags": get_full_tags_helper(data[0][1].get("tags"))} score = Common.DBotScore.NONE # unknown reputation = {"tag": "N/A"} if bad_tag in domain_data["tags"]: score = Common.DBotScore.BAD # bad reputation["tag"] = bad_tag elif good_tag in domain_data["tags"]: score = Common.DBotScore.GOOD # good reputation["tag"] = good_tag # Create the DBotScore structure first using the Common.DBotScore class. dbot_score = Common.DBotScore( indicator=domain, indicator_type=DBotScoreType.DOMAIN, integration_name="Synapse", score=score, malicious_description=f'Synapse returned reputation tag: {reputation["tag"]}', reliability=demisto.params().get("integrationReliability"), ) # Create the Domain Standard Context structure using Common.Domain and add # dbot_score to it. domain_standard_context = Common.Domain(domain=domain, dbot_score=dbot_score) command_results.append( CommandResults( readable_output=tableToMarkdown("Domain Details", domain_data), outputs_prefix="Synapse.Domain", outputs_key_field="domain", outputs=domain_data, indicator=domain_standard_context, ) ) return command_results def url_reputation_command(client, args, good_tag, bad_tag) -> List[CommandResults]: """ Returns URL Reputation for a list of URLs. """ urls = argToList(args.get("url")) if len(urls) == 0: raise ValueError("URL(s) not specified") for url in urls: if not re.match(urlRegex, url): raise ValueError(f'Value "{url}" is not a valid URL address.') command_results: List[CommandResults] = [] for url in urls: query = f"inet:url={url}" data = asyncio.run(client.synapse_get_nodes(query)) if not data: continue url_data = {"url": url, "tags": get_full_tags_helper(data[0][1].get("tags"))} score = Common.DBotScore.NONE # unknown reputation = {"tag": "N/A"} if bad_tag in url_data["tags"]: score = Common.DBotScore.BAD # bad reputation["tag"] = bad_tag elif good_tag in url_data["tags"]: score = Common.DBotScore.GOOD # good reputation["tag"] = good_tag # Create the DBotScore structure first using the Common.DBotScore class. dbot_score = Common.DBotScore( indicator=url, indicator_type=DBotScoreType.URL, integration_name="Synapse", score=score, malicious_description=f'Synapse returned reputation tag: {reputation["tag"]}', reliability=demisto.params().get("integrationReliability"), ) # Create the URL Standard Context structure using Common.URL and add # dbot_score to it. url_standard_context = Common.URL(url=url, dbot_score=dbot_score) command_results.append( CommandResults( readable_output=tableToMarkdown("URL Details", url_data), outputs_prefix="Synapse.URL", outputs_key_field="url", outputs=url_data, indicator=url_standard_context, ) ) return command_results def file_reputation_command(client, args, good_tag, bad_tag) -> List[CommandResults]: """ Returns File Reputation for a list of hashes (MD5, SHA1, or SHA256). """ files = argToList(args.get("file")) if len(files) == 0: raise ValueError("File(s) not specified") command_results: List[CommandResults] = [] for file in files: file_query = file_regex_helper(file) data = asyncio.run(client.synapse_get_nodes(file_query)) if not data: continue file_data = file_context_builder_helper(file, data) file_data["query"] = file_query score = Common.DBotScore.NONE # unknown reputation = {"tag": "N/A"} if bad_tag in file_data["tags"]: score = Common.DBotScore.BAD # bad reputation["tag"] = bad_tag elif good_tag in file_data["tags"]: score = Common.DBotScore.GOOD # good reputation["tag"] = good_tag # Create the DBotScore structure first using the Common.DBotScore class. dbot_score = Common.DBotScore( indicator=file, indicator_type=DBotScoreType.FILE, integration_name="Synapse", score=score, malicious_description=f'Synapse returned reputation tag: {reputation["tag"]}', reliability=demisto.params().get("integrationReliability"), ) # Create the File Standard Context structure using Common.File and add # dbot_score to it. file_standard_context = Common.File( md5=file_data.get("MD5"), sha1=file_data.get("SHA1"), sha256=file_data.get("SHA256"), sha512=file_data.get("SHA512"), dbot_score=dbot_score, ) command_results.append( CommandResults( readable_output=tableToMarkdown("File Details", file_data), outputs_prefix="Synapse.File", outputs_key_field="hash", outputs=file_data, indicator=file_standard_context, ) ) return command_results def storm_query_command(client, args): """ Executes a storm query and expects nodes in response. """ try: data = asyncio.run(client.synapse_get_nodes(args.get("query"), args.get("limit"))) except DemistoException as e: if "AuthDeny" in str(e): return "Authorization Error: make sure credentials are correct set" else: raise e else: nodes = convert_raw_into_nodes_helper(data) name = f'Synapse Query Results: `{args.get("query")}`' headers = ["form", "valu", "created", "tags"] readable_output = tableToMarkdown(name, nodes, headers=headers, removeNull=True) if len(nodes) == 1: name_single = "Synapse Node Properties" headers_single = list(data[0][1]["props"].keys()) readable_output += tableToMarkdown(name_single, data[0][1]["props"], headers=headers_single, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Nodes", outputs_key_field="valu", outputs=nodes, readable_output=readable_output, raw_response=data, ) return results def list_users_command(client): """ Executes API call for list users and returns response. """ data = client.list_users() users = [] for user in data.get("result"): my_user = { "Name": user.get("name"), "Email": user.get("email"), "Admin": user.get("admin"), "Iden": user.get("iden"), "Rules": user_rules_helper(user.get("rules")), "Roles": user_roles_helper(client, user.get("roles")), } users.append(my_user) name = "Synapse Users" headers = ["Name", "Email", "Admin", "Rules", "Roles"] readable_output = tableToMarkdown(name, users, headers=headers, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Users", outputs_key_field="Iden", outputs=users, readable_output=readable_output, raw_response=data, ) return results def list_roles_command(client): """ Executes API call for list roles and returns response. """ data = client.list_roles() roles = [] for role in data.get("result"): my_role = {"Name": role.get("name"), "Iden": role.get("iden"), "Rules": user_rules_helper(role.get("rules"))} roles.append(my_role) name = "Synapse Roles" headers = ["Name", "Iden", "Rules"] readable_output = tableToMarkdown(name, roles, headers=headers, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Roles", outputs_key_field="Iden", outputs=roles, readable_output=readable_output, raw_response=data, ) return results def add_user_command(client, args): """ Add a user to Synapse. Accepts username and password. """ data = client.add_user(args.get("username"), args.get("password")) user = data.get("result") my_user = { "Name": user.get("name"), "Email": user.get("email"), "Admin": user.get("admin"), "Iden": user.get("iden"), "Rules": user_rules_helper(user.get("rules")), "Roles": user_roles_helper(client, user.get("roles")), } name = "Synapse New User" headers = ["Name", "Email", "Admin", "Rules", "Roles"] readable_output = tableToMarkdown(name, my_user, headers=headers, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Users", outputs_key_field="Iden", outputs=my_user, readable_output=readable_output, raw_response=data, ) return results def add_role_command(client, args): """ Add a role to Synapse. Accepts new role. """ data = client.add_role(args.get("role")) role = data.get("result") my_role = { "Name": role.get("name"), "Iden": role.get("iden"), "Rules": user_rules_helper(role.get("rules")), } name = "Synapse New Role" headers = ["Name", "Iden", "Rules"] readable_output = tableToMarkdown(name, my_role, headers=headers, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Roles", outputs_key_field="Iden", outputs=my_role, readable_output=readable_output, raw_response=data, ) return results def grant_user_role_command(client, args): """ Grant role to a user. """ data = client.grant_role(args.get("user"), args.get("role")) user = data.get("result") my_user = { "Name": user.get("name"), "Email": user.get("email"), "Admin": user.get("admin"), "Iden": user.get("iden"), "Rules": user_rules_helper(user.get("rules")), "Roles": user_roles_helper(client, user.get("roles")), } name = "Synapse New User Role" headers = ["Name", "Email", "Admin", "Rules", "Roles"] readable_output = tableToMarkdown(name, my_user, headers=headers, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Users", outputs_key_field="Iden", outputs=my_user, readable_output=readable_output, raw_response=data, ) return results def query_model_command(client, args): """ Accepts a node type (str) to query and returns docs and properties. """ data = client.get_model() model_resp = model_query_helper(data.get("result"), args.get("query").lower()) q_type, q_form = {}, {} if model_resp.get("type"): q_type = { "Type": model_resp.get("query"), "Doc": model_resp["type"]["info"].get("doc", "N/A"), "Example": model_resp["type"]["info"].get("ex", "N/A"), } if model_resp.get("form"): q_form = {"Form": model_resp.get("query"), "Properties": model_query_properties_helper(model_resp.get("form"))} full_resp = {"Valu": model_resp.get("query")} full_resp.update(q_type) full_resp.update(q_form) name = "Synapse Model Type" headers = ["Type", "Doc", "Example"] readable_output = tableToMarkdown(name, q_type, headers=headers, removeNull=False) if full_resp.get("Form"): name_form = f'Synapse `{full_resp.get("Valu")}` Form Properties' headers_form = list(q_form["Properties"].keys()) readable_output += tableToMarkdown(name_form, q_form["Properties"], headers=headers_form, removeNull=False) results = CommandResults( outputs_prefix="Synapse.Model", outputs_key_field="Valu", outputs=full_resp, readable_output=readable_output, raw_response=model_resp, ) return results """ MAIN FUNCTION """ def main() -> None: """ main function, parses params and runs command functions """ username = demisto.params().get("credentials").get("identifier") password = demisto.params().get("credentials").get("password") port = demisto.params().get("port") base = demisto.params()["url"].rstrip("/") + ":" + str(port) use_ssl = not demisto.params().get("insecure", False) use_proxy = demisto.params().get("proxy", False) good_tag = demisto.params().get("good_tag") bad_tag = demisto.params().get("bad_tag") use_optic = demisto.params().get("use_optic", False) if use_optic: base_url = urljoin(base, "/api/v1/optic") else: base_url = urljoin(base, "/api/v1") demisto.debug(f"Command being called is {demisto.command()}") try: client = Client(base_url=base_url, username=username, password=password, verify=use_ssl, proxy=use_proxy) if demisto.command() == "test-module": return_results(test_module(client)) elif demisto.command() == "ip": return_results(ip_reputation_command(client, demisto.args(), good_tag, bad_tag)) elif demisto.command() == "domain": return_results(domain_reputation_command(client, demisto.args(), good_tag, bad_tag)) elif demisto.command() == "url": return_results(url_reputation_command(client, demisto.args(), good_tag, bad_tag)) elif demisto.command() == "file": return_results(file_reputation_command(client, demisto.args(), good_tag, bad_tag)) elif demisto.command() == "synapse-storm-query": return_results(storm_query_command(client, demisto.args())) elif demisto.command() == "synapse-list-users": return_results(list_users_command(client)) elif demisto.command() == "synapse-list-roles": return_results(list_roles_command(client)) elif demisto.command() == "synapse-create-user": return_results(add_user_command(client, demisto.args())) elif demisto.command() == "synapse-create-role": return_results(add_role_command(client, demisto.args())) elif demisto.command() == "synapse-grant-user-role": return_results(grant_user_role_command(client, demisto.args())) elif demisto.command() == "synapse-query-model": return_results(query_model_command(client, demisto.args())) # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()