Hackuity
From a war-room, query your Hackuity cockpit in order to seamlessly retrieve information related to your vulnerability stock.
Vulnerability Management · Hackuity
Details
| ID | Hackuity |
|---|---|
| Provider | Hackuity |
| Category | Vulnerability Management |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
From a war-room, query your Hackuity cockpit in order to seamlessly retrieve information related to your vulnerability stock.
This integration was integrated and tested with version 1.25.0 of Hackuity
Configure Hackuity in Cortex
| Parameter | Required |
|---|---|
| Corporate server URL | True |
| Namespace | True |
| Api key | True |
| Trust any certificate (not secure) | False |
| Use system proxy settings | 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.
hackuity-search-findings
Search for findings in Hackuity.
Base Command
hackuity-search-findings
Input
| Argument Name | Description | Required |
|---|---|---|
| asset_name | The name of the asset. | Optional |
| asset_type | The type of the asset if the asset name is specified (by default, restricts to IPs & domains). | Optional |
| attribute | An attribute value. | Optional |
| cvss_min | The minimum CVSS (included). | Optional |
| cvss_max | The maximum CVSS (excluded). | Optional |
| limit | The maximum number of items to return. Default is 20. | Optional |
| trs_min | The minimum TRS (included). | Optional |
| trs_max | The maximum TRS (excluded). | Optional |
| vuln_type | The vulnerability type (ID). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Hackuity.Findings.Asset.ID | String | The ID of the asset |
| Hackuity.Findings.Asset.Name | String | The name of the asset |
| Hackuity.Findings.Asset.Type | String | The type of the asset |
| Hackuity.Findings.Attributes | Unknown | The attributes of the finding |
| Hackuity.Findings.Score.CVSS | Number | The CVSS of the finding |
| Hackuity.Findings.Score.TRS | Number | The TRS of the finding |
| Hackuity.Findings.ID | String | The ID of the finding |
| Hackuity.Findings.Status.Ignored | Boolean | Whether the finding is ignored |
| Hackuity.Findings.Status.State | String | The state of the finding |
| Hackuity.Findings.Status.SubState | String | The sub-state of the finding |
| Hackuity.Findings.Status.LastClosedAt | Date | The date of the last time the finding was closed |
| Hackuity.Findings.VulnType.ID | String | The ID of the vulnerability type |
| Hackuity.Findings.VulnType.Name | String | The name of the vulnerability type |
Command example
!hackuity-search-findings asset_name=example.com
Context Example
{
"Hackuity": {
"Findings": [
{
"Asset": {
"ID": "NKTVm2RU4606",
"Name": "example.com",
"Type": "DOMAIN"
},
"Attributes": {
"cve_id": "CVE-2015-6550"
},
"ID": "j6SMpiorqFi1",
"Score": {
"CVSS": 10,
"TRS": 693
},
"Status": {
"Ignored": false,
"State": "CLOSED",
"SubState": "FIXED"
},
"VulnType": {
"ID": "common-vulnerability-exposure",
"Name": "Common Vulnerability and Exposure (CVE)"
}
},
{
"Asset": {
"ID": "NKTVm2RU4606",
"Name": "example.com",
"Type": "DOMAIN"
},
"Attributes": {
"cve_id": "CVE-2015-6551"
},
"ID": "ag8FkNpubY7N",
"Score": {
"CVSS": 10,
"TRS": 693
},
"Status": {
"Ignored": false,
"State": "CLOSED",
"SubState": "FIXED"
},
"VulnType": {
"ID": "common-vulnerability-exposure",
"Name": "Common Vulnerability and Exposure (CVE)"
}
}
]
}
}
Human Readable Output
Findings
Asset VulnType Attributes Score Status ID: NKTVm2RU4606
Name: example.com
Type: DOMAINID: common-vulnerability-exposure
Name: Common Vulnerability and Exposure (CVE)cve_id: CVE-2015-6550 CVSS: 10.0
TRS: 693Ignored: false
State: CLOSED
SubState: FIXEDID: NKTVm2RU4606
Name: example.com
Type: DOMAINID: common-vulnerability-exposure
Name: Common Vulnerability and Exposure (CVE)cve_id: CVE-2015-6551 CVSS: 10.0
TRS: 693Ignored: false
State: CLOSED
SubState: FIXED
hackuity-search-vulndb-vulnerabilities
Search for vulndb vulnerabilities in Hackuity.
Base Command
hackuity-search-vulndb-vulnerabilities
Input
| Argument Name | Description | Required |
|---|---|---|
| asset_name | The name of the asset. | Optional |
| asset_type | The type of the asset if the asset name is specified (by default, restricts to IPs & domains). | Optional |
| attribute | An attribute value. | Optional |
| cvss_min | The minimum CVSS (included). | Optional |
| cvss_max | The maximum CVSS (excluded). | Optional |
| limit | The maximum number of items to return. Default is 20. | Optional |
| trs_min | The minimum TRS (included). | Optional |
| trs_max | The maximum TRS (excluded). | Optional |
| vuln_type | The vulnerability type (ID). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Hackuity.Vulnerabilities.Attributes | String | The attributes of the vulnerability |
| Hackuity.Vulnerabilities.Score.CVSS | String | The CVSS of the vulnerability |
| Hackuity.Vulnerabilities.Score.TRS | String | The TRS of the vulnerability |
| Hackuity.Vulnerabilities.Description | String | The description of the vulnerability |
| Hackuity.Vulnerabilities.ID | String | The ID of the vulnerability |
| Hackuity.Vulnerabilities.Seen.First | Date | The date of the first time the vulnerability has been seen |
| Hackuity.Vulnerabilities.Findings.Total | String | The total number of findings on this vulnerability |
| Hackuity.Vulnerabilities.Findings.Open | String | The number of open findings on this vulnerability |
| Hackuity.Vulnerabilities.Findings.Closed | String | The number of closed findings on this vulnerability |
| Hackuity.Vulnerabilities.Findings.Ignored | String | The number of ignored findings on this vulnerability |
| Hackuity.Vulnerabilities.VulnType.ID | String | The ID of the vulnerability type |
| Hackuity.Vulnerabilities.VulnType.Name | String | The name of the vulnerability type |
Command example
!hackuity-search-vulndb-vulnerabilities asset_name=example.com
Context Example
{
"Hackuity": {
"Vulnerabilities": [
{
"Attributes": [
{
"key": "cve_id",
"value": "CVE-2020-0705"
}
],
"Description": "An information disclosure vulnerability exists when the Windows Network Driver Interface Specification (NDIS) improperly handles memory.To exploit this vulnerability, an attacker would first have to gain execution on the victim system, aka 'Windows Network Driver Interface Specification (NDIS) Information Disclosure Vulnerability'.",
"Findings": {
"Closed": 0,
"Ignored": 0,
"Open": 1,
"Total": 1
},
"ID": "hy#asset/NKTVm2RU4606:LWxh4Y7UpCUw",
"Score": {
"CVSS": 9,
"TRS": 636
},
"Seen": {
"First": "2021-03-03T07:56:07Z"
},
"VulnTypes": [
{
"ID": "common-vulnerability-exposure",
"Name": "Common Vulnerability and Exposure (CVE)"
}
]
},
{
"Attributes": [
{
"key": "cve_id",
"value": "CVE-2020-0958"
}
],
"Description": "An elevation of privilege vulnerability exists in Windows when the Windows kernel-mode driver fails to properly handle objects in memory, aka 'Win32k Elevation of Privilege Vulnerability'. This CVE ID is unique from CVE-2020-0956, CVE-2020-0957.",
"Findings": {
"Closed": 0,
"Ignored": 0,
"Open": 1,
"Total": 1
},
"ID": "hy#asset/NKTVm2RU4606:mTYugfvOy9yt",
"Score": {
"CVSS": 9,
"TRS": 636
},
"Seen": {
"First": "2021-03-03T07:56:07Z"
},
"VulnTypes": [
{
"ID": "common-vulnerability-exposure",
"Name": "Common Vulnerability and Exposure (CVE)"
}
]
}
]
}
}
Human Readable Output
VulnDB vulnerabilities
VulnTypes Description Attributes Score Findings Seen {‘ID’: ‘common-vulnerability-exposure’, ‘Name’: ‘Common Vulnerability and Exposure (CVE)’} An information disclosure vulnerability exists when the Windows Network Driver Interface Specification (NDIS) improperly handles memory.To exploit this vulnerability, an attacker would first have to gain execution on the victim system, aka ‘Windows Network Driver Interface Specification (NDIS) Information Disclosure Vulnerability’. {‘key’: ‘cve_id’, ‘value’: ‘CVE-2020-0705’} CVSS: 9.0
TRS: 636Total: 1
Open: 1
Closed: 0
Ignored: 0First: 2021-03-03T07:56:07Z {‘ID’: ‘common-vulnerability-exposure’, ‘Name’: ‘Common Vulnerability and Exposure (CVE)’} An elevation of privilege vulnerability exists in Windows when the Windows kernel-mode driver fails to properly handle objects in memory, aka ‘Win32k Elevation of Privilege Vulnerability’. This CVE ID is unique from CVE-2020-0956, CVE-2020-0957. {‘key’: ‘cve_id’, ‘value’: ‘CVE-2020-0958’} CVSS: 9.0
TRS: 636Total: 1
Open: 1
Closed: 0
Ignored: 0First: 2021-03-03T07:56:07Z
hackuity-search-provider-vulnerabilities
Search for provider vulnerabilities in Hackuity.
Base Command
hackuity-search-provider-vulnerabilities
Input
| Argument Name | Description | Required |
|---|---|---|
| asset_name | The name of the asset. | Optional |
| asset_type | The type of the asset if the asset name is specified (by default, restricts to IPs & domains). | Optional |
| attribute | An attribute value. | Optional |
| cvss_min | The minimum CVSS (included). | Optional |
| cvss_max | The maximum CVSS (excluded). | Optional |
| limit | The maximum number of items to return. Default is 20. | Optional |
| trs_min | The minimum TRS (included). | Optional |
| trs_max | The maximum TRS (excluded). | Optional |
| vuln_type | The vulnerability type (ID). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Hackuity.Vulnerabilities.Attributes | String | The attributes of the vulnerability |
| Hackuity.Vulnerabilities.Score.CVSS | String | The CVSS of the vulnerability |
| Hackuity.Vulnerabilities.Score.TRS | String | The TRS of the vulnerability |
| Hackuity.Vulnerabilities.Description | String | The description of the vulnerability |
| Hackuity.Vulnerabilities.ID | String | The ID of the vulnerability |
| Hackuity.Vulnerabilities.Seen.First | Date | The date of the first time the vulnerability has been seen |
| Hackuity.Vulnerabilities.Findings.Total | String | The total number of findings on this vulnerability |
| Hackuity.Vulnerabilities.Findings.Open | String | The number of open findings on this vulnerability |
| Hackuity.Vulnerabilities.Findings.Closed | String | The number of closed findings on this vulnerability |
| Hackuity.Vulnerabilities.Findings.Ignored | String | The number of ignored findings on this vulnerability |
| Hackuity.Vulnerabilities.VulnType.ID | String | The ID of the vulnerability type |
| Hackuity.Vulnerabilities.VulnType.Name | String | The name of the vulnerability type |
Command example
!hackuity-search-provider-vulnerabilities asset_name=example.com
Context Example
{
"Hackuity": {
"Vulnerabilities": [
{
"Attributes": [],
"Description": "KB4561669: Windows 7 and Windows Server 2008 R2 June 2020 Security Update",
"Findings": {
"Closed": 2,
"Ignored": 0,
"Open": 8,
"Total": 10
},
"ID": "hy#asset/NKTVm2RU4606:QHBzm5XEkjIp",
"Score": {
"CVSS": 9,
"TRS": 636
},
"Seen": {
"First": "2021-03-03T07:56:07Z"
},
"VulnTypes": [
{
"ID": "common-vulnerability-exposure",
"Name": "Common Vulnerability and Exposure (CVE)"
}
]
},
{
"Attributes": [],
"Description": "KB4541500: Windows 7 and Windows Server 2008 R2 March 2020 Security Update",
"Findings": {
"Closed": 1,
"Ignored": 0,
"Open": 15,
"Total": 16
},
"ID": "hy#asset/NKTVm2RU4606:rX8JQJaETpoq",
"Score": {
"CVSS": 9,
"TRS": 636
},
"Seen": {
"First": "2021-03-03T07:56:07Z"
},
"VulnTypes": [
{
"ID": "common-vulnerability-exposure",
"Name": "Common Vulnerability and Exposure (CVE)"
}
]
}
]
}
}
Human Readable Output
Provider vulnerabilities
VulnTypes Description Attributes Score Findings Seen {‘ID’: ‘common-vulnerability-exposure’, ‘Name’: ‘Common Vulnerability and Exposure (CVE)’} KB4561669: Windows 7 and Windows Server 2008 R2 June 2020 Security Update CVSS: 9.0
TRS: 636Total: 10
Open: 8
Closed: 2
Ignored: 0First: 2021-03-03T07:56:07Z {‘ID’: ‘common-vulnerability-exposure’, ‘Name’: ‘Common Vulnerability and Exposure (CVE)’} KB4541500: Windows 7 and Windows Server 2008 R2 March 2020 Security Update CVSS: 9.0
TRS: 636Total: 16
Open: 15
Closed: 1
Ignored: 0First: 2021-03-03T07:56:07Z
hackuity-dashboard-widgets
List the widgets in the default dashboard of the user.
Base Command
hackuity-dashboard-widgets
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Hackuity.Dashboard.Widgets.ID | String | The ID of the widget |
| Hackuity.Dashboard.Widgets.Params | Unknown | The configuration of the widget |
| Hackuity.Dashboard.Widgets.Type | String | The type of the widget |
Command example
#### Context Example
```json
{
"Hackuity": {
"Dashboard": {
"Widgets": [
{
"ID": "abcd3fgh1jklmn0pqrstuv",
"Params": {
"nbDaysToCompare": 28,
"withTotal": true
},
"Type": "ASSETS_OVERVIEW"
}
]
}
}
}
Human Readable Output
Dashboard widgets
ID Type Params abcd3fgh1jklmn0pqrstuv ASSETS_OVERVIEW nbDaysToCompare: 28
withTotal: true
hackuity-dashboard-data
Get the data of a dashboard widget
Base Command
hackuity-dashboard-data
Input
| Argument Name | Description | Required |
|---|---|---|
| widget_id | The ID of the widget. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Hackuity.Dashboard.Data | Unknown | The data of the widget |
Command example
!hackuity-dashboard-data widget_id=abcd3fgh1jklmn0pqrstuv
Context Example
{
"Hackuity": {
"Dashboard": {
"Data": {
"abcd3fgh1jklmn0pqrstuv": {
"currentNbAssets": 456,
"previousNbAssets": 123
}
}
}
}
}
Human Readable Output
Dashboard widget data (abcd3fgh1jklmn0pqrstuv)
currentNbAssets previousNbAssets 456 123
Configuration parameters
url— Corporate server URL (required)namespace— Namespace (required)login— Login (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (5)
-
hackuity-dashboard-dataGet the data of a dashboard widget.
-
hackuity-dashboard-widgetsList the widgets in the default dashboard of the user.
-
hackuity-search-findingsSearch for findings in Hackuity.
-
hackuity-search-provider-vulnerabilitiesSearch for provider vulnerabilities in Hackuity.
-
hackuity-search-vulndb-vulnerabilitiesSearch for vulndb vulnerabilities in Hackuity.
import traceback from collections.abc import Callable from functools import partial from typing import Any, TypeVar import dateutil.parser import demistomock as demisto # noqa: F401 import requests import urllib3 from CommonServerPython import * # noqa: F401 from funcy import get_in, identity, set_in from CommonServerUserPython import * # noqa # Disable insecure warnings urllib3.disable_warnings() # pylint: disable=no-member """ CONSTANTS """ DATE_FORMAT = r"%Y-%m-%dT%H:%M:%SZ" USER_AGENT = f"XSOAR/{get_demisto_version_as_str()} - Hackuity/1.0.0 - {get_integration_name()}" T = TypeVar("T") """ CLIENT CLASS """ class Client(BaseClient): def __init__( self, url: str, namespace: str, apikey: str, verify: bool = True, proxy: bool = True, ): self.namespace = namespace self.apikey = apikey base_url = f"{url.rstrip('/')}/api/v1" super().__init__(base_url=base_url, verify=verify, proxy=proxy) @staticmethod def http_error_handler(res: requests.Response): # pragma: no cover # adapted from default error handler in BaseClient._http_request err_msg = f"Error in API call [{res.status_code}] - {res.reason}" try: error_entry = res.json() # json response if (reason := error_entry.get("reason")) and (correlationId := error_entry.get("correlationId")): # hackuity-formatted error message err_msg += f"\n{reason}\ncorrelationId={correlationId}" else: # other error messages err_msg += f"\n{json.dumps(error_entry)}" except ValueError: # non-json responses err_msg += f"\n{res.text}" raise DemistoException(err_msg, res=res) def http_request( self, *, method: str = "GET", url_suffix: str = "", json_data: dict[str, Any] = None, resp_type: str = "json", ): # pragma: no cover headers = { "content-type": "application/json", "user-agent": USER_AGENT, "authorization": f"Basic {self.apikey}", } demisto.info(f"API call: HTTP {method} on {url_suffix}") return self._http_request( method=method, url_suffix=url_suffix, headers=headers, json_data=json_data, resp_type=resp_type, error_handler=self.http_error_handler, ) def get_user_id(self) -> str: """Get the id of the user""" integration_context = get_integration_context() user_id = integration_context.get("user_id", {}) if not user_id: user_id = self.fetch_user()["userId"] integration_context["user_id"] = user_id set_integration_context(integration_context) return user_id def get_vulnerability_name(self, vuln_id: str) -> str | None: """Get the name of a vulnerability from its id""" # read from cache integration_context = get_integration_context() vulnerability_names = integration_context.get("vulnerability_names", {}) if vuln_id in vulnerability_names: return vulnerability_names[vuln_id] # fetch vulnerability try: result = self.http_request(url_suffix=f"/namespaces/N000000101010/vulnerabilityTypes/{vuln_id}") name = result["i18n"]["en"]["name"] except DemistoException: return None # failed to get the name (don't save it) # save into cache vulnerability_names[vuln_id] = name integration_context["vulnerability_names"] = vulnerability_names set_integration_context(integration_context) return name def compute_asset_id(self, asset_name: str, asset_type: str | None) -> str: request: dict[str, Any] = {"assetAbsoluteNames": [asset_name]} if asset_type: request["assetTypes"] = [asset_type.upper()] response = self.http_request( method="POST", url_suffix="/search/assets", json_data=request, ) for asset in response.get("searchAssets", []): if asset["assetAbsoluteName"] == asset_name: return asset["assetId"] raise ValueError("Asset not found") def fetch_dashboard_widgets(self) -> list[dict[str, Any]]: response = self.http_request( method="GET", url_suffix=(f"/namespaces/{self.namespace}/dashboards/{self.get_user_id()}"), ) return response.get("widgets", []) def fetch_dashboard_widget_data(self, *, widget_id: str) -> dict: try: response = self.http_request( method="GET", url_suffix=(f"/namespaces/{self.namespace}/dashboards/{self.get_user_id()}/widgets/{widget_id}/data"), ) except DemistoException as e: if e.res.status_code == 404: raise ValueError(f"Widget not found: {widget_id}") raise return response.get("payload", {}) def fetch_user(self) -> dict[str, Any]: """Fetch current user. Can be used for testing access token.""" return self.http_request(url_suffix="/echo/user") def fetch_findings( self, *, asset_name: str | None, asset_type: str | None, attribute: str | None, cvss_min: float | None, cvss_max: float | None, limit: int, trs_min: int | None, trs_max: int | None, vuln_type: str | None, ) -> list[dict[str, Any]]: request: dict[str, Any] = { "findingStatusScoreEnvironmentalMin": cvss_min, "findingStatusScoreEnvironmentalMax": cvss_max, "findingStatusScoreHyScoreMin": trs_min, "findingStatusScoreHyScoreMax": trs_max, "limit": limit, "offset": 0, "sortFields": [ {"fieldName": "findingStatus.score.hyScore", "desc": True}, {"fieldName": "findingStatus.score.environmental", "desc": True}, ], } if asset_name: try: asset_id = self.compute_asset_id(asset_name, asset_type) except ValueError: return [] # asset not found so no findings request["assetIds"] = [asset_id] elif asset_type: raise ValueError("Asset type filter requires asset name") if vuln_type: request["vulnTypeIds"] = [vuln_type] if attribute: request["attributesValues"] = [{"value": attribute}] response = self.http_request( method="POST", url_suffix="/search/findings", json_data=request, ) return response.get("searchFindings", []) def fetch_aggfindings( self, *, asset_name: str | None, asset_type: str | None, attribute: str | None, cvss_min: float | None, cvss_max: float | None, hy_global_only: bool, limit: int, trs_min: int | None, trs_max: int | None, vuln_type: str | None, ) -> list[dict[str, Any]]: request: dict[str, Any] = { "aggFindingNodeStatusUnignoredOpenEnvironmentalMaxMin": cvss_min, "aggFindingNodeStatusUnignoredOpenEnvironmentalMaxMax": cvss_max, "aggFindingNodeStatusUnignoredOpenHyScoreMaxMin": trs_min, "aggFindingNodeStatusUnignoredOpenHyScoreMaxMax": trs_max, "limit": limit, "offset": 0, "sortFields": [ { "fieldName": "nodeStatus.unIgnoredOpen.total.hyScore.max", "desc": True, }, { "fieldName": "nodeStatus.unIgnoredOpen.total.environmental.max", "desc": True, }, ], } if hy_global_only: request["hyGlobalOnly"] = True else: request["byProviderOnly"] = True request["excludedTypes"] = ["commonVulnerabilityExposure", "hyCsvInfos"] if asset_name: try: asset_id = self.compute_asset_id(asset_name, asset_type) except ValueError: return [] # asset not found so no aggfindings request["nodeId"] = f"hy#asset/{asset_id}" elif asset_type: raise ValueError("Asset type filter requires asset name") if vuln_type: request["vulnTypeIds"] = [vuln_type] if attribute: request["aggFindingAttributesFlats"] = [{"value": attribute}] response = self.http_request( method="POST", url_suffix="/search/aggFindings", json_data=request, ) return response.get("searchAggFindings", []) """ HELPER FUNCTIONS """ def remap_item( item: dict[str, Any], mappings: list[ tuple[list[str], list[str]] | tuple[list[str], list[str], Any] | tuple[list[str], list[str], Any, Callable[[Any], Any]] ], ) -> dict[str, Any]: output: dict[str, Any] = {} for mapping in mappings: default = None formatter = identity # mypy does not understand the len checks hence the `type: ignore` comments # see https://github.com/python/mypy/issues/1178 source = mapping[0] destination = mapping[1] if len(mapping) >= 3: default = mapping[2] # type: ignore if len(mapping) >= 4: formatter = mapping[3] # type: ignore output = set_in(output, destination, formatter(get_in(item, source, default))) return output def get_first_value(item: dict[str, T] | None) -> T | None: if item: return next(iter(item.values())) return None def format_date(date_str: str | None) -> str | None: """Format a date from API""" if date_str: return datetime.strftime(dateutil.parser.parse(date_str), DATE_FORMAT) return None def none_or_apply(item: str | None, apply: Callable[[str], T]) -> T | None: if item is None: return None return apply(item) """ COMMAND FUNCTIONS """ def test_module(client: Client) -> str: """Tests API connectivity and authentication""" # excption formating in case of authentication error # is already done in client.http_error_handler set_integration_context({}) # reset cache client.fetch_user() return "ok" def hackuity_search_findings_command(client: Client, args: dict[str, Any]) -> CommandResults: asset_name = args.get("asset_name") asset_type = args.get("asset_type") attribute = args.get("attribute") limit = int(args["limit"]) cvss_min = none_or_apply(args.get("cvss_min"), float) cvss_max = none_or_apply(args.get("cvss_max"), float) vuln_type = args.get("vuln_type") trs_min = none_or_apply(args.get("trs_min"), int) trs_max = none_or_apply(args.get("trs_max"), int) raw_response = client.fetch_findings( asset_name=asset_name, asset_type=asset_type, attribute=attribute, cvss_min=cvss_min, cvss_max=cvss_max, limit=limit, trs_min=trs_min, trs_max=trs_max, vuln_type=vuln_type, ) outputs = [] for finding in raw_response: output = remap_item( finding, [ ( ["assetId"], ["Asset", "ID"], ), ( ["assetAbsoluteName"], ["Asset", "Name"], ), ( ["assetType"], ["Asset", "Type"], ), ( ["findingAttributes"], ["Attributes"], {}, ), ( ["findingStatus", "score", "environmental"], ["Score", "CVSS"], 0.0, ), ( ["findingId"], ["ID"], ), ( ["findingStatus", "ignored"], ["Status", "Ignored"], False, ), ( ["findingStatus", "state"], ["Status", "State"], ), ( ["findingStatus", "subState"], ["Status", "SubState"], ), ( ["findingStatus", "lastClosedAt"], ["Status", "LastClosedAt"], None, format_date, ), ( ["findingStatus", "score", "hyScore"], ["Score", "TRS"], 0, ), ( ["vulnTypeId"], ["VulnType", "ID"], ), ], ) output["VulnType"]["Name"] = client.get_vulnerability_name(output["VulnType"]["ID"]) outputs.append(output) return CommandResults( outputs_prefix="Hackuity.Findings", outputs=outputs, readable_output=tableToMarkdown( "Findings", outputs, headers=["Asset", "VulnType", "Attributes", "Score", "Status"], ), ) def hackuity_search_vulnerabilities_command(client: Client, args: dict[str, Any], hy_global_only: bool) -> CommandResults: asset_name = args.get("asset_name") asset_type = args.get("asset_type") attribute = args.get("attribute") limit = int(args["limit"]) cvss_min = none_or_apply(args.get("cvss_min"), float) cvss_max = none_or_apply(args.get("cvss_max"), float) vuln_type = args.get("vuln_type") trs_min = none_or_apply(args.get("trs_min"), int) trs_max = none_or_apply(args.get("trs_max"), int) raw_response = client.fetch_aggfindings( asset_name=asset_name, asset_type=asset_type, attribute=attribute, cvss_min=cvss_min, cvss_max=cvss_max, hy_global_only=hy_global_only, limit=limit, trs_min=trs_min, trs_max=trs_max, vuln_type=vuln_type, ) outputs = [] for aggfinding in raw_response: output = remap_item( aggfinding, [ ( [ "aggExtAttribute", "sharedDetailsLocal", "aggFindingAttributes", ], ["Attributes"], [], ), ( [ "nodeStatus", "unIgnoredOpen", "total", "environmental", "max", ], ["Score", "CVSS"], 0.0, ), ( ["aggExtAttribute", "sharedDetailsLocal", "title"], ["Description"], ), ( ["id"], ["ID"], ), ( ["aggExtAttribute", "firstDeliveredAt"], ["Seen", "First"], None, format_date, ), ( ["nodeStatus", "unIgnoredOpen", "total", "hyScore", "max"], ["Score", "TRS"], 0, ), ( ["nodeStatus", "total", "nb"], ["Findings", "Total"], 0, ), ( ["nodeStatus", "unIgnoredOpen", "total", "nb"], ["Findings", "Open"], 0, ), ( ["nodeStatus", "unIgnoredClosed", "total", "nb"], ["Findings", "Closed"], 0, ), ( ["nodeStatus", "ignored", "nb"], ["Findings", "Ignored"], 0, ), ], ) output["VulnTypes"] = [ { "ID": vuln_type, "Name": client.get_vulnerability_name(vuln_type), } for vuln_type in get_in(aggfinding, ["aggExtAttribute", "vulnTypeIds"], []) ] outputs.append(output) return CommandResults( outputs_prefix="Hackuity.Vulnerabilities", outputs=outputs, readable_output=tableToMarkdown( f"{'VulnDB' if hy_global_only else 'Provider'} vulnerabilities", outputs, headers=[ "VulnTypes", "Description", *(["Attributes"] if hy_global_only else []), "Score", "Findings", "Seen", ], ), ) def hackuity_dashboard_widgets_command(client: Client, args: dict[str, Any]) -> CommandResults: raw_response = client.fetch_dashboard_widgets() outputs = [ remap_item( widget, [ ( ["id"], ["ID"], ), ( ["params"], ["Params"], None, get_first_value, ), ( ["type"], ["Type"], ), ], ) for widget in raw_response ] return CommandResults( outputs_prefix="Hackuity.Dashboard.Widgets", outputs=outputs, readable_output=tableToMarkdown( "Dashboard widgets", outputs, headers=["ID", "Type", "Params"], ), ) def hackuity_dashboard_data_command(client: Client, args: dict[str, Any]) -> CommandResults: widget_id = args["widget_id"] try: raw_response = client.fetch_dashboard_widget_data(widget_id=widget_id) except ValueError: return CommandResults( readable_output=f"Widget not found: `{widget_id}`.", ) outputs = get_first_value(raw_response) return CommandResults( outputs_prefix=f"Hackuity.Dashboard.Data.{widget_id}", outputs=outputs, readable_output=tableToMarkdown( f"Dashboard widget data ({widget_id})", outputs, ), ) """ MAIN FUNCTION """ def main() -> None: # pragma: no cover """main function, parses params and runs command functions""" params = demisto.params() url = params.get("url") namespace = params.get("namespace") apikey = params.get("login", {}).get("password") verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) commands: dict[str, Callable[[Client, dict[str, str]], CommandResults]] = { "hackuity-dashboard-data": hackuity_dashboard_data_command, "hackuity-dashboard-widgets": hackuity_dashboard_widgets_command, "hackuity-search-findings": hackuity_search_findings_command, "hackuity-search-provider-vulnerabilities": partial(hackuity_search_vulnerabilities_command, hy_global_only=False), "hackuity-search-vulndb-vulnerabilities": partial(hackuity_search_vulnerabilities_command, hy_global_only=True), } command = demisto.command() demisto.debug(f"Command being called is {command}") try: client = Client( url=url, namespace=namespace, apikey=apikey, verify=verify_certificate, proxy=proxy, ) if command == "test-module": return_results(test_module(client)) elif command in commands: return_results(commands[command](client, demisto.args())) else: raise NotImplementedError(f"{command} is not an existing command") # Log exceptions and return errors except Exception as e: demisto.error(traceback.format_exc()) # print the traceback return_error(f"Failed to execute {command} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): # pragma: no cover main()