import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """QutteraWebsiteMalwareScanner Integration for Cortex XSOAR (aka Demisto) """ import urllib3 # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" class Client(BaseClient): """Client class for basic interaction with Quttera API.""" def __init__(self, params): self.apikey = params["apikey"] self.base_url = params["base_url"] super().__init__(base_url=self.base_url) def _api_request(self, domain: str, method: str, operation: str) -> Dict: """Start domain scanning :type domain: ``str`` :param domain: Domain to scan :return: dict containing response from api request :rtype: ``Dict`` """ operations = { "scan": f"/api/v3/{self.apikey}/url/scan", "rescan": f"/api/v3/{self.apikey}/url/scan", "statscan": f"/api/v3/{self.apikey}/url/status", "malware": f"/api/v3/{self.apikey}/url/report", "blacklist": f"/api/v3/{self.apikey}/blacklist/status", } op = operations[operation] return self._http_request(method=method, url_suffix=f"{op}/{domain}", ok_codes=(200, 404)) def _test_module(self): return self._http_request(method="GET", url_suffix=f"/api/v3/{self.apikey}/status") def scan_start(client: Client, args: dict) -> CommandResults: """ quttera-scan-start command: Scan a domain/website for malware and blacklist. :type client: ``Client`e :param Client: Qutter API client :type args: ``Dict[str, Any]`` :param args: all command arguments from ``demisto.args()`` :return: ``CommandResults`` object contain the start scan response :rtype: ``CommandResults`` """ domain = args.get("domain") if not domain: raise ValueError("domain is missing") start = client._api_request(domain=domain, method="POST", operation="scan") demisto.info(f"Start output {start}") readable_output = f"Started scan of {domain}. Run !quttera-report-malware to see the results." if start.get("errorstr") != "success": readable_output = f"Failed to scan domain {domain}" return CommandResults( readable_output=readable_output, outputs_prefix="QutteraWebsiteMalwareScanning.Start", outputs_key_field="error", outputs=start, ) def rescan_status(client: Client, args: dict) -> CommandResults: """ Retrieves scan status for previously submitted domain name or URL. """ domain = args.get("domain") if not domain: raise ValueError("domain is missing") rescan = client._api_request(domain=domain, method="PUT", operation="rescan") readable_output = f"Rescan of {domain}" return CommandResults( readable_output=readable_output, outputs_prefix="QutteraWebsiteMalwareScanning.Status", outputs_key_field="error", outputs=rescan, ) def scan_report(client: Client, args: dict) -> CommandResults: """ Retrieves scan report for previously submitted domain or URL in context or as a File (default) """ domain = args.get("domain") if not domain: raise ValueError("domain is missing") readable_output = "No output" report = client._api_request(domain=domain, method="GET", operation="statscan") state = None try: state = report["status"]["state"] states = ["NEW", "DOWNLOAD", "DOWNLOADED", "SCAN"] if state in states: readable_output = f"Domain {domain} is being scanned" else: status = report["status"]["scanner_result"] readable_output = f"Domain {domain} is {status}" except KeyError: readable_output = f"Domain {domain} was not scanned. Scan it with !quttera-scan-start domain={domain}" return CommandResults( readable_output=readable_output, outputs_prefix="QutteraWebsiteMalwareScanning.report", outputs_key_field="error" ) def report_malware(client: Client, args: dict) -> CommandResults: """ Returns malware status for given url """ url = args.get("url") if not url: raise ValueError("url is missing") scanned_files = None state = None full_report = None malware = client._api_request(domain=url, method="GET", operation="malware") apikey = client.apikey base_url = client.base_url try: scanned_files = malware["report"]["scanned_files"] state = malware["report"]["state"] headers = ("url", "status", "files", "full_report") full_report = (f"{base_url}/api/v3/{apikey}/url/report/{url}",) malware_data = {"url": url, "status": state, "files": scanned_files, "full_report": full_report} readable_output = tableToMarkdown( "Malware report", malware_data, headers=headers, headerTransform=string_to_table_header, ) except KeyError: readable_output = f"Report is not ready or domain {url} was not scanned yet" return CommandResults( readable_output=readable_output, outputs_prefix="QutteraWebsiteMalwareScanning.malware", outputs_key_field="error", outputs=malware, ) def status_blacklist(client: Client, args: dict) -> CommandResults: """ Returns url blacklisting information and reputation. """ domain = args.get("domain") if not domain: raise ValueError("domain is missing") blacklist = client._api_request(domain=domain, method="GET", operation="blacklist") readable_output = f"Domain {domain} is NOT blacklisted" if blacklist["status"]["blacklisted"] == "yes": readable_output = f"Domain {domain} is blacklisted" return CommandResults( readable_output=readable_output, outputs_prefix="", outputs_key_field="QutteraWebsiteMalwareScanning.report", ) def test_module(client: Client): """Performs basic request to ensure API server is running Return True if request succeeded """ resp = client._test_module() if resp.get("errorstr") == "success": return "ok" raise DemistoException(f"Service is down: ${resp}") def main(): # pragma: no cover """main function, parses params and runs command functions""" params = demisto.params() args = demisto.args() command = demisto.command() demisto.info(f"Command called {command}") demisto.info(f"Args are {args}") client = Client(params) try: if command == "quttera-scan-start": results = scan_start(client, args) elif command == "quttera-rescan-status": results = rescan_status(client, args) elif command == "quttera-scan-report": results = scan_report(client, args) elif command == "quttera-report-malware": results = report_malware(client, args) elif command == "quttera-status-blacklist": results = status_blacklist(client, args) elif command == "test-module": results = test_module(client) else: raise ValueError(f"Command {command} is not implemented in this integration") return_results(results) except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{e!s}") if __name__ in ("__main__", "__builtin__", "builtins"): main()