ps-banshee 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- banshee/__init__.py +16 -0
- banshee/_version.py +15 -0
- banshee/app_config.py +40 -0
- banshee/branding.py +36 -0
- banshee/commands/__init__.py +12 -0
- banshee/commands/args.py +44 -0
- banshee/commands/cmd_classic_alerts.py +175 -0
- banshee/commands/cmd_entity.py +54 -0
- banshee/commands/cmd_ioc.py +236 -0
- banshee/commands/cmd_lists.py +293 -0
- banshee/commands/cmd_pcap_enrich.py +74 -0
- banshee/commands/cmd_playbook_alerts.py +297 -0
- banshee/commands/cmd_risklist.py +239 -0
- banshee/commands/cmd_rules.py +185 -0
- banshee/commands/epilogs.py +602 -0
- banshee/commands/errors.py +20 -0
- banshee/detection_rules/__init__.py +14 -0
- banshee/detection_rules/detection_rules_search.py +210 -0
- banshee/entity_match/__init__.py +17 -0
- banshee/entity_match/constants.py +168 -0
- banshee/entity_match/errors.py +20 -0
- banshee/entity_match/lookup.py +42 -0
- banshee/entity_match/search.py +52 -0
- banshee/formatters/__init__.py +12 -0
- banshee/formatters/output_formatters.py +45 -0
- banshee/fusion_files/__init__.py +14 -0
- banshee/fusion_files/feed_stat.py +66 -0
- banshee/indicators/__init__.py +18 -0
- banshee/indicators/constants.py +103 -0
- banshee/indicators/helpers.py +24 -0
- banshee/indicators/lookup.py +92 -0
- banshee/indicators/rules.py +82 -0
- banshee/indicators/search.py +80 -0
- banshee/indicators/soar.py +57 -0
- banshee/legacy_alerts/__init__.py +12 -0
- banshee/legacy_alerts/alert_lookup.py +38 -0
- banshee/legacy_alerts/alert_search.py +82 -0
- banshee/legacy_alerts/alert_update.py +80 -0
- banshee/legacy_alerts/constants.py +35 -0
- banshee/legacy_alerts/rules_search.py +46 -0
- banshee/lists/__init__.py +24 -0
- banshee/lists/fetch_list.py +35 -0
- banshee/lists/list_add.py +53 -0
- banshee/lists/list_bulk_add.py +85 -0
- banshee/lists/list_bulk_remove.py +85 -0
- banshee/lists/list_clear.py +31 -0
- banshee/lists/list_create.py +46 -0
- banshee/lists/list_entities.py +42 -0
- banshee/lists/list_entries.py +37 -0
- banshee/lists/list_helpers.py +100 -0
- banshee/lists/list_info.py +39 -0
- banshee/lists/list_remove.py +46 -0
- banshee/lists/list_search.py +57 -0
- banshee/lists/list_status.py +37 -0
- banshee/main.py +118 -0
- banshee/pcap_enrich/__init__.py +14 -0
- banshee/pcap_enrich/constants.py +18 -0
- banshee/pcap_enrich/helpers.py +64 -0
- banshee/pcap_enrich/pcap_enrich.py +251 -0
- banshee/playbook_alerts/__init__.py +14 -0
- banshee/playbook_alerts/alert_lookup.py +49 -0
- banshee/playbook_alerts/alert_search.py +62 -0
- banshee/playbook_alerts/alert_update.py +107 -0
- banshee/playbook_alerts/constants.py +75 -0
- banshee/risklist/__init__.py +17 -0
- banshee/risklist/risklist_create.py +154 -0
- banshee/risklist/risklist_fetch.py +89 -0
- banshee/risklist/risklist_stat.py +58 -0
- banshee/threat/__init__.py +16 -0
- banshee/threat/constants.py +99 -0
- banshee/threat/endpoints.py +20 -0
- banshee/threat/fetch_threat_map.py +59 -0
- ps_banshee-1.1.0.dist-info/METADATA +146 -0
- ps_banshee-1.1.0.dist-info/RECORD +78 -0
- ps_banshee-1.1.0.dist-info/WHEEL +5 -0
- ps_banshee-1.1.0.dist-info/entry_points.txt +2 -0
- ps_banshee-1.1.0.dist-info/licenses/LICENSE +21 -0
- ps_banshee-1.1.0.dist-info/top_level.txt +1 -0
banshee/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
"""Base package for PS Banshee."""
|
|
15
|
+
|
|
16
|
+
from ._version import __version__ as version
|
banshee/_version.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
from importlib.metadata import version
|
|
14
|
+
|
|
15
|
+
__version__ = version('ps-banshee')
|
banshee/app_config.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from psengine.config import Config
|
|
16
|
+
from pydantic import ValidationError
|
|
17
|
+
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
from .commands.errors import InitConfigError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def config_init(cmd: str, rf_token: str = None, no_ssl_verify: bool = False) -> Config:
|
|
23
|
+
"""Global configuration for the CLI.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
cmd (str): The command name + sub command, used to generate the app_id,
|
|
27
|
+
typically will be the name one of the banshee commands,
|
|
28
|
+
confor example: 'ca-search', or 'entity-lookup'.
|
|
29
|
+
rf_token (str, optional): The Recorded Future API token.
|
|
30
|
+
no_ssl_verify (bool, optional): Disable SSL verification.
|
|
31
|
+
"""
|
|
32
|
+
# invert no_ssl_verify
|
|
33
|
+
ssl_verify = not no_ssl_verify
|
|
34
|
+
app_id = f'banshee_{cmd}/{__version__}'
|
|
35
|
+
try:
|
|
36
|
+
Config.init(rf_token=rf_token, app_id=app_id, client_ssl_verify=ssl_verify)
|
|
37
|
+
except ValidationError as e:
|
|
38
|
+
if 'rf_token' in e.errors()[0]['loc']:
|
|
39
|
+
raise InitConfigError('Invalid Recorded Future API key') # noqa: B904
|
|
40
|
+
raise InitConfigError(e.errors()[0]['msg']) # noqa: B904
|
banshee/branding.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from typer import Typer
|
|
16
|
+
|
|
17
|
+
BRANDING = ':rocket: \033[93mBrought to you by the Cyber Security Engineers at Recorded Future\033[0m :rocket:' # noqa: E501
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def banshee_cmd(app: Typer, help_: str, epilog: str, *args, **kwargs):
|
|
21
|
+
"""Main decorator create banshee commands.
|
|
22
|
+
Under the hood, it adds branding to the help text of the command.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
app (Typer): The Typer app instance
|
|
27
|
+
help_ (str): The help text for the command
|
|
28
|
+
epilog (str): The epilog text for the command
|
|
29
|
+
*args: Additional arguments to pass to the command
|
|
30
|
+
**kwargs: Additional keyword arguments to pass to the command
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Typer: The updated Typer app instance
|
|
34
|
+
"""
|
|
35
|
+
help_ += f'\n\n{BRANDING}'
|
|
36
|
+
return app.command(help=help_, epilog=epilog, *args, **kwargs) # noqa: B026
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
banshee/commands/args.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from typing import Annotated, Optional
|
|
15
|
+
|
|
16
|
+
from typer import Option
|
|
17
|
+
|
|
18
|
+
################################
|
|
19
|
+
# Global options / arguments
|
|
20
|
+
################################
|
|
21
|
+
|
|
22
|
+
# How to use: api_key: RF_API_KEY = None
|
|
23
|
+
OPT_RF_API_KEY = Annotated[
|
|
24
|
+
Optional[str],
|
|
25
|
+
Option(
|
|
26
|
+
'--api-key', '-k', help='Recorded Future API Key', envvar='RF_TOKEN', show_default=False
|
|
27
|
+
),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# How to use: pretty: PRETTY_PRINT = False
|
|
31
|
+
OPT_PRETTY_PRINT = Annotated[
|
|
32
|
+
bool, Option('--pretty', '-p', help='Pretty print the results in a human readable format')
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
OPT_NO_SSL_VERIFY = Annotated[
|
|
37
|
+
Optional[bool],
|
|
38
|
+
Option(
|
|
39
|
+
'--no-ssl-verify',
|
|
40
|
+
'-s',
|
|
41
|
+
help="""Disable SSL Verification. Useful when using proxies. To
|
|
42
|
+
utilize a proxy set the environment variable HTTP_PROXY or HTTPS_PROXY.""",
|
|
43
|
+
),
|
|
44
|
+
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
|
|
18
|
+
from typer import Argument, BadParameter, Option, Typer
|
|
19
|
+
|
|
20
|
+
from ..branding import banshee_cmd
|
|
21
|
+
from ..legacy_alerts.alert_lookup import lookup_alert
|
|
22
|
+
from ..legacy_alerts.alert_search import search_alerts
|
|
23
|
+
from ..legacy_alerts.alert_update import update_alerts
|
|
24
|
+
from ..legacy_alerts.constants import AlertStatus
|
|
25
|
+
from ..legacy_alerts.rules_search import search_alert_rules
|
|
26
|
+
from .args import OPT_PRETTY_PRINT
|
|
27
|
+
from .epilogs import (
|
|
28
|
+
EPILOG_ALERT_LOOKUP,
|
|
29
|
+
EPILOG_ALERT_RULES_SEARCH,
|
|
30
|
+
EPILOG_ALERT_SEARCH,
|
|
31
|
+
EPILOG_ALERT_UPDATE,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
CMD_NAME = 'ca'
|
|
35
|
+
CMD_HELP = 'Search and lookup Classic Alerts'
|
|
36
|
+
CMD_RICH_HELP = 'Recorded Future Classic Alerts'
|
|
37
|
+
|
|
38
|
+
app = Typer(no_args_is_help=True)
|
|
39
|
+
|
|
40
|
+
ALERT_ID_INVALID_MSG = "Alert ID '{}' is not valid. Alert ID should be at least 6 characters long." # noqa: E501
|
|
41
|
+
|
|
42
|
+
###################################
|
|
43
|
+
# Callbacks
|
|
44
|
+
###################################
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def validate_alert_id(alert_id: str):
|
|
48
|
+
# if value is less than 6 char long
|
|
49
|
+
if len(alert_id) < 6:
|
|
50
|
+
raise BadParameter(ALERT_ID_INVALID_MSG.format(alert_id))
|
|
51
|
+
|
|
52
|
+
return alert_id
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def parse_alert_ids_input(value: list[str]):
|
|
56
|
+
if not value:
|
|
57
|
+
raise BadParameter('No Alert IDs supplied')
|
|
58
|
+
|
|
59
|
+
alert_ids = value
|
|
60
|
+
if isinstance(value, str):
|
|
61
|
+
alert_ids = [x for x in re.split(r'[\s]+', value) if x]
|
|
62
|
+
|
|
63
|
+
if not len(alert_ids):
|
|
64
|
+
raise BadParameter('No Alert IDs provided')
|
|
65
|
+
|
|
66
|
+
# Now check that each ID is valid
|
|
67
|
+
for alert_id in alert_ids:
|
|
68
|
+
validate_alert_id(alert_id)
|
|
69
|
+
|
|
70
|
+
return alert_ids
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def parse_triggered(value: str):
|
|
74
|
+
if not value.startswith('[') and not value.startswith('('):
|
|
75
|
+
value = f'-{value.strip()}'
|
|
76
|
+
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
###################################
|
|
81
|
+
# Commands
|
|
82
|
+
###################################
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@banshee_cmd(app=app, help_='Lookup a single Classic Alert', epilog=EPILOG_ALERT_LOOKUP)
|
|
86
|
+
def lookup(
|
|
87
|
+
alert_id: Annotated[
|
|
88
|
+
str, Argument(help='Alert ID to lookup', callback=validate_alert_id, show_default=False)
|
|
89
|
+
],
|
|
90
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
91
|
+
):
|
|
92
|
+
lookup_alert(id_=alert_id, pretty=pretty)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@banshee_cmd(app=app, help_='Search for Classic Alerts', epilog=EPILOG_ALERT_SEARCH)
|
|
96
|
+
def search(
|
|
97
|
+
triggered: Annotated[
|
|
98
|
+
str,
|
|
99
|
+
Option(
|
|
100
|
+
'--triggered',
|
|
101
|
+
'-t',
|
|
102
|
+
callback=parse_triggered,
|
|
103
|
+
help='Filter on triggered time, e.g. 1d; 12h; [2024-08-01, 2024-08-14]; [2024-09-23 12:03:58.000, 2024-09-23 12:03:58.567)', # noqa: E501
|
|
104
|
+
show_default=True,
|
|
105
|
+
),
|
|
106
|
+
] = '1d',
|
|
107
|
+
alert_rules: Annotated[
|
|
108
|
+
list[str],
|
|
109
|
+
Option('--rule', '-r', help='Filter by an alert rule name (freetext)', show_default=False),
|
|
110
|
+
] = None,
|
|
111
|
+
status: Annotated[
|
|
112
|
+
AlertStatus, Option('-s', '--status', help='Filter by alert status', show_default=False)
|
|
113
|
+
] = None,
|
|
114
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
115
|
+
):
|
|
116
|
+
search_alerts(
|
|
117
|
+
triggered=triggered,
|
|
118
|
+
alert_rules=alert_rules,
|
|
119
|
+
status=status,
|
|
120
|
+
pretty=pretty,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@banshee_cmd(app=app, help_='Search Classic Alert rules', epilog=EPILOG_ALERT_RULES_SEARCH)
|
|
125
|
+
def rules(
|
|
126
|
+
freetext: Annotated[str, Argument(help='Freetext to search in alert rules')] = None,
|
|
127
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
128
|
+
):
|
|
129
|
+
search_alert_rules(pretty=pretty, freetext=freetext)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@banshee_cmd(app=app, help_='Update a classic alert', epilog=EPILOG_ALERT_UPDATE)
|
|
133
|
+
def update(
|
|
134
|
+
alert_ids: list[str] = Argument( # noqa: B008
|
|
135
|
+
... if sys.stdin.isatty() else None, # noqa: B008
|
|
136
|
+
show_default=False,
|
|
137
|
+
help='One or more whitespace separated Alert ID',
|
|
138
|
+
),
|
|
139
|
+
status: Annotated[
|
|
140
|
+
AlertStatus, Option('-s', '--status', help='New alert status', show_default=False)
|
|
141
|
+
] = None,
|
|
142
|
+
note: Annotated[str, Option('-n', '--note', help='Add a text note', show_default=False)] = None,
|
|
143
|
+
note_append: Annotated[
|
|
144
|
+
bool,
|
|
145
|
+
Option(
|
|
146
|
+
'-A',
|
|
147
|
+
'--append',
|
|
148
|
+
help='Append to the existing text note, instead of overwriting it.',
|
|
149
|
+
show_default=True,
|
|
150
|
+
),
|
|
151
|
+
] = False,
|
|
152
|
+
assignee: Annotated[
|
|
153
|
+
str,
|
|
154
|
+
Option(
|
|
155
|
+
'--assignee',
|
|
156
|
+
'-a',
|
|
157
|
+
help='New user to assign the alert(s) to. Accepts uhash or email address of the user, for example: uhash:3aXZxdkM12; analyst@acme.com', # noqa: E501
|
|
158
|
+
show_default=False,
|
|
159
|
+
),
|
|
160
|
+
] = None,
|
|
161
|
+
):
|
|
162
|
+
if alert_ids is None:
|
|
163
|
+
alert_ids = sys.stdin.read()
|
|
164
|
+
|
|
165
|
+
parsed_ids = parse_alert_ids_input(alert_ids)
|
|
166
|
+
|
|
167
|
+
if status is None and note is None and assignee is None:
|
|
168
|
+
raise BadParameter('At least one of --status, --note or --assignee must be privded.')
|
|
169
|
+
|
|
170
|
+
if note_append and note is None:
|
|
171
|
+
raise BadParameter('note argument must be provided when append option is set')
|
|
172
|
+
|
|
173
|
+
update_alerts(
|
|
174
|
+
alert_ids=parsed_ids, status=status, note=note, note_append=note_append, assignee=assignee
|
|
175
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
|
|
16
|
+
from typer import Argument, Option, Typer
|
|
17
|
+
|
|
18
|
+
from ..branding import banshee_cmd
|
|
19
|
+
from ..entity_match import EntityType, entity_lookup, entity_search
|
|
20
|
+
from .args import OPT_PRETTY_PRINT
|
|
21
|
+
from .epilogs import EPILOG_ENTITY_LOOKUP, EPILOG_ENTITY_SEARCH
|
|
22
|
+
|
|
23
|
+
CMD_NAME = 'entity'
|
|
24
|
+
CMD_HELP = 'Search and lookup entities'
|
|
25
|
+
CMD_RICH_HELP = 'Entity Match'
|
|
26
|
+
|
|
27
|
+
app = Typer(no_args_is_help=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@banshee_cmd(app=app, help_='Lookup an entity by its ID', epilog=EPILOG_ENTITY_LOOKUP)
|
|
31
|
+
def lookup(
|
|
32
|
+
entity_id: str = Argument(show_default=False, help='ID of the entity to lookup'),
|
|
33
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
34
|
+
):
|
|
35
|
+
entity_lookup(entity_id=entity_id, pretty=pretty)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@banshee_cmd(
|
|
39
|
+
app=app, help_='Search entities by name and optically by type', epilog=EPILOG_ENTITY_SEARCH
|
|
40
|
+
)
|
|
41
|
+
def search(
|
|
42
|
+
name: str = Argument(show_default=False, help='Name of the entity to search for'),
|
|
43
|
+
type_: Annotated[
|
|
44
|
+
list[EntityType],
|
|
45
|
+
Option(
|
|
46
|
+
'-t', '--type', help='One or more type of the entity to search for', show_default=False
|
|
47
|
+
),
|
|
48
|
+
] = None,
|
|
49
|
+
limit: Annotated[
|
|
50
|
+
int, Option('-l', '--limit', help='Limit number of results', min=1, max=100)
|
|
51
|
+
] = 100,
|
|
52
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
53
|
+
):
|
|
54
|
+
entity_search(name=name, type_=type_, limit=limit, pretty=pretty)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Annotated, Optional
|
|
17
|
+
|
|
18
|
+
from typer import Argument, BadParameter, Option, Typer
|
|
19
|
+
|
|
20
|
+
from ..branding import banshee_cmd
|
|
21
|
+
from ..indicators import IOCType, lookup_ioc, search_ioc, search_ioc_rules, soar_enrich
|
|
22
|
+
from .args import OPT_PRETTY_PRINT
|
|
23
|
+
from .epilogs import EPILOG_IOC_BULK_LOOKUP, EPILOG_IOC_LOOKUP, EPILOG_IOC_RULES, EPILOG_IOC_SEARCH
|
|
24
|
+
|
|
25
|
+
CMD_NAME = 'ioc'
|
|
26
|
+
CMD_HELP = 'Search and lookup IOCs'
|
|
27
|
+
CMD_RICH_HELP = 'Indicators of Compromise'
|
|
28
|
+
|
|
29
|
+
app = Typer(no_args_is_help=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_ioc_input(value: list[str]):
|
|
33
|
+
if not value:
|
|
34
|
+
raise BadParameter('No IOCs supplied')
|
|
35
|
+
|
|
36
|
+
iocs = value
|
|
37
|
+
if isinstance(value, str):
|
|
38
|
+
iocs = [x for x in re.split(r'[\s]+', value) if x]
|
|
39
|
+
|
|
40
|
+
if not len(iocs):
|
|
41
|
+
raise BadParameter('No IOCs provided')
|
|
42
|
+
|
|
43
|
+
return iocs
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@banshee_cmd(
|
|
47
|
+
app=app,
|
|
48
|
+
help_=(
|
|
49
|
+
'Detailed enrichment for one or more IOCs — one API call per indicator. '
|
|
50
|
+
'Use `--verbosity` to control how many fields are returned, from basic risk score up to '
|
|
51
|
+
'full intel including links, analyst notes, etc. '
|
|
52
|
+
'Use this when you need rich context.'
|
|
53
|
+
),
|
|
54
|
+
epilog=EPILOG_IOC_LOOKUP,
|
|
55
|
+
rich_help_panel='IOC Enrichment',
|
|
56
|
+
)
|
|
57
|
+
def lookup(
|
|
58
|
+
entity_type: Annotated[IOCType, Argument(show_default=False, help='Type of IOC')],
|
|
59
|
+
ioc: list[str] = Argument( # noqa: B008
|
|
60
|
+
... if sys.stdin.isatty() else None, # noqa: B008
|
|
61
|
+
show_default=False,
|
|
62
|
+
help='One or more whitespace separated IOC',
|
|
63
|
+
),
|
|
64
|
+
ai_insights: Annotated[
|
|
65
|
+
bool,
|
|
66
|
+
Option(
|
|
67
|
+
'--ai-insights',
|
|
68
|
+
'-a',
|
|
69
|
+
help=(
|
|
70
|
+
'Enable AI-generated insights from Recorded Future that summarize relevant '
|
|
71
|
+
'risk rules and key references. Response times may be slightly longer due '
|
|
72
|
+
'to AI processing.'
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
] = False,
|
|
76
|
+
verbosity: Annotated[
|
|
77
|
+
int,
|
|
78
|
+
Option(
|
|
79
|
+
'--verbosity',
|
|
80
|
+
'-v',
|
|
81
|
+
min=1,
|
|
82
|
+
max=5,
|
|
83
|
+
help=(
|
|
84
|
+
'Controls the amount of data returned in the response. '
|
|
85
|
+
'Higher verbosity levels include additional fields and details '
|
|
86
|
+
'in the JSON output. Higher verbosity levels may result in slower '
|
|
87
|
+
'response times due to increased data retrieval. '
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
] = 1,
|
|
91
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
92
|
+
):
|
|
93
|
+
if ioc is None:
|
|
94
|
+
ioc = sys.stdin.read()
|
|
95
|
+
|
|
96
|
+
ioc = parse_ioc_input(ioc)
|
|
97
|
+
|
|
98
|
+
lookup_ioc(
|
|
99
|
+
indicators=ioc,
|
|
100
|
+
entity_type=entity_type,
|
|
101
|
+
verbose_level=verbosity,
|
|
102
|
+
pretty=pretty,
|
|
103
|
+
ai_insights=ai_insights,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@banshee_cmd(
|
|
108
|
+
app=app,
|
|
109
|
+
help_=(
|
|
110
|
+
'Fast bulk enrichment that batches up to 1000 IOCs per API call — '
|
|
111
|
+
'submit any number of indicators and the command handles the batching automatically. '
|
|
112
|
+
'Returns a fixed set of fields — risk score and triggered risk rules. '
|
|
113
|
+
'Use this for high-volume triage. '
|
|
114
|
+
),
|
|
115
|
+
epilog=EPILOG_IOC_BULK_LOOKUP,
|
|
116
|
+
rich_help_panel='IOC Enrichment',
|
|
117
|
+
)
|
|
118
|
+
def bulk_lookup(
|
|
119
|
+
entity_type: Annotated[IOCType, Argument(show_default=False, help='Type of IOC')],
|
|
120
|
+
ioc: list[str] = Argument( # noqa: B008
|
|
121
|
+
... if sys.stdin.isatty() else None, # noqa: B008
|
|
122
|
+
show_default=False,
|
|
123
|
+
help='One or more whitespace separated IOC',
|
|
124
|
+
),
|
|
125
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
126
|
+
):
|
|
127
|
+
if ioc is None:
|
|
128
|
+
ioc = sys.stdin.read()
|
|
129
|
+
|
|
130
|
+
ioc = parse_ioc_input(ioc)
|
|
131
|
+
|
|
132
|
+
soar_enrich(indicators=ioc, entity_type=entity_type.value, pretty=pretty)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def parse_risk_score_input(value: str):
|
|
136
|
+
if not value:
|
|
137
|
+
return value
|
|
138
|
+
|
|
139
|
+
if not re.match(r'^[\[\(](\d+|),(\d+|)[\]\)]$', value.strip()):
|
|
140
|
+
raise BadParameter('Invalid risk score format')
|
|
141
|
+
|
|
142
|
+
return value.strip()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@banshee_cmd(
|
|
146
|
+
app=app, help_='Search for IOCs', epilog=EPILOG_IOC_SEARCH, rich_help_panel='IOC Search'
|
|
147
|
+
)
|
|
148
|
+
def search(
|
|
149
|
+
entity_type: Annotated[IOCType, Argument()],
|
|
150
|
+
limit: Annotated[
|
|
151
|
+
Optional[int],
|
|
152
|
+
Option('--limit', '-l', help='Maximum number of IOCs to return', min=1, max=1000),
|
|
153
|
+
] = 5,
|
|
154
|
+
risk_score: Annotated[
|
|
155
|
+
Optional[str],
|
|
156
|
+
Option(
|
|
157
|
+
'--risk-score',
|
|
158
|
+
'-r',
|
|
159
|
+
help='Filter by risk score range',
|
|
160
|
+
callback=parse_risk_score_input,
|
|
161
|
+
show_default=False,
|
|
162
|
+
),
|
|
163
|
+
] = None,
|
|
164
|
+
risk_rule: Annotated[
|
|
165
|
+
Optional[str], Option('--risk-rule', '-R', help='Filter by risk rule', show_default=False)
|
|
166
|
+
] = None,
|
|
167
|
+
verbosity: Annotated[
|
|
168
|
+
int,
|
|
169
|
+
Option(
|
|
170
|
+
'--verbosity',
|
|
171
|
+
'-v',
|
|
172
|
+
min=1,
|
|
173
|
+
max=5,
|
|
174
|
+
help=(
|
|
175
|
+
'Controls the amount of data returned in the response. '
|
|
176
|
+
'Higher verbosity levels include additional fields and details '
|
|
177
|
+
'in the JSON output. Higher verbosity levels may result in slower '
|
|
178
|
+
'response times due to increased data retrieval. '
|
|
179
|
+
),
|
|
180
|
+
),
|
|
181
|
+
] = 1,
|
|
182
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
183
|
+
):
|
|
184
|
+
search_ioc(
|
|
185
|
+
entity_type=entity_type.value,
|
|
186
|
+
limit=limit,
|
|
187
|
+
risk_score=risk_score,
|
|
188
|
+
risk_rule=risk_rule,
|
|
189
|
+
verbose_level=verbosity,
|
|
190
|
+
pretty=pretty,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@banshee_cmd(
|
|
195
|
+
app=app, help_='Search for IOC Rules', epilog=EPILOG_IOC_RULES, rich_help_panel='IOC Rules'
|
|
196
|
+
)
|
|
197
|
+
def rules(
|
|
198
|
+
entity_type: Annotated[IOCType, Argument(show_default=False, help='Type of IOC')],
|
|
199
|
+
freetext: Annotated[
|
|
200
|
+
str,
|
|
201
|
+
Option(
|
|
202
|
+
'-F',
|
|
203
|
+
'--freetext',
|
|
204
|
+
show_default=False,
|
|
205
|
+
help='Free text search to filter rules by name/description',
|
|
206
|
+
),
|
|
207
|
+
] = None,
|
|
208
|
+
mitre_code: Annotated[
|
|
209
|
+
str,
|
|
210
|
+
Option(
|
|
211
|
+
'-M',
|
|
212
|
+
'--mitre-code',
|
|
213
|
+
show_default=False,
|
|
214
|
+
help='Filter by MITRE ATT&CK code',
|
|
215
|
+
),
|
|
216
|
+
] = None,
|
|
217
|
+
criticality: Annotated[
|
|
218
|
+
int,
|
|
219
|
+
Option(
|
|
220
|
+
'-C',
|
|
221
|
+
'--criticality',
|
|
222
|
+
show_default=False,
|
|
223
|
+
min=0,
|
|
224
|
+
max=5,
|
|
225
|
+
help='Filter by criticality. Higher the value, higher the criticality',
|
|
226
|
+
),
|
|
227
|
+
] = None,
|
|
228
|
+
pretty: OPT_PRETTY_PRINT = False,
|
|
229
|
+
):
|
|
230
|
+
search_ioc_rules(
|
|
231
|
+
entity_type=entity_type.value,
|
|
232
|
+
freetext=freetext,
|
|
233
|
+
mitre_code=mitre_code,
|
|
234
|
+
criticality=criticality,
|
|
235
|
+
pretty=pretty,
|
|
236
|
+
)
|