Thinkst Canary
By presenting itself as an apparently benign and legitimate service(s), the Canary draws the attention of unwanted activity. When someone trips one of the Canary's triggers, an alert is sent to notify the responsible parties so that action can be taken before valubale systems in your network are compromised.
Deception & Breach Simulation · Thinkst Canary
Details
| ID | Thinkst Canary |
|---|---|
| Provider | Thinkst Canary |
| Category | Deception & Breach Simulation |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
By presenting itself as an apparently benign and legitimate service(s), the canary draws the attention of unwanted activity. When someone trips one of the Canary’s triggers, an alert is sent to notify the responsible parties so that action can be taken before valuable systems in your network are compromised.
Use Cases
- Fetch alerts from CanaryTools as incidents in Cortex XSOAR and acknowledge them.
- Get information about all registered Canaries.
- Get information about Canary Tokens.
- Add IP addresses to allow list.
Configure Thinkst Canary on Cortex XSOAR
For information about how to get your Authentication Token, see the Canary documentation.
- Navigate to Settings > Integrations > Servers & Services.
- Search for Thinkst Canary.
- Click Add instance to create and configure a new integration instance.
- Name: A textual name for the integration instance.
- Fetch incidents: Should the instance fetch incidents or not
- Incident type: Choose a type for Canary Tools incidents
- Canary Server URL
- API auth token
- Trust any certificate (not secure)
- Use system proxy settings
- 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.
- List all registered Canaries: canarytools-list-canaries
- List all Canary tokens: canarytools-list-tokens
- Check if an IP address is whitelisted: canarytools-check-whitelist
- Add an IP address to the allow list: canarytools-whitelist-ip
- Edit an alert status: canarytools-edit-alert-status
- Get a Canary Token file: canarytools-get-token
1. List all registered Canaries
Lists all registered Canaries.
Base Command
canarytools-list-canaries
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| CanaryTools.Device.ID | string | Device ID |
| CanaryTools.Device.Name | string | Device name |
| CanaryTools.Device.Description | string | Device description |
| CanaryTools.Device.Address | string | Device IP address |
| CanaryTools.Device.Status | boolean | Device status - Live (True/False) |
| CanaryTools.Device.Location | string | Device location |
| CanaryTools.Device.Version | string | Device version |
| CanaryTools.Device.LastSeen | date | Device last seen time |
| CanaryTools.Device.LastUpdated | date | Device last updated time |
Command Example
!canarytools-list-canaries
Context Example
{
"CanaryTools":{
"Device":{
"Status":true,
"Description":"Lab",
"LastUpdated":"2019-02-24 16:46:01 UTC+0000",
"Version":"2.1.2",
"Location":"Lab",
"Address":"192.168.1.43",
"Name":"VirtualCanary-1",
"ID":"0002f07cb32d45b1",
"LastSeen":"Sun Feb 24 2019 16:46:01 GMT+0000 (UTC)"
}
}
}
Human Readable Output

2. List all Canary tokens
Lists all Canary tokens.
Base Command
canarytools-list-tokens
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| CanaryTools.Tokens.CanaryToken | string | Canary Token |
| CanaryTools.Tokens.CreatedTime | date | Token Created time |
| CanaryTools.Tokens.Status | boolean | Token status - Enabled (True / False) |
| CanaryTools.Tokens.Kind | string | Token Kind |
| CanaryTools.Tokens.Triggered | number | Token triggered count |
| CanaryTools.Tokens.DocName | string | Token document name (If the token is from type document) |
| CanaryTools.Tokens.TokenURL | string | Token URL (How the token is presented) |
Command Example
!canarytools-list-tokens
Context Example
{
"CanaryTools":{
"Tokens":[
{
"Status":true,
"Kind":"doc-msword",
"Triggered":16,
"TokenURL":"http://e71ed0532685.o3n.io/cdn/ows8bhzvpx9ro3nz32r5vb3pa/logo.gif",
"DocName":"token.docx",
"CanaryToken":"ows8bhzvpx9ro3nz32t5vb3pa",
"CreatedTime":"2019-01-23 15:51:31 (UTC)"
}
]
}
}
Human Readable Output

