Attivo Botsink
Network-based Threat Deception for Post-Compromise Threat Detection.
Deception & Breach Simulation · Attivo Botsink
Details
| ID | Attivo Botsink |
|---|---|
| Provider | SentinelOne |
| Category | Deception & Breach Simulation |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.8.3296088 |
| Supported Modules | Agentix XSIAM |
README
Use the Attivo BOTsink integration to pull Attivo events into Cortex XSOAR to initiate investigations, manage deception environments, and to deploy decoy systems.
This integration was tested with version Attivo BOTsink v4.1.1 and v4.1.3.
Use Cases
- Determine if an artifact is part of the deception environment
- Dynamically deploy decoy systems
- Search for events related to a specific attacker
Configure Attivo Botsink on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Attivo Botsink.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- Botsink name or address
- BOTsink API credentials
- SSL Verification toggle
- Minimum severity when fetching events (Very High, High, Medium)
- Fetch incidents toggle
- Click Test to validate the URLs, token, and connection.
Fetched Incidents Data
The Attivo BOTsink plugin for Cortex XSOAR can optionally pull Attivo events into Cortex XSOAR to initiate investigations. The fetch_severity parameter specifies the lowest severity of event to pull (Very High, High, or Medium).
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.
- Verify if a user is part of a deception environment: attivo-check-user
- Verify if a host is part of the deception environment: attivo-check-host
- Run a playbook configured on Attivo BOTsink: attivo-run-playbook
- Deploy a decoy system on a subnet: attivo-deploy-decoy
- Get events for an attacker IP address: attivo-get-events
- Get information for playbooks: attivo-list-playbooks
- Get information for network decoys: attivo-list-hosts
- Get a list of all deceptive users: attivo-list-users
1. Verify if a user is part of a deception environment
Checks whether a user is part of the deceptive environment.
Base Command
attivo-check-user
Input
| Argument Name | Description | Required |
|---|---|---|
| user | User to validate | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Attivo.User.IsDeceptive | boolean | Is the user part of the Deception environment |
| Attivo.User.Groups | unknown | If the user is part of the Deception environment, the member is a member of these groups |
Command Example
!attivo-check-user user="a-user-l-ftp-0"
Context Example
{
"Attivo": {
"User": {
"IsDeceptive": true,
"Groups": [
"a-user-l-ftp"
],
"Name": "a-user-l-ftp-0"
}
}
}
Human Readable Output
2. Verify if a host is part of a deception environment
Checks whether a host is part of the deception environment.
Base Command
attivo-check-host
Input
| Argument Name | Description | Required |
|---|---|---|
| host | Host name or IP address to validate | Required |
Command Example
!attivo-check-host host="linuxserver"
Context Example
{
"Attivo": {
"Host": {
"IsDeceptive": true,
"HostInfo": {
"name": [
"linuxserver"
],
"ip": "162.236.53.68",
"vlan": null,
"user_defined": true,
"mac": "52:54:00:9f:65:76",
"dhcp": false
}
}
}
}
Human Readable Output
3. Run a playbook configured on Attivo BOTsink
Run a pre-built Attivo playbook on the BOTsink appliance.
Base Command
attivo-run-playbook
Input
| Argument Name | Description | Required |
|---|---|---|
| playbook_name | Name of the prebuilt playbook | Required |
| attacker_ip | Malicious source IP | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Attivo.Playbook.Status | boolean | Was the playbook successful |
| Attivo.Playbook.Message | string | Complete status message |
Command Example
!attivo-run-playbook attacker_ip=172.16.2.20 playbook_name="Endpoint Forensics"
4. Deploy a decoy system on a subnet
Deploy a new network decoy
Base Command
attivo-deploy-decoy
Input
| Argument Name | Description | Required |
|---|---|---|
| vulnerable_ip | Used to determine which subnet to deploy to | Required |
| decoy_number | The number of decoys to deploy | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Attivo.DeployDecoy.Status | boolean | Was the network decoy successfully deployed |
| Attivo.DeployDecoy.Message | string | Complete status message |
Command Example
!attivo-deploy-decoy vulnerable_ip=172.16.40.55
Human Readable Output
1 new Attivo decoy(s) deployed on the subnet with 172.16.40.55
5. Get events for an attacker IP address
Retrieves events for a specific source IP.
Base Command
attivo-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| attacker_ip | Source IP address | Required |
| severity | The minimum Attivo severity for the events, default is "Medium"; "VeryHigh", "High", "Medium", "Low", "VeryLow", "SystemActivity" | Optional |
| alerts_start_date | Date and time to start looking for events. For example: 2018-12-10 or 2018-12-10T13:59:05Z |
Optional |
| alerts_end_date | Date and time to stop looking for events. For example: 2018-12-10 or 2018-12-10T13:59:05Z |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Attivo.Events.Count | number | Total number of events retrieved |
| Attivo.Events.List.AttackName | unknown | Short name of the attack |
| Attivo.Events.List.Attack Phase | string | Kill chain phase of the attack |
| Attivo.Events.List.Server | string | Internal name of the target decoy |
| Attivo.Events.List.Target | string | Display name of the target decoy |
| Attivo.Events.List.TargetOS | string | Operating system of the target decoy |
| Attivo.Events.List.Attacker | string | Attacker IP address |
| Attivo.Events.List.Service | string | The attacked service |
| Attivo.Events.List.Timestamp | string | Time of the attack |
| Attivo.Events.List.TargetIP | string | IP address of the target decoy |
| Attivo.Events.List.Severity | string | Attivo severity of the attack |
Command Example
!attivo-get-events attacker_ip=CentOS70 alerts_start_date=2018-11-30T23:59:05Z alerts_end_date=2018-12-01T00:02:05Z
Context Example
{
"Attivo": {
"Events": {
"Count": 2,
"List": [
{
"geoip_src_latitude": null,
"Severity": "Medium",
"Service": "DNS SERVER",
"VLAN": null,
"AttackName": "DNS Response",
"TargetIP": "SinkHole",
"AttackPhase": "C&C",
"TargetOS": "CentOS 7.0",
"Timestamp": "2018-12-01T00:01:43.500Z",
"geoip_dest_city_name": null,
"geoip_dest_country_code2": null,
"geoip_dest_country_code3": null,
"Attacker": "CentOS70",
"Device": "0",
"geoip_src_country_code3": null,
"geoip_src_country_code2": null,
"Target": "SinkHole",
"Server": "ZZZ-BServer01",
"geoip_dest_latitude": null,
"geoip_src_country_name": null,
"geoip_src_longitude": null,
"geoip_dest_country_name": null,
"geoip_dest_longitude": null
},
{
"geoip_src_latitude": null,
"Severity": "Medium",
"Service": "DNS SERVER",
"VLAN": null,
"AttackName": "DNS Response",
"TargetIP": "SinkHole",
"AttackPhase": "C&C",
"TargetOS": "CentOS 7.0",
"Timestamp": "2018-12-01T00:01:38.500Z",
"geoip_dest_city_name": null,
"geoip_dest_country_code2": null,
"geoip_dest_country_code3": null,
"Attacker": "CentOS70",
"Device": "0",
"geoip_src_country_code3": null,
"geoip_src_country_code2": null,
"Target": "SinkHole",
"Server": "ZZZ-BServer01",
"geoip_dest_latitude": null,
"geoip_src_country_name": null,
"geoip_src_longitude": null,
"geoip_dest_country_name": null,
"geoip_dest_longitude": null
}
]
}
}
}
6. Get information for playbooks
List information about playbooks configured on the Attivo device
Base Command
attivo-list-playbooks
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
7. Get information for network decoys
List information about network decoys
Base Command
attivo-list-hosts
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Human Readable Output
8. Get a list of all deceptive users
Lists all deceptive users.
Base Command
attivo-list-users
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Human Readable Output
Known Limitations
This integration works with the Attivo BOTsink. You can only run the attivo-deploy-decoy using the physical appliance.
Configuration parameters
server— Botsink name or address (required)credentials— BOTsink API credentials (required)insecure— Trust any certificate (not secure)fetch_severity— Minimum severity when fetching events; "Very High", "High", "Medium" (required)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalfirst_fetch— Number of days to go back for the initial fetch. Use "0" to only retrieve new incidents. (required)
Commands (8)
-
attivo-check-hostChecks whether a host is deceptive.
-
attivo-check-userChecks whether a user is deceptive.
-
attivo-deploy-decoyDeploys a new network decoy.
-
attivo-get-eventsRetrieves events for a specified source IP.
-
attivo-list-hostsList information about network decoys.
-
attivo-list-playbooksList information about playbooks configured on the Attivo device.
-
attivo-list-usersList all deceptive users.
-
attivo-run-playbookRuns a prebuilt playbook on the BOTsink appliance.
import base64 import json import os import socket import time from datetime import datetime import demistomock as demisto # noqa: F401 import requests import urllib3 from CommonServerPython import * # noqa: F401 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) SERVER = demisto.params()["server"] API_USER = demisto.params()["credentials"]["identifier"] API_PASS = demisto.params()["credentials"]["password"] VERIFY_SSL = not demisto.params()["insecure"] handle_proxy() class BSAPI: BS_DEFAULT_PORT = 8443 TIMEOUT = 20 def __init__(self, bs_host, bs_port=BS_DEFAULT_PORT, verify_ssl=False, timeout=TIMEOUT): self.bs_host = bs_host self.bs_port = bs_port self.timeout = timeout self.session_key = None self.base_url = f"https://{self.bs_host}:{self.bs_port}/api" self.verify_ssl = verify_ssl def do_request(self, url, data=None, headers=None, files=None, method=None, content_type="application/json", json_dump=True): # Guess the method if not provided if not method: if data: method = "post" else: method = "get" headers = {} if self.session_key: headers = {"sessionKey": self.session_key} if content_type: headers["content-type"] = content_type url = self.base_url + url # Convert data dictionary to a string if data and json_dump: data = json.dumps(data) request_func = getattr(requests, method) r = None try: r = request_func(url, headers=headers, data=data, files=files, verify=self.verify_ssl) except requests.exceptions.SSLError: demisto.error("SSL verification failed") demisto.results(f"SSL verification to {url} failed") except requests.exceptions.ConnectionError as e: demisto.error(f"Could not connect to: {SERVER}") demisto.error(f"Exception: {e}") demisto.results(f"Could not connect to {SERVER} ({e})") except Exception as e: demisto.error(f"Generic Exception: {e}") demisto.error(f"Type is: {e.__class__.__name__}") if r is not None and r.content: try: json_res = r.json() except ValueError: return_error(f"Failed deserializing response JSON - {r.content}") return json_res else: return None def login(self, bs_user, bs_pass): url = "/auth/login" login_data = { "userName": base64.b64encode(bs_user.encode()).decode(), "password": base64.b64encode(bs_pass.encode()).decode(), } login_status = self.do_request(url, data=login_data) if login_status and "sessionKey" in login_status: self.session_key = login_status["sessionKey"] return login_status def logout(self): url = "/auth/logout" logout_status = self.do_request(url) return logout_status def deploy_decoys(self, target_ip, vlan=None, decoy_number=1): url = "/autodeploy/config" if vlan: data = {"config": [{"ipAddress": target_ip, "vlanID": vlan, "numberOfIPsToAcquire": decoy_number}]} else: data = {"config": [{"ipAddress": target_ip, "numberOfIPsToAcquire": decoy_number}]} deploy_status = self.do_request(url, data=data, content_type=None) return deploy_status def get_threatdirect_rules(self): url = "/nwinterfaces/get" td_decoys = self.do_request(url) return td_decoys def get_bs_health(self): url = "/device/health" health = self.do_request(url) return health def get_monitoring_rules(self): url = "/interfaces/get" monitoring_rules = self.do_request(url, data="{}", method="post", json_dump=None) return monitoring_rules def get_deceptive_objects(self, object_type, object_id): if object_type == "USERS": if object_id == "ALL": url = "/obj_group_cfg/summary/user" else: url = f"/obj_group_cfg/user/{object_id}" else: response = f"Unknown option: {object_type}" return response deceptive_objects = self.do_request(url) return deceptive_objects def get_playbooks(self): url = "/pb/getAll" return self.do_request(url) def run_playbook(self, playbook_id, attacker_ip): "This simulates an internal playbook execution based on the attacker IP" url = "/pb/runplaybook" data = {"attacker_ip": attacker_ip, "playbook_id": playbook_id} return self.do_request(url, data=data) def get_events( self, severity_start=None, severity_end=None, timestamp_start=None, timestamp_end=None, offset=None, acknowledged="unacknowledged", attacker_ip=None, category=None, device=None, service=None, target_os=None, target_host=None, target_ip=None, target_vlan=None, keywords=None, description=None, comments=None, ): url = "/eventsquery/alerts" query_data = { "severity_start": severity_start, "severity_end": severity_end, "timestampStart": timestamp_start, "timestampEnd": timestamp_end, "offset": offset, "acknowledged": acknowledged, "attackerIp": [] if attacker_ip is None else attacker_ip, "category": [] if category is None else category, "device": [] if device is None else device, "service": [] if service is None else service, "targetOs": [] if target_os is None else target_os, "targetHost": [] if target_host is None else target_host, "targetIP": [] if target_ip is None else target_ip, "targetVLAN": [] if target_vlan is None else target_vlan, "keywords": [] if keywords is None else keywords, "description": [] if description is None else description, "comments": [] if comments is None else comments, } event_data = self.do_request(url, data=query_data) return event_data def convert_severity_string(self, severity_string): conversion = { "VeryHigh": 14, "Very High": 14, "High": 11, "Medium": 7, "Low": 4, "VeryLow": 3, "Very Low": 3, "SystemActivity": 0, "System Activity": 0, } if severity_string in conversion: return conversion[severity_string] else: return None def convert_to_demisto_severity(self, attivo_severity): if attivo_severity >= 14: # Very High demisto_severity = 3 elif attivo_severity >= 11: # High demisto_severity = 3 elif attivo_severity >= 7: # Medium demisto_severity = 2 else: # Low demisto_severity = 1 return demisto_severity def valid_ip(host): try: socket.inet_aton(host) return True except Exception: return False def date_to_epoch(date): date_pattern1 = r"\d{4}-\d{2}-\d{2}$" date_format1 = "%Y-%m-%d" date_pattern2 = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$" date_format2 = "%Y-%m-%dT%H:%M:%SZ" epoch = None if re.match(date_pattern2, date): epoch = int(time.mktime(time.strptime(date, date_format2))) * 1000 elif re.match(date_pattern1, date): epoch = int(time.mktime(time.strptime(date, date_format1))) * 1000 return epoch """ EXECUTION CODE """ attivo_api = BSAPI(SERVER, verify_ssl=VERIFY_SSL) if demisto.command() == "attivo-get-events": args = demisto.args() login_status = attivo_api.login(API_USER, API_PASS) attacker_ip = args["attacker_ip"] severity_string = args["severity"] start_date = args.get("alerts_start_date") if start_date is not None: timestampStart = date_to_epoch(start_date) else: one_day = 24 * 60 * 60 timestampStart = (int(time.time()) - one_day) * 1000 end_date = args.get("alerts_end_date") if end_date is not None: timestampEnd = date_to_epoch(end_date) else: timestampEnd = int(time.time()) * 1000 if timestampEnd is None: demisto.info(f"Bad date: {end_date}\nDate should be of the format yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ") return_error(f"Bad date: {end_date}\nDate should be of the format yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ") if timestampStart is None: demisto.info(f"\nBad date: {start_date}\nDate should be of the format yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ") return_error(f"\nBad date: {start_date}\nDate should be of the format yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ") severity_end = "15" severity_start = attivo_api.convert_severity_string(severity_string) attacker_ips = [attacker_ip] demisto.info(f"Pulling events for {attacker_ip} and severity {severity_start} from {timestampStart} to {timestampEnd}") events = attivo_api.get_events( severity_start=severity_start, severity_end=severity_end, timestamp_start=timestampStart, timestamp_end=timestampEnd, attacker_ip=attacker_ips, ) attivo_api.logout() brief_events = [] context = [] for event in events["eventdata"]: brief_events.append( { "Attack Name": event["attackName"], "Severity": event["details"]["Severity"], "Target IP": event["details"]["Target IP"], "Target OS": event["details"]["Target OS"], "Timestamp": event["details"]["Timestamp"], } ) context.append({k.replace(" ", ""): v for k, v in list(event["details"].items())}) headers = ["Attack Name", "Severity", "Timestamp", "Target IP", "Target OS"] entry = { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": events["eventdata"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown(f"Found {len(brief_events)} events:", brief_events, headers=headers), "EntryContext": {"Attivo.Events.Count": len(events["eventdata"]), "Attivo.Events.List": context}, } demisto.results(entry) if demisto.command() == "fetch-incidents": login_status = attivo_api.login(API_USER, API_PASS) date_pattern = "%Y-%m-%dT%H:%M:%S.%fZ" os.environ["TZ"] = "UTC" FETCH_SEVERITY = demisto.params()["fetch_severity"] FIRST_FETCH = int(demisto.params()["first_fetch"]) severity_start = attivo_api.convert_severity_string(FETCH_SEVERITY) if not severity_start: demisto.info(f"Attivo fetch-incidents: Unknown severity specified ('{FETCH_SEVERITY}') using Medium") severity_start = 7 # Medium severity_end = "15" # Very High # When run for the first time, get events from the specified number of days one_day = 24 * 60 * 60 first_fetch_seconds = (int(time.time()) - (one_day * FIRST_FETCH)) * 1000 last_run_time = demisto.getLastRun().get("time", None) if last_run_time is None or last_run_time == 0: last_run_time = first_fetch_seconds demisto.info(f"Attivo fetch-incidents: Last run time {last_run_time}, severity {FETCH_SEVERITY}:{severity_start}") new_last_run = 0.0 incidents = [] events = attivo_api.get_events( timestamp_start=last_run_time, timestamp_end="now", severity_start=severity_start, severity_end=severity_end ) if "error" in events: demisto.error("fetch-incidents error: {}".format(events["error"])) sys.exit() demisto.info("Total new Attivo incidents to add: {}".format(len(events["eventdata"]))) for event in events["eventdata"]: event_date = event["timeStamp"] date_obj = datetime.strptime(event_date, date_pattern) event_timestamp = int((date_obj - datetime(1970, 1, 1)).total_seconds()) * 1000 + date_obj.microsecond / 1000 new_last_run = max(new_last_run, event_timestamp) demisto_severity = attivo_api.convert_to_demisto_severity(event["alertLevel"]) event_type = event["details"]["Attack Phase"] incidents.append( { "name": event["attackName"], "occurred": event_date, "details": event["attackDesc"], "severity": demisto_severity, "type": event_type, "rawJSON": json.dumps(event), } ) if len(incidents) > 0 and new_last_run > 0: new_last_run += 1 demisto.info(f"Setting new last run value to {new_last_run}") demisto.setLastRun({"time": new_last_run}) else: demisto.info("No new Attivo incidents to add") logout_status = attivo_api.logout() demisto.incidents(incidents) if demisto.command() == "test-module": # This is the call made when pressing the integration test button. login_status = attivo_api.login(API_USER, API_PASS) if login_status and "sessionKey" in login_status: demisto.info("Attivo Login successful (session key = {})".format(login_status["sessionKey"])) logout_status = attivo_api.logout() demisto.results("ok") sys.exit(0) else: demisto.error(f"Login to {SERVER} failed") demisto.error(f"API Results: {login_status}") demisto.results(f"Login to {SERVER} failed\n{login_status}") if demisto.command() == "attivo-list-playbooks": login_status = attivo_api.login(API_USER, API_PASS) all_playbooks = attivo_api.get_playbooks() brief_playbooks = [] for playbook in all_playbooks["pb"]: brief_playbook = {"ID": playbook["id"], "Name": playbook["name"]} demisto.info("INVESTIGATE {}".format(playbook["investigate"])) if len(playbook["investigate"]) > 0: investigate_names = [] for investigate in playbook["investigate"]: investigate_names.append(investigate["name"]) brief_playbook["Investigate"] = ", ".join(investigate_names) else: brief_playbook["Investigate"] = [] if len(playbook["analyze"]) > 0: analyze_names = [] for analyze in playbook["analyze"]: analyze_names.append(analyze["name"]) brief_playbook["Analyze"] = ", ".join(analyze_names) else: brief_playbook["Analyze"] = [] if len(playbook["manage"]) > 0: manage_names = [] for manage in playbook["manage"]: manage_names.append(manage["name"]) brief_playbook["Manage"] = ", ".join(manage_names) else: brief_playbook["Manage"] = [] if len(playbook["isolate"]) > 0: isolate_names = [] for isolate in playbook["isolate"]: isolate_names.append(isolate["name"]) brief_playbook["Isolate"] = ", ".join(isolate_names) else: brief_playbook["Isolate"] = [] brief_playbooks.append(brief_playbook) headers = ["Name", "ID", "Investigate", "Analyze", "Manage", "Isolate"] entry = { "Type": entryTypes["note"], "Contents": brief_playbooks, "ContentsFormat": formats["json"], "HumanReadable": tableToMarkdown("Attivo playbooks", brief_playbooks, headers=headers), "ReadableContentsFormat": formats["markdown"], "EntryContext": {}, } demisto.results(entry) logout_status = attivo_api.logout() if demisto.command() == "attivo-run-playbook": playbook_name = demisto.args()["playbook_name"] attacker_ip = demisto.args()["attacker_ip"] playbook_id = None playbook_status = None login_status = attivo_api.login(API_USER, API_PASS) all_playbooks = attivo_api.get_playbooks() for playbook in all_playbooks["pb"]: if playbook["name"] == playbook_name: playbook_id = playbook["id"] break if not playbook_id: demisto.error(f"ID not found for Attivo playbook named: {playbook_name}") status_message = f"Failed: could not find playbook named '{playbook_name}'" status = False else: demisto.info(f"Running Attivo playbook named {playbook_name} ({playbook_id}) with attacker IP {attacker_ip}") playbook_status = attivo_api.run_playbook(playbook_id, attacker_ip) demisto.info(f"Run playbook status = {playbook_status}") if "error" in playbook_status: error_text = playbook_status["error"] status_message = error_text status = False elif "status" in playbook_status: status_text = playbook_status["status"] if status_text == "submitted": status = True status_message = ( f"Attivo playbook '{playbook_name}' (ID={playbook_id}) has been run with attacker IP {attacker_ip}" ) else: status = False status_message = f"Attivo playbook has not been run. Status = '{status_text}'" else: status = False status_message = "Attivo playbook has not been run. Status = 'Unknown failure'" entry = { "Type": entryTypes["note"], "Contents": playbook_status, "ContentsFormat": formats["json"], "HumanReadable": status_message, "ReadableContentsFormat": formats["text"], "EntryContext": {"Attivo.Playbook.Status": status, "Attivo.Playbook.Message": status_message}, } demisto.results(entry) logout_status = attivo_api.logout() if demisto.command() == "attivo-deploy-decoy": vulnerable_ip = demisto.args()["vulnerable_ip"] decoy_number = demisto.args()["decoy_number"] login_status = attivo_api.login(API_USER, API_PASS) demisto.info(f"Deploying {decoy_number} decoy(s) on the subnet of {vulnerable_ip}") deploy_status = {} deploy_status = attivo_api.deploy_decoys(vulnerable_ip, decoy_number=decoy_number) demisto.info(f"Deployment status = {deploy_status}") status = False status_text = "Unknown failure" if "result" in deploy_status: status_text = deploy_status["result"][0]["success"] if status_text is True: status = True elif "success" in deploy_status: status_text = deploy_status["success"] if status_text is True: status = True elif "error" in deploy_status: status_text = deploy_status["error"] if status: status_message = f"{decoy_number} new Attivo decoy(s) deployed on the subnet with {vulnerable_ip}" else: status_message = f"No Attivo decoys have been deployed. {status_text}" entry = { "Type": entryTypes["note"], "Contents": deploy_status, "ContentsFormat": formats["json"], "HumanReadable": status_message, "ReadableContentsFormat": formats["text"], "EntryContext": {"Attivo.DeployDecoy.Status": status, "Attivo.DeployDecoy.Message": status_message}, } demisto.results(entry) logout_status = attivo_api.logout() if demisto.command() == "attivo-list-users": demisto.info("Retrieving information about all deceptive users") login_status = attivo_api.login(API_USER, API_PASS) user_groups = attivo_api.get_deceptive_objects("USERS", "ALL") users = {} # type: Dict for user_group in user_groups["objGroup"]: group_id = user_group["esid"] group_name = user_group["name"] users_in_group = attivo_api.get_deceptive_objects("USERS", group_id) for user_object in users_in_group["objGroup"]["objects"]: user = user_object["username"] if user in users: users[user].append(group_name) else: users[user] = [group_name] all_users = [] for user in sorted(users.keys(), key=lambda x: x.lower()): user_entry = {"User": user, "Groups": ", ".join(users[user])} all_users.append(user_entry) headers = ["User", "Groups"] entry = { "Type": entryTypes["note"], "Contents": all_users, "ContentsFormat": formats["json"], "HumanReadable": tableToMarkdown("Attivo deceptive users", all_users, headers=headers), "ReadableContentsFormat": formats["markdown"], "EntryContext": {}, } demisto.results(entry) logout_status = attivo_api.logout() if demisto.command() == "attivo-check-user": user = demisto.args()["user"] demisto.info(f"Check Attivo for user = {user}") login_status = attivo_api.login(API_USER, API_PASS) is_deceptive = False this_user_object = None user_groups = attivo_api.get_deceptive_objects("USERS", "ALL") in_groups = [] for user_group in user_groups["objGroup"]: group_id = user_group["esid"] users_in_group = attivo_api.get_deceptive_objects("USERS", group_id) for user_object in users_in_group["objGroup"]["objects"]: this_user = user_object["username"] if this_user == user: this_user_object = user_object is_deceptive = True in_groups.append(user_group["name"]) break output_table = {"User": user, "Is Deceptive": str(is_deceptive), "Groups": ", ".join(in_groups)} entry = { "Type": entryTypes["note"], "Contents": this_user_object, "ContentsFormat": formats["text"], "HumanReadable": output_table, "ReadableContentsFormat": formats["table"], "EntryContext": {"Attivo.User.Name": user, "Attivo.User.IsDeceptive": is_deceptive, "Attivo.User.Groups": in_groups}, } demisto.info(f"User {user}, deceptive = {is_deceptive}, group(s) = {in_groups}") demisto.results(entry) logout_status = attivo_api.logout() if demisto.command() == "attivo-list-hosts": demisto.info("Retrieving information about all deceptive hosts") login_status = attivo_api.login(API_USER, API_PASS) all_hosts = [] td_monitoring = attivo_api.get_threatdirect_rules() bs_monitoring = attivo_api.get_monitoring_rules() if ( td_monitoring.get("forwarder_vm_monitoring_rules") is not None and td_monitoring.get("forwarder_vm_monitoring_rules").get("forwarderVmMonitoringRules") is not None ): for rule in td_monitoring["forwarder_vm_monitoring_rules"]["forwarderVmMonitoringRules"]: if rule["type"] == "onNet": td_type = "EP" else: td_type = "VM" host_names = [] if rule.get("dnsName"): host_names.append(rule["dnsName"]) host_entry = { "IP": rule["ip"], "MAC": rule["customized_mac"], "VLAN": rule["vlanID"], "DHCP": rule["dhcpip"], "TD Name": rule["threatDirectName"], "TD Type": td_type, "Host Name": ", ".join(host_names), } all_hosts.append(host_entry) if ( bs_monitoring.get("cfg_monitoring_rules") is not None and bs_monitoring.get("cfg_monitoring_rules").get("monitoringRules") is not None ): for rule in bs_monitoring["cfg_monitoring_rules"]["monitoringRules"]: # demisto.info("BS RULE: {}".format(rule)) vlan = rule["vlanID"] if vlan == -1: vlan = None host_names = [] if rule.get("dnsName"): host_names.append(rule["dnsName"]) if rule.get("interfaceName"): host_names.append(rule["interfaceName"]) host_entry = { "IP": rule["ipAddress"], "MAC": rule["externalMAC"], "DHCP": rule["isDHCPIP"], "VLAN": vlan, "User Defined": rule["userDefined"], "Host Name": ", ".join(host_names), } if td_monitoring is not None: host_entry["TD Name"] = "" host_entry["TD Type"] = "" all_hosts.append(host_entry) headers = ["IP", "Host Name", "MAC", "VLAN", "DHCP"] if td_monitoring["forwarder_vm_monitoring_rules"]["forwarderVmMonitoringRules"]: headers.append("TD Name") headers.append("TD Type") entry = { "Type": entryTypes["note"], "Contents": all_hosts, "ContentsFormat": formats["json"], "HumanReadable": tableToMarkdown( f"Attivo deceptive hosts (network decoys): {len(all_hosts)}", all_hosts, headers=headers ), "ReadableContentsFormat": formats["markdown"], "EntryContext": {}, } demisto.results(entry) logout_status = attivo_api.logout() if demisto.command() == "attivo-check-host": host = demisto.args()["host"] demisto.info(f"Check Attivo for host = {host}") login_status = attivo_api.login(API_USER, API_PASS) is_deceptive = False this_rule = None host_info = {} if valid_ip(host): ip_address = host host_name = None else: host_name = host ip_address = None # Check native Monitoring Rules bs_monitoring = attivo_api.get_monitoring_rules() if ( bs_monitoring is not None and bs_monitoring.get("cfg_monitoring_rules") is not None and bs_monitoring.get("cfg_monitoring_rules").get("monitoringRules") is not None ): for rule in bs_monitoring["cfg_monitoring_rules"]["monitoringRules"]: this_ip = rule["ipAddress"] mac = rule["externalMAC"] dhcp = rule["isDHCPIP"] vlan = rule["vlanID"] if vlan == -1: vlan = None user_defined = rule["userDefined"] this_host_name = [] if rule.get("dnsName"): this_host_name.append(rule["dnsName"]) if rule.get("interfaceName"): this_host_name.append(rule["interfaceName"]) if (ip_address and this_ip == ip_address) or (host_name and this_host_name and host_name in this_host_name): this_rule = rule is_deceptive = True demisto.info( f"Attivo BOTSink IP/Host match ({this_ip}) ({this_host_name}) ({user_defined}) ({mac}) ({dhcp}) ({vlan})" ) host_info = { "ip": this_ip, "name": this_host_name, "user_defined": user_defined, "mac": mac, "dhcp": dhcp, "vlan": vlan, } break if not is_deceptive: # Check ThreatDirect Monitoring Rules td_monitoring = attivo_api.get_threatdirect_rules() if ( td_monitoring is not None and td_monitoring.get("forwarder_vm_monitoring_rules") is not None and td_monitoring.get("forwarder_vm_monitoring_rules").get("forwarderVmMonitoringRules") is not None ): for rule in td_monitoring["forwarder_vm_monitoring_rules"]["forwarderVmMonitoringRules"]: this_ip = rule["ip"] this_host_name = [] mac = rule["customized_mac"] vlan = rule["vlanID"] dhcp = rule["dhcpip"] td_name = rule["threatDirectName"] if rule["type"] == "onNet": td_type = "EP" else: td_type = "VM" if rule.get("dnsName"): this_host_name.append(rule["dnsName"]) if (ip_address and this_ip == ip_address) or (host_name and this_host_name and host_name in this_host_name): this_rule = rule is_deceptive = True demisto.info( f"Attivo ThreatDirect IP match ({this_ip}) ({this_host_name})" f" ({mac}) ({dhcp}) ({vlan}) ({td_name}) ({td_type})" ) host_info = { "ip": this_ip, "name": this_host_name, "mac": mac, "dhcp": dhcp, "vlan": vlan, "td_name": td_name, "td_type": td_type, } break # elif host_name and this_host_name and host_name in this_host_name: # this_rule = rule # is_deceptive = True # demisto.info("Attivo ThreatDirect host match ({ip}) ({name}) # ({user_defined}) ({mac}) ({dhcp}) ({vlan})".format(this_ip, #this_host_name, # user_defined, mac, dhcp, vlan)) # break if is_deceptive: output_table = { "Is Deceptive": "True", "IP Address": this_ip, "Host Names": ", ".join(this_host_name), "MAC Address": mac, "DHCP": str(dhcp), "User Defined": str(user_defined), "VLAN": vlan, } else: output_table = { "Is Deceptive": "False", "IP Address": ip_address, "Host Names": host_name, "MAC Address": "", "DHCP": "", "User Defined": "", "VLAN": "", } entry = { "Type": entryTypes["note"], "Contents": output_table, "ContentsFormat": formats["table"], "HumanReadable": output_table, "ReadableContentsFormat": formats["table"], "EntryContext": {"Attivo.Host.HostInfo": host_info, "Attivo.Host.IsDeceptive": is_deceptive}, } demisto.info(f"Deception status for {host} is {is_deceptive}") demisto.results(entry) logout_status = attivo_api.logout()