3. Check if an IP address is on allow list
Checks whether a given IP address and port are on allow list.
Base Command
canarytools-check-whitelist
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address | Required |
| port | Destination port | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CanaryTools.IP.Address | string | IP address |
| CanaryTools.IP.Port | string | Destination port for the IP allow list |
| CanaryTools.IP.Whitelisted | boolean | Is the IP address on allow list (true/false) |
Command Example
!canarytools-check-whitelist ip=1.1.1.1 port=45
Context Example
{
"CanaryTools":{
"IP":{
"Whitelisted":"True",
"Port":"45",
"Address":"1.1.1.1"
}
}
}
Human Readable Output

4. Add an IP address to the allow list
Adds an IP address to the allow list in Canary.
Base Command
canarytools-whitelist-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address to allow list | Required |
| port | Destination port to allow list | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| CanaryTools.IP.Address | string | IP address |
| CanaryTools.IP.Port | string | Destination port for the IP allow list |
| CanaryTools.IP.Whitelisted | boolean | Is the IP address on allow list (True/False) |
Command Example
!canarytools-whitelist-ip ip=2.2.2.2 port=21
Context Example
{
"CanaryTools":{
"IP":{
"Whitelisted":"True",
"Port":"21",
"Address":"2.2.2.2"
}
}
}
Human Readable Output

5. Edit an alert status
Edits the status for an alert in Canary Tools.
Base Command
canarytools-edit-alert-status
Input
| Argument Name | Description | Required |
|---|---|---|
| alertID | Alert ID (e.g., incident:canarytoken:d6fe0ae4dfd36cc3cc6d9d4f::1548593719) | Required |
| status | Required status for the alert (Acknowledge, Unacknowledge) | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CanaryTools.Alert.ID | string | Alert ID |
| CanaryTools.Alert.Status | string | Alert status |
Command Example
!canarytools-edit-alert-status alertID=incident:canarytoken:d6fe0ae4dfd36cc3cc6d9d4f::1548593719 status=Acknowledge
Context Example
{
"CanaryTools":{
"Alert":{
"Status":"Acknowledge",
"ID":"incident:canarytoken:d6fe0ae4dfd36cc3cc6d9d4f::1548593719"
}
}
}
Human Readable Output

6. Get a Canary Token file
Fetches a Canary Token file from the Canary Tools server.
Base Command
canarytools-get-token
Input
| Argument Name | Description | Required |
|---|---|---|
| token | Canary Token | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| CanaryTools.Tokens.CanaryToken | string | Canary Token |
| File.Size | string | File Size |
| File.SHA1 | string | File SHA-1 |
| File.SHA256 | string | File SHA-256 |
| File.Name | string | File name |
| File.SSDeep | string | File SSDeep |
| File.EntryID | string | File EntryID |
| File.Info | string | File info |
| File.Type | string | File type |
| File.MD5 | string | File MD5 |
| File.Extension | string | File extension |
Command Example
!canarytools-get-token token=wpdkr30rx9naixdsijxdbd5ab
Context Example
{
"CanaryTools":{
"Tokens":"wpdkr30rx9naixdsijxwbd5ab"
},
"File":{
"Info":"image/jpeg",
"SHA1":"9719f38b13a9ab79469987a1ba495939c8577c54",
"Name":"affinity-photo-161120170952.jpg",
"Extension":"jpg",
"Size":240804,
"EntryID":"163@f7519b11-9105-4dd4-8036-fe2790f28ca6",
"SSDeep":"6144:IIgHoF7QKgKBiWlCdA1xQe5S55nXxlQIqwUAlP:InIFHriAGALT2Xd5P",
"SHA256":"cacf61ee474920578bd0e6f000b65fa40b313b912a8611b930414a7b9ae1d49b",
"Type":"ASCII text, with very long lines, with no line terminators\n",
"MD5":"c499c7f42eae921c974a4e71cb4a4cc5"
}
}
Human Readable Output

Configuration parameters
server— Canary Server URL (e.g., https://***.canary.tools) (required)auth_token— API Authentication Tokenauthentication_token—insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsfetchDelta— First fetch time (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year)incidentType— Incident typeincidentFetchInterval— Incidents Fetch Interval
Commands (6)
-
canarytools-check-whitelistChecks whether a given IP address and port are on allow list.
-
canarytools-edit-alert-statusEdits the status for an alert in Canary Tools.
-
canarytools-get-tokenFetches a Canary Token file from the Canary Tools server.
-
canarytools-list-canariesLists all registered Canaries.
-
canarytools-list-tokensLists all Canary tokens.
-
canarytools-whitelist-ipAdds an IP address to the allow list in Canary.
import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 from CommonServerUserPython import * """ IMPORTS """ import requests # disable insecure warnings urllib3.disable_warnings() # remove proxy if not set to true in params if not demisto.params().get("proxy", False): # Remove proxy environment variables if they exist for proxy_var in ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"]: os.environ.pop(proxy_var, None) """ GLOBALS""" SERVER = demisto.params().get("server").rstrip("/") + "/api/v1/" VERIFY_CERTIFICATE = not demisto.params().get("insecure", True) FETCH_DELTA = demisto.params().get("fetchDelta", "24 hours").strip() RELEVANT_DEVICE_ENTRIES = { "description": "Description", "id": "ID", "ip_address": "Address", "last_seen": "LastSeen", "live": "Status", "location": "Location", "name": "Name", "updated_std": "LastUpdated", "version": "Version", } RELEVANT_TOKEN_ENTRIES = { "canarytoken": "CanaryToken", "created_printable": "CreatedTime", "enabled": "Enabled", "kind": "Kind", "triggered_count": "Triggered", "doc_name": "DocName", "url": "TokenURL", } DEF_PARAMS = { "auth_token": demisto.params().get("authentication_token", {}).get("password") or demisto.params().get("auth_token", None) } if not DEF_PARAMS["auth_token"]: raise DemistoException("API Authentication Token must be provided.") """HELPER FUNCTIONS""" def http_request(method, url, params=None): # pragma: no cover """ HTTP request helper function """ if params is None: params = DEF_PARAMS else: params.update(DEF_PARAMS) res = requests.request(method=method, url=url, params=params, verify=VERIFY_CERTIFICATE) if not res.ok: try: res_json = res.json() if "message" in res_json: LOG(str(res.text)) LOG(res_json.get("message")) return_error(res_json.get("message")) except ValueError: LOG(str(res.text)) return_error(str(res.text)) except Exception as ex: LOG(res.text) return_error(str(ex)) try: res_json = res.json() return res_json except Exception as ex: demisto.debug(str(ex)) return_error(str(ex)) def get_alerts(last_fetch=None): """ Retrieve all unacknowledged alerts from Canary Tools :param last_fetch: Last fetch incidents time """ if last_fetch: params = {"newer_than": last_fetch} res = http_request("GET", SERVER + "incidents/unacknowledged", params) else: res = http_request("GET", SERVER + "incidents/unacknowledged") alerts = res.get("incidents") return alerts def create_incident(alert): """ Turns an alert from Canary Tools to the incident structure in Demisto :return: Demisto incident, e.g., CanaryToken triggered """ incident = { "name": demisto.get(alert, "description.description"), "occurred": timestamp_to_datestring(1000 * (int(demisto.get(alert, "description.created")))), "rawJSON": json.dumps(alert), } return incident """COMMANDS""" def test_module(): # pragma: no cover try: res = requests.request("GET", SERVER + "ping", params=DEF_PARAMS, verify=VERIFY_CERTIFICATE) if not res.ok: try: res_json = res.json() return_error("Could not connect, reason: {}".format(res_json.get("message"))) except Exception as ex: demisto.debug(str(ex)) return_error("Could not parse server response, please verify instance parameters") demisto.results("ok") except Exception as ex: demisto.debug(str(ex)) return_error("Failed to establish new connection, please verify instance parameters") def list_canaries(): """ Retrieve all Canaries available in Canary Tools :return: json response, a list of all devices """ res = http_request("GET", SERVER + "devices/all") new_devices = [ {new_key: device.get(old_key, None) for old_key, new_key in list(RELEVANT_DEVICE_ENTRIES.items())} for device in res["devices"] ] return res, new_devices def list_canaries_command(): """ Retrieve all Canaries available in Canary Tools """ res_json, new_devices = list_canaries() context = createContext(new_devices, removeNull=True) headers = ["ID", "Name", "Description", "Address", "Status", "Location", "Version", "LastSeen", "LastUpdated"] contents = res_json human_readable = tableToMarkdown("Canary Devices", new_devices, headers=headers) outputs = {"CanaryTools.Device(val.ID && val.ID === obj.ID)": context} return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents) def list_tokens(): """ Retrieve all Canary Tokens available in Canary Tools :return: json response, a list of all tokens """ res = http_request("GET", SERVER + "canarytokens/fetch") new_tokens = [] for token in res["tokens"]: new_tokens.append({new_key: token.get(old_key, None) for old_key, new_key in list(RELEVANT_TOKEN_ENTRIES.items())}) return res, new_tokens def list_tokens_command(): """ Retrieve all Canary Tokens available in Canary Tools """ res_json, new_tokens = list_tokens() headers = sorted(new_tokens[0].keys()) if new_tokens else None context = createContext(new_tokens, removeNull=True) contents = res_json for token in new_tokens: token.pop("TokenURL", None) human_readable = tableToMarkdown("Canary Tools Tokens", new_tokens, headers=headers, removeNull=True) outputs = {"CanaryTools.Token(val.CanaryToken && val.CanaryToken === obj.CanaryToken)": context} return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents, ignore_auto_extract=True) def get_token_command(): """ Fetch a Canary Token from the Canary Tools server :return: Canary Token information or file """ token = demisto.args().get("token") params = {"canarytoken": token} res = http_request("GET", SERVER + "canarytoken/fetch", params=params) context = demisto.get(res, "token.canarytoken") contents = res human_readable = "File Fetched Successfully" outputs = {"CanaryTools.Token(val.CanaryToken && val.CanaryToken === obj.CanaryToken)": context} if demisto.get(res, "token.doc"): name = demisto.get(res, "token.doc_name") content = demisto.get(res, "token.doc") token_file = fileResult(name, content) demisto.results(token_file) if demisto.get(res, "token.web_image"): name = demisto.get(res, "token.web_image_name") content = demisto.get(res, "token.web_image") token_file = fileResult(name, content) demisto.results(token_file) else: token_data = res.get("token", []) token_data.pop("url", None) human_readable = tableToMarkdown("Canary Tools Tokens", token_data) return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents, ignore_auto_extract=True) def check_whitelist(ip, port): """ Check if a given IP address is whitelisted in Canary Tools :return: json response """ params = {"src_ip": ip, "dst_port": port} res = http_request("GET", SERVER + "settings/is_ip_whitelisted", params=params) return res def check_whitelist_command(): """ Check if a given IP address is whitelisted in Canary Tools """ ip = demisto.args().get("ip") port = demisto.args().get("port") res = check_whitelist(ip, port) if not port: port = "Any" # is_ip_ignored key determines whether the ip is whitelisted or not. context = {"Address": str(ip), "Port": str(port), "Whitelisted": str(res.get("is_ip_ignored"))} contents = res context = createContext(context, removeNull=True) outputs = {"CanaryTools.IP(val.Address && val.Address===obj.Address && val.Port && val.Port===obj.Port)": context} if res.get("is_ip_ignored"): human_readable = f"The IP address {ip}:{port} is Whitelisted" else: human_readable = f"The IP address {ip}:{port} is not Whitelisted" return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents) def whitelist_ip(ip, port): """ Whitelist an IP address in Canary Tools :return: json response """ params = {"src_ip": ip, "dst_port": port} res = http_request("POST", SERVER + "settings/whitelist_ip_port", params=params) return res def whitelist_ip_command(): """ Whitelist an IP address in Canary Tools """ ip = demisto.args().get("ip") port = demisto.args().get("port") res = whitelist_ip(ip, port) if not port: port = "Any" result_status = res.get("result") if result_status == "success": context = {"Address": str(ip), "Port": str(port), "Whitelisted": "True"} context = createContext(context, removeNull=True) contents = res human_readable = f"The IP address {ip}:{port} was added to the Whitelist" outputs = {"CanaryTools.IP(val.Address && val.Address===obj.Address && val.Port && val.Port===obj.Port)": context} return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents) elif result_status == "failure": return_outputs(readable_output=res.get("message"), outputs={}, raw_response=res) elif result_status == "error": return_error(res.get("message")) def alert_status_command(): """ Acknowledge or Uncknowledge an Alert in Canary Tools """ args = demisto.args() alert = args.get("alert_id") status = args.get("status") context = { "ID": str(alert), "Status": str(status), } context = createContext(context, removeNull=True) params = {"incident": alert} if status == "Acknowledge": res = http_request("POST", SERVER + "incident/acknowledge", params=params) if res.get("action") == "acknowledged": contents = res human_readable = f"The Alert {alert} was " + res.get("action") outputs = {"CanaryTools.Alert(val.ID && val.ID === obj.ID)": context} return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents) elif status == "Unacknowledge": res = http_request("POST", SERVER + "incident/unacknowledge", params=params) if res.get("action") == "unacknowledged": contents = res human_readable = f"The Alert {alert} was " + res.get("action") outputs = {"CanaryTools.Alert(val.ID && val.ID === obj.ID)": context} return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents) else: return_error("Unsupported command") def fetch_incidents_command(): """ Fetch alerts from Canary Tools as incidents in Demisto last_fetch: The latest fetched alert creation time """ last_fetch = demisto.getLastRun().get("time") if last_fetch is None: last_fetch = parse_date_range(FETCH_DELTA, "%Y-%m-%d-%H:%M:%S")[0] # All alerts retrieved from get_alerts are newer than last_fetch and are in a chronological order alerts = get_alerts(last_fetch) incidents = [] current_fetch = last_fetch for alert in alerts: current_fetch = 1000 * (int(demisto.get(alert, "description.created")) + 1) current_fetch = timestamp_to_datestring(current_fetch, "%Y-%m-%d-%H:%M:%S") incident = create_incident(alert) incidents.append(incident) demisto.incidents(incidents) demisto.setLastRun({"time": current_fetch}) # Execution Code try: if demisto.command() == "test-module": test_module() elif demisto.command() == "canarytools-list-tokens": list_tokens_command() elif demisto.command() == "canarytools-get-token": get_token_command() elif demisto.command() == "canarytools-list-canaries": list_canaries_command() elif demisto.command() == "canarytools-check-whitelist": check_whitelist_command() elif demisto.command() == "canarytools-whitelist-ip": whitelist_ip_command() elif demisto.command() == "canarytools-edit-alert-status": alert_status_command() elif demisto.command() == "fetch-incidents": fetch_incidents_command() except Exception as e: return_error(f"Unable to perform command : {demisto.command}, Reason: {e}")