psengine 2.0.4__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.
- psengine/__init__.py +22 -0
- psengine/_sdk_id.py +16 -0
- psengine/_version.py +14 -0
- psengine/analyst_notes/__init__.py +32 -0
- psengine/analyst_notes/constants.py +15 -0
- psengine/analyst_notes/errors.py +42 -0
- psengine/analyst_notes/helpers.py +90 -0
- psengine/analyst_notes/models.py +219 -0
- psengine/analyst_notes/note.py +149 -0
- psengine/analyst_notes/note_mgr.py +400 -0
- psengine/base_http_client.py +285 -0
- psengine/classic_alerts/__init__.py +24 -0
- psengine/classic_alerts/classic_alert.py +275 -0
- psengine/classic_alerts/classic_alert_mgr.py +507 -0
- psengine/classic_alerts/constants.py +31 -0
- psengine/classic_alerts/errors.py +38 -0
- psengine/classic_alerts/helpers.py +87 -0
- psengine/classic_alerts/markdown/__init__.py +13 -0
- psengine/classic_alerts/markdown/markdown.py +359 -0
- psengine/classic_alerts/models.py +141 -0
- psengine/collective_insights/__init__.py +29 -0
- psengine/collective_insights/collective_insights.py +164 -0
- psengine/collective_insights/constants.py +44 -0
- psengine/collective_insights/errors.py +18 -0
- psengine/collective_insights/insight.py +89 -0
- psengine/collective_insights/models.py +81 -0
- psengine/common_models.py +89 -0
- psengine/config/__init__.py +15 -0
- psengine/config/config.py +284 -0
- psengine/config/errors.py +18 -0
- psengine/constants.py +63 -0
- psengine/detection/__init__.py +17 -0
- psengine/detection/detection_mgr.py +135 -0
- psengine/detection/detection_rule.py +85 -0
- psengine/detection/errors.py +26 -0
- psengine/detection/helpers.py +56 -0
- psengine/detection/models.py +47 -0
- psengine/endpoints.py +98 -0
- psengine/enrich/__init__.py +28 -0
- psengine/enrich/constants.py +73 -0
- psengine/enrich/errors.py +26 -0
- psengine/enrich/lookup.py +299 -0
- psengine/enrich/lookup_mgr.py +341 -0
- psengine/enrich/models/__init__.py +13 -0
- psengine/enrich/models/base_enriched_entity.py +43 -0
- psengine/enrich/models/lookup.py +271 -0
- psengine/enrich/models/soar.py +138 -0
- psengine/enrich/soar.py +89 -0
- psengine/enrich/soar_mgr.py +176 -0
- psengine/entity_lists/__init__.py +16 -0
- psengine/entity_lists/constants.py +19 -0
- psengine/entity_lists/entity_list.py +435 -0
- psengine/entity_lists/entity_list_mgr.py +185 -0
- psengine/entity_lists/errors.py +26 -0
- psengine/entity_lists/models.py +87 -0
- psengine/entity_match/__init__.py +16 -0
- psengine/entity_match/entity_match.py +90 -0
- psengine/entity_match/entity_match_mgr.py +235 -0
- psengine/entity_match/errors.py +18 -0
- psengine/entity_match/models.py +22 -0
- psengine/errors.py +41 -0
- psengine/helpers/__init__.py +23 -0
- psengine/helpers/helpers.py +471 -0
- psengine/logger/__init__.py +15 -0
- psengine/logger/constants.py +39 -0
- psengine/logger/errors.py +18 -0
- psengine/logger/rf_logger.py +148 -0
- psengine/markdown/__init__.py +21 -0
- psengine/markdown/markdown.py +169 -0
- psengine/markdown/models.py +22 -0
- psengine/playbook_alerts/__init__.py +34 -0
- psengine/playbook_alerts/constants.py +35 -0
- psengine/playbook_alerts/errors.py +35 -0
- psengine/playbook_alerts/helpers.py +80 -0
- psengine/playbook_alerts/mappings.py +44 -0
- psengine/playbook_alerts/markdown/__init__.py +13 -0
- psengine/playbook_alerts/markdown/markdown.py +98 -0
- psengine/playbook_alerts/markdown/markdown_code_repo.py +64 -0
- psengine/playbook_alerts/markdown/markdown_domain_abuse.py +118 -0
- psengine/playbook_alerts/markdown/markdown_identity_exposure.py +158 -0
- psengine/playbook_alerts/models/__init__.py +36 -0
- psengine/playbook_alerts/models/common_models.py +18 -0
- psengine/playbook_alerts/models/panel_log.py +329 -0
- psengine/playbook_alerts/models/panel_status.py +70 -0
- psengine/playbook_alerts/models/pba_code_repo_leak.py +52 -0
- psengine/playbook_alerts/models/pba_cyber_vulnerability.py +53 -0
- psengine/playbook_alerts/models/pba_domain_abuse.py +139 -0
- psengine/playbook_alerts/models/pba_identity_exposures.py +93 -0
- psengine/playbook_alerts/models/pba_third_party_risk.py +103 -0
- psengine/playbook_alerts/models/search_endpoint.py +68 -0
- psengine/playbook_alerts/pa_category.py +37 -0
- psengine/playbook_alerts/playbook_alert_mgr.py +593 -0
- psengine/playbook_alerts/playbook_alerts.py +393 -0
- psengine/rf_client.py +430 -0
- psengine/risklists/__init__.py +17 -0
- psengine/risklists/constants.py +15 -0
- psengine/risklists/errors.py +20 -0
- psengine/risklists/models.py +65 -0
- psengine/risklists/risklist_mgr.py +156 -0
- psengine/stix2/__init__.py +21 -0
- psengine/stix2/base_stix_entity.py +62 -0
- psengine/stix2/complex_entity.py +372 -0
- psengine/stix2/constants.py +81 -0
- psengine/stix2/enriched_indicator.py +261 -0
- psengine/stix2/errors.py +22 -0
- psengine/stix2/helpers.py +68 -0
- psengine/stix2/rf_bundle.py +240 -0
- psengine/stix2/simple_entity.py +145 -0
- psengine/stix2/util.py +53 -0
- psengine-2.0.4.dist-info/METADATA +189 -0
- psengine-2.0.4.dist-info/RECORD +115 -0
- psengine-2.0.4.dist-info/WHEEL +5 -0
- psengine-2.0.4.dist-info/entry_points.txt +2 -0
- psengine-2.0.4.dist-info/licenses/LICENSE +21 -0
- psengine-2.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,118 @@
|
|
|
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 base64
|
|
15
|
+
|
|
16
|
+
from markdown_strings import bold
|
|
17
|
+
|
|
18
|
+
from ...constants import TIMESTAMP_STR
|
|
19
|
+
from ...helpers import FormattingHelpers
|
|
20
|
+
from ...markdown import MarkdownMaker
|
|
21
|
+
from ...markdown.markdown import divider, table_from_rows
|
|
22
|
+
from ..models.pba_domain_abuse import ValueServer
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _add_screenshots(pba, md_maker: MarkdownMaker):
|
|
26
|
+
screenshots = [f'{bold("Screenshot Count:")} {len(pba.panel_evidence_summary.screenshots)}']
|
|
27
|
+
for screenshot in pba.panel_evidence_summary.screenshots:
|
|
28
|
+
screenshots.append(f'{bold("Created:")} {screenshot.created.strftime(TIMESTAMP_STR)}')
|
|
29
|
+
|
|
30
|
+
image = base64.b64encode(pba.images[screenshot.image_id]['image_bytes']).decode('utf-8')
|
|
31
|
+
|
|
32
|
+
for mentions in pba.panel_evidence_summary.screenshot_mentions:
|
|
33
|
+
if mentions.screenshot == screenshot.image_id and mentions.url:
|
|
34
|
+
url = FormattingHelpers.cleanup_rf_id(mentions.url)
|
|
35
|
+
md_maker.iocs_to_defang.append(url)
|
|
36
|
+
screenshots.append(f'{bold("Screenshot URL:")} {"".join(url)}')
|
|
37
|
+
|
|
38
|
+
screenshots.append(f'')
|
|
39
|
+
screenshots.append(divider())
|
|
40
|
+
|
|
41
|
+
md_maker.add_section('Screenshots', screenshots)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _add_whois(pba, md_maker: MarkdownMaker):
|
|
45
|
+
whois_body = [
|
|
46
|
+
body for body in pba.panel_evidence_whois.body if isinstance(body.value, ValueServer)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
whois_data = []
|
|
50
|
+
for whois in whois_body:
|
|
51
|
+
entity = FormattingHelpers.cleanup_rf_id(whois.entity)
|
|
52
|
+
md_maker.iocs_to_defang.append(entity)
|
|
53
|
+
whois_entity = f"{bold('Entity:')} {entity}"
|
|
54
|
+
|
|
55
|
+
if (v := whois.value) and v.name_servers:
|
|
56
|
+
created_dt, updated_dt, expires_dt = '', '', ''
|
|
57
|
+
name_servers = [FormattingHelpers.cleanup_rf_id(s) for s in v.name_servers]
|
|
58
|
+
servers = f"{bold('Name servers:')} {', '.join(name_servers)}"
|
|
59
|
+
md_maker.iocs_to_defang.extend(name_servers)
|
|
60
|
+
|
|
61
|
+
if v.created_date:
|
|
62
|
+
created_dt = f'{bold("Creation Date:")} {v.created_date.strftime(TIMESTAMP_STR)}'
|
|
63
|
+
|
|
64
|
+
if v.updated_date:
|
|
65
|
+
updated_dt = f'{bold("Update Date:")} {v.updated_date.strftime(TIMESTAMP_STR)}'
|
|
66
|
+
|
|
67
|
+
if v.expires_date:
|
|
68
|
+
expires_dt = f'{bold("Expiration Date:")} {v.expires_date.strftime(TIMESTAMP_STR)}'
|
|
69
|
+
|
|
70
|
+
registrar = f'{bold("Registrar:")} {v.registrar_name}' if v.registrar_name else ''
|
|
71
|
+
|
|
72
|
+
whois_data.extend(
|
|
73
|
+
[whois_entity, servers, created_dt, updated_dt, expires_dt, registrar]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if whois_data:
|
|
77
|
+
md_maker.add_section('WHOIS Details', whois_data)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _add_dns_records(pba, md_maker: MarkdownMaker):
|
|
81
|
+
records = [
|
|
82
|
+
[
|
|
83
|
+
FormattingHelpers.cleanup_rf_id(record.entity),
|
|
84
|
+
record.risk_score,
|
|
85
|
+
record.criticality,
|
|
86
|
+
record.record_type,
|
|
87
|
+
', '.join(c.context for c in record.context_list if c),
|
|
88
|
+
]
|
|
89
|
+
for record in pba.panel_evidence_summary.resolved_record_list
|
|
90
|
+
]
|
|
91
|
+
md_maker.iocs_to_defang.extend(list(zip(*records))[0])
|
|
92
|
+
|
|
93
|
+
records.sort(key=lambda x: x[1], reverse=True)
|
|
94
|
+
records.insert(0, ['Entity', 'Risk Score', 'Criticality', 'Record Type', 'Context'])
|
|
95
|
+
evidence_summary = [
|
|
96
|
+
f'{bold("Reason:")} {pba.panel_evidence_summary.explanation}\n',
|
|
97
|
+
f'{table_from_rows(records)}',
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
md_maker.add_section('DNS Records', evidence_summary)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _domain_abuse_markdown(pba, md_maker: MarkdownMaker, *args) -> str: # noqa: ARG001
|
|
104
|
+
if targets := pba.panel_status.targets:
|
|
105
|
+
targets = [FormattingHelpers.cleanup_rf_id(t) for t in targets]
|
|
106
|
+
md_maker.iocs_to_defang.extend(targets)
|
|
107
|
+
md_maker.add_section('Targets', targets)
|
|
108
|
+
|
|
109
|
+
if pba.panel_evidence_summary.resolved_record_list:
|
|
110
|
+
_add_dns_records(pba, md_maker)
|
|
111
|
+
|
|
112
|
+
if pba.panel_evidence_whois:
|
|
113
|
+
_add_whois(pba, md_maker)
|
|
114
|
+
|
|
115
|
+
if pba.images and not md_maker.character_limit:
|
|
116
|
+
_add_screenshots(pba, md_maker)
|
|
117
|
+
|
|
118
|
+
return md_maker.format_output()
|
|
@@ -0,0 +1,158 @@
|
|
|
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 markdown_strings import bold, unordered_list
|
|
15
|
+
|
|
16
|
+
from ...constants import TIMESTAMP_STR
|
|
17
|
+
from ...markdown import MarkdownMaker
|
|
18
|
+
from ...markdown.markdown import divider
|
|
19
|
+
|
|
20
|
+
DOMAIN_CONFIG_URL = 'https://app.recordedfuture.com/portal/identity/domain-configuration'
|
|
21
|
+
PORTAL_URL = 'https://app.recordedfuture.com/portal/playbook-alerts/{}'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_compromised_host(pba) -> str:
|
|
25
|
+
host_data = []
|
|
26
|
+
summ = pba.panel_evidence_summary
|
|
27
|
+
|
|
28
|
+
if not (
|
|
29
|
+
summ.compromised_host
|
|
30
|
+
and summ.compromised_host.os_username
|
|
31
|
+
and summ.compromised_host.computer_name
|
|
32
|
+
):
|
|
33
|
+
return ''
|
|
34
|
+
|
|
35
|
+
comprom = summ.compromised_host
|
|
36
|
+
os = _format_field('Operating System', comprom.os)
|
|
37
|
+
username = _format_field('OS Username', comprom.os_username)
|
|
38
|
+
file_path = _format_field('File Path', comprom.malware_file)
|
|
39
|
+
timezone = _format_field('Time Zone', comprom.timezone)
|
|
40
|
+
uac = _format_field('User Account Control Setting', comprom.uac)
|
|
41
|
+
av = _format_field('Antivirus', comprom.antivirus)
|
|
42
|
+
machine = _format_field('Machine Name', comprom.computer_name)
|
|
43
|
+
|
|
44
|
+
host_data = [x for x in [os, username, file_path, timezone, machine, uac, av] if x]
|
|
45
|
+
|
|
46
|
+
return f"\n{bold('Compromised Host:')}\n\n{unordered_list(host_data, esc=False)}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_technology(pba) -> str:
|
|
50
|
+
items = []
|
|
51
|
+
for tech in pba.panel_evidence_summary.technologies:
|
|
52
|
+
label = 'Category:' if tech.category else 'Technology:'
|
|
53
|
+
items.append(f'{bold(label)} {tech.name}')
|
|
54
|
+
|
|
55
|
+
if not items:
|
|
56
|
+
return ''
|
|
57
|
+
|
|
58
|
+
return f"\n{bold('Technology:')}\n\n{unordered_list(items, esc=False)}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _add_exposure(pba, md_maker: MarkdownMaker):
|
|
62
|
+
summ = pba.panel_evidence_summary
|
|
63
|
+
result = []
|
|
64
|
+
|
|
65
|
+
result.append(_format_field('Identity', pba.panel_status.entity_name))
|
|
66
|
+
result.append(_format_password(summ.exposed_secret.details))
|
|
67
|
+
result.append(_format_assessments(summ.assessments))
|
|
68
|
+
if summ.compromised_host.exfiltration_date:
|
|
69
|
+
result.append(
|
|
70
|
+
_format_field(
|
|
71
|
+
'Exfiltration Date', summ.compromised_host.exfiltration_date.strftime(TIMESTAMP_STR)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result.append(_format_field('Authorization URL', summ.authorization_url))
|
|
76
|
+
result.append(_format_field('IP Address', summ.infrastructure.ip))
|
|
77
|
+
result.append(_format_field('Properties', summ.exposed_secret.details.properties))
|
|
78
|
+
|
|
79
|
+
result.append(_format_hashes(summ.exposed_secret.hashes))
|
|
80
|
+
result.append(_format_source(summ.dump))
|
|
81
|
+
result.append(_get_compromised_host(pba))
|
|
82
|
+
|
|
83
|
+
result.append('\n' + _format_field('Malware Family', summ.malware_family.name))
|
|
84
|
+
|
|
85
|
+
result.append(_get_technology(pba))
|
|
86
|
+
result.append(divider())
|
|
87
|
+
|
|
88
|
+
md_maker.add_section('Exposure', result)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _format_field(label, value) -> str:
|
|
92
|
+
"""Generic helper for fields that can be single strings or lists.
|
|
93
|
+
Returns an empty string if there's no value.
|
|
94
|
+
"""
|
|
95
|
+
if not value:
|
|
96
|
+
return ''
|
|
97
|
+
if isinstance(value, list):
|
|
98
|
+
value = ', '.join(value)
|
|
99
|
+
return f'{bold(label + ":")} {value}'
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _format_password(secret_details):
|
|
103
|
+
password = ''
|
|
104
|
+
if secret_details.clear_text_hint:
|
|
105
|
+
# Hides all but the password hint
|
|
106
|
+
password = (
|
|
107
|
+
f"{bold('Password:')} {secret_details.clear_text_hint:•<8} [ⓘ] ({DOMAIN_CONFIG_URL})"
|
|
108
|
+
)
|
|
109
|
+
if secret_details.clear_text_value:
|
|
110
|
+
# If support has enabled clear text, it takes precedence
|
|
111
|
+
password = f"{bold('Password:')} {secret_details.clear_text_value}"
|
|
112
|
+
return password
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _format_assessments(assessments) -> str:
|
|
116
|
+
if assessments:
|
|
117
|
+
names = ', '.join(ass.name for ass in assessments)
|
|
118
|
+
return f"{bold('Assessment:')} {names}"
|
|
119
|
+
return ''
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _format_hashes(hashes) -> str:
|
|
123
|
+
hash_values = [f'{bold(h.algorithm)} {h.hash_}' for h in hashes if h.hash_]
|
|
124
|
+
if hash_values:
|
|
125
|
+
formatted_hashes = '\n\n' + unordered_list(hash_values, esc=False)
|
|
126
|
+
return f"\n{bold('Hashes:')} {formatted_hashes}\n"
|
|
127
|
+
return ''
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _format_source(dump) -> str:
|
|
131
|
+
source_data = []
|
|
132
|
+
if dump.name:
|
|
133
|
+
source_data.append(f"{bold('Name:')} {dump.name}")
|
|
134
|
+
if dump.description:
|
|
135
|
+
source_data.append(f"{bold('Description:')} {dump.description}")
|
|
136
|
+
if source_data:
|
|
137
|
+
return f"\n{bold('Source:')}\n\n{unordered_list(source_data, esc=False)}"
|
|
138
|
+
return ''
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _add_actions_to_consider(pba, md_maker: MarkdownMaker):
|
|
142
|
+
actions = []
|
|
143
|
+
actions.append(f'- [Check Incident Report] ({PORTAL_URL.format(pba.playbook_alert_id)})')
|
|
144
|
+
actions.append('- Enforce Password Reset')
|
|
145
|
+
actions.append('- Initiate MFA Challenge')
|
|
146
|
+
actions.append('- Request Compromised Host Incident Response')
|
|
147
|
+
actions.append('- Review Malware Hunting Packages')
|
|
148
|
+
|
|
149
|
+
md_maker.add_section('Actions to Consider', actions)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _identity_exposure_markdown(pba, md_maker: MarkdownMaker, *args) -> str: # noqa: ARG001
|
|
153
|
+
if pba.panel_evidence_summary:
|
|
154
|
+
_add_exposure(pba, md_maker)
|
|
155
|
+
|
|
156
|
+
_add_actions_to_consider(pba, md_maker)
|
|
157
|
+
|
|
158
|
+
return md_maker.format_output()
|
|
@@ -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
|
+
from .common_models import ResolvedEntity
|
|
15
|
+
from .panel_log import PanelLogV2
|
|
16
|
+
from .panel_status import PanelAction
|
|
17
|
+
from .pba_code_repo_leak import (
|
|
18
|
+
CodeRepoEvidencePanel,
|
|
19
|
+
CodeRepoPanelStatus,
|
|
20
|
+
)
|
|
21
|
+
from .pba_cyber_vulnerability import (
|
|
22
|
+
CyberVulnerabilityEvidencePanel,
|
|
23
|
+
CyberVulnerabilityPanelStatus,
|
|
24
|
+
)
|
|
25
|
+
from .pba_domain_abuse import (
|
|
26
|
+
DomainAbuseEvidenceDns,
|
|
27
|
+
DomainAbuseEvidenceSummary,
|
|
28
|
+
DomainAbuseEvidenceWhois,
|
|
29
|
+
DomainAbusePanelStatus,
|
|
30
|
+
)
|
|
31
|
+
from .pba_identity_exposures import (
|
|
32
|
+
IdentityEvidencePanel,
|
|
33
|
+
IdentityPanelStatus,
|
|
34
|
+
)
|
|
35
|
+
from .pba_third_party_risk import TPREvidencePanel, TPRPanelStatus
|
|
36
|
+
from .search_endpoint import DatetimeRange, SearchCounts, SearchData, SearchResponse, SearchStatus
|
|
@@ -0,0 +1,18 @@
|
|
|
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 ...common_models import RFBaseModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ResolvedEntity(RFBaseModel):
|
|
18
|
+
name: str
|
|
@@ -0,0 +1,329 @@
|
|
|
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 datetime import datetime
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import Field, HttpUrl, model_validator
|
|
18
|
+
|
|
19
|
+
from ...common_models import IdOptionalNameType, RFBaseModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ChangeType(RFBaseModel):
|
|
23
|
+
type_: str = Field(alias='type')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PriorityChange(ChangeType):
|
|
27
|
+
old: str
|
|
28
|
+
new: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class StatusChange(ChangeType):
|
|
32
|
+
old: str
|
|
33
|
+
new: str
|
|
34
|
+
actions_taken: list
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class OldNewOptionalType(ChangeType):
|
|
38
|
+
"""This is valid for the following Panel Log types.
|
|
39
|
+
|
|
40
|
+
- ``ExternalIdChange``,
|
|
41
|
+
- ``DescriptionChange``,
|
|
42
|
+
- ``TitleChange``,
|
|
43
|
+
- ``ReopenStrategyChange``
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
old: Optional[str] = None
|
|
48
|
+
new: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AddedRemovedTypeEntities(ChangeType):
|
|
52
|
+
"""This is valid for the following Panel Log types.
|
|
53
|
+
|
|
54
|
+
- ``EntityChangeV2``,
|
|
55
|
+
- ``RelatedEntityChangeV2``
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
removed: Optional[list[IdOptionalNameType]] = []
|
|
59
|
+
added: Optional[list[IdOptionalNameType]] = []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AddedRemovedList(ChangeType):
|
|
63
|
+
removed: Optional[list[str]] = []
|
|
64
|
+
added: Optional[list[str]] = []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CommentChange(ChangeType):
|
|
68
|
+
comment: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Assignee(RFBaseModel):
|
|
72
|
+
id_: str = Field(alias='id')
|
|
73
|
+
name: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AssigneeChange(ChangeType):
|
|
77
|
+
old: Optional[Assignee] = None
|
|
78
|
+
new: Optional[Assignee] = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DnsRecord(RFBaseModel):
|
|
82
|
+
type_: Optional[str] = Field(alias='type', default=None)
|
|
83
|
+
entity: Optional[IdOptionalNameType] = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DomainAbuseDnsChange(ChangeType):
|
|
87
|
+
domain: str
|
|
88
|
+
removed: list[DnsRecord]
|
|
89
|
+
added: list[DnsRecord]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class WhoisRecord(RFBaseModel):
|
|
93
|
+
status: Optional[str] = None
|
|
94
|
+
registrar_name: Optional[str] = None
|
|
95
|
+
private_registration: Optional[bool] = None
|
|
96
|
+
name_servers: Optional[list[str]] = []
|
|
97
|
+
contact_email: Optional[str] = None
|
|
98
|
+
created: Optional[datetime] = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class WhoisContactRecord(ChangeType):
|
|
102
|
+
telephone: Optional[str] = None
|
|
103
|
+
street1: Optional[str] = None
|
|
104
|
+
state: Optional[str] = None
|
|
105
|
+
postal_code: Optional[str] = None
|
|
106
|
+
organization: Optional[str] = None
|
|
107
|
+
name: Optional[str] = None
|
|
108
|
+
fax: Optional[str] = None
|
|
109
|
+
email: Optional[str] = None
|
|
110
|
+
country_code: Optional[str] = None
|
|
111
|
+
country: Optional[str] = None
|
|
112
|
+
city: Optional[str] = None
|
|
113
|
+
created: Optional[datetime] = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DomainAbuseWhoisChange(ChangeType):
|
|
117
|
+
domain: str
|
|
118
|
+
old_record: Optional[WhoisRecord] = None
|
|
119
|
+
new_record: Optional[WhoisRecord] = None
|
|
120
|
+
removed_contacts: list[WhoisContactRecord]
|
|
121
|
+
added_contacts: list[WhoisContactRecord]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class LogotypeInScreenshot(RFBaseModel):
|
|
125
|
+
logotype_id: Optional[str] = None
|
|
126
|
+
screenshot_id: Optional[str] = None
|
|
127
|
+
url: HttpUrl
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class DomainAbuseLogoTypeChange(ChangeType):
|
|
131
|
+
domain: str
|
|
132
|
+
removed: Optional[list[LogotypeInScreenshot]] = []
|
|
133
|
+
added: Optional[list[LogotypeInScreenshot]] = []
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class MaliciousAssessment(RFBaseModel):
|
|
137
|
+
id_: str = Field(alias='id')
|
|
138
|
+
level: int
|
|
139
|
+
title: Optional[str] = None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class MaliciousDnsRecord(RFBaseModel):
|
|
143
|
+
id_: Optional[str] = Field(alias='id', default=None)
|
|
144
|
+
assessments: list[MaliciousAssessment]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class DomainAbuseMaliciousDnsChange(ChangeType):
|
|
148
|
+
domain: str
|
|
149
|
+
removed: Optional[list[MaliciousDnsRecord]] = []
|
|
150
|
+
added: Optional[list[MaliciousDnsRecord]] = []
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class ReregistrationRecord(RFBaseModel):
|
|
154
|
+
registrar: Optional[str] = None
|
|
155
|
+
registrar_name: Optional[str] = None
|
|
156
|
+
iana_id: Optional[int] = None
|
|
157
|
+
expiration: Optional[datetime] = None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class DomainAbuseReregistrationRecordChange(ChangeType):
|
|
161
|
+
domain: str
|
|
162
|
+
removed: Optional[ReregistrationRecord] = None
|
|
163
|
+
added: Optional[ReregistrationRecord] = None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Source(RFBaseModel):
|
|
167
|
+
id_: str = Field(alias='id')
|
|
168
|
+
name: str
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class UrlAssessment(MaliciousAssessment):
|
|
172
|
+
source: Source
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class MaliciousUrlRecord(RFBaseModel):
|
|
176
|
+
url: Optional[HttpUrl] = None
|
|
177
|
+
assessments: list[UrlAssessment]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class DomainAbuseMaliciousUrlChange(ChangeType):
|
|
181
|
+
domain: str
|
|
182
|
+
removed: Optional[list[MaliciousUrlRecord]] = []
|
|
183
|
+
added: Optional[list[MaliciousUrlRecord]] = []
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class MentionedEntity(RFBaseModel):
|
|
187
|
+
entity: IdOptionalNameType
|
|
188
|
+
reference: Optional[str] = None
|
|
189
|
+
fragment: Optional[str] = None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class ScreenshotMention(RFBaseModel):
|
|
193
|
+
url: HttpUrl
|
|
194
|
+
screenshot_id: str
|
|
195
|
+
document: str
|
|
196
|
+
analyzed: datetime
|
|
197
|
+
mentioned_entities: list[MentionedEntity]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class DomainAbuseScreenshotMentions(ChangeType):
|
|
201
|
+
domain: str
|
|
202
|
+
added: list[ScreenshotMention]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class VulnerabilityAssessment(RFBaseModel):
|
|
206
|
+
id_: str = Field(alias='id')
|
|
207
|
+
level: int
|
|
208
|
+
title: Optional[str] = None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TriggeredRiskRule(RFBaseModel):
|
|
212
|
+
id_: str = Field(alias='id')
|
|
213
|
+
name: Optional[str] = None
|
|
214
|
+
description: Optional[str] = None
|
|
215
|
+
evidence_string: Optional[str] = None
|
|
216
|
+
machine_name: Optional[str] = None
|
|
217
|
+
timestamp: Optional[datetime] = None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class VulnerabilityLifecycleChange(ChangeType):
|
|
221
|
+
added: Optional[VulnerabilityAssessment] = None
|
|
222
|
+
removed: Optional[VulnerabilityAssessment] = None
|
|
223
|
+
triggered_by_risk_rule: Optional[TriggeredRiskRule] = None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Document(RFBaseModel):
|
|
227
|
+
id_: str = Field(alias='id')
|
|
228
|
+
content: str
|
|
229
|
+
owner_id: str
|
|
230
|
+
owner_name: Optional[str] = None
|
|
231
|
+
published: datetime
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class WatchList(RFBaseModel):
|
|
235
|
+
id_: str = Field(alias='id')
|
|
236
|
+
name: Optional[str] = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class RepoAssessment(RFBaseModel):
|
|
240
|
+
id_: str = Field(alias='id')
|
|
241
|
+
level: int
|
|
242
|
+
title: Optional[str] = None
|
|
243
|
+
text_indicator: Optional[str] = None
|
|
244
|
+
entity: Optional[IdOptionalNameType] = None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class CodeRepoLeakageEvidence(RFBaseModel):
|
|
248
|
+
assessments: list[RepoAssessment]
|
|
249
|
+
document: Document
|
|
250
|
+
target_entities: list[IdOptionalNameType]
|
|
251
|
+
watch_lists: list[WatchList]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class CodeRepoLeakageEvidenceChange(ChangeType):
|
|
255
|
+
added: list[CodeRepoLeakageEvidence]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class TPRRiskEvidence(RFBaseModel):
|
|
259
|
+
level: int
|
|
260
|
+
evidence_string: Optional[str] = None
|
|
261
|
+
timestamp: Optional[datetime] = None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ThirdPartyAssessmentChange(ChangeType):
|
|
265
|
+
risk_attribute: str
|
|
266
|
+
added: Optional[TPRRiskEvidence] = None
|
|
267
|
+
removed: Optional[TPRRiskEvidence] = None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class Assessment(RFBaseModel):
|
|
271
|
+
level: int
|
|
272
|
+
evidence_string: str
|
|
273
|
+
timestamp: datetime
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class AssessmentChange(ChangeType):
|
|
277
|
+
risk_attribute: str
|
|
278
|
+
removed: Optional[Assessment] = None
|
|
279
|
+
added: Optional[Assessment] = None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
TYPE_MAPPING = {
|
|
283
|
+
'assignee_change': AssigneeChange,
|
|
284
|
+
'status_change': StatusChange,
|
|
285
|
+
'priority_change': PriorityChange,
|
|
286
|
+
'reopen_strategy_change': OldNewOptionalType,
|
|
287
|
+
'title_change': OldNewOptionalType,
|
|
288
|
+
'entities_change': AddedRemovedTypeEntities,
|
|
289
|
+
'related_entities_change': AddedRemovedTypeEntities,
|
|
290
|
+
'description_change': OldNewOptionalType,
|
|
291
|
+
'external_id_change': OldNewOptionalType,
|
|
292
|
+
'comment_change': CommentChange,
|
|
293
|
+
'action_change': AddedRemovedList,
|
|
294
|
+
'assessment_ids_change': AddedRemovedList,
|
|
295
|
+
'dns_change': DomainAbuseDnsChange,
|
|
296
|
+
'whois_change': DomainAbuseWhoisChange,
|
|
297
|
+
'logotype_in_screenshot_change': DomainAbuseLogoTypeChange,
|
|
298
|
+
'malicious_dns_change': DomainAbuseMaliciousDnsChange,
|
|
299
|
+
'reregistration_change': DomainAbuseReregistrationRecordChange,
|
|
300
|
+
'malicious_url_change': DomainAbuseMaliciousUrlChange,
|
|
301
|
+
'screenshot_mentions_change': DomainAbuseScreenshotMentions,
|
|
302
|
+
'lifecycle_in_cve_change': VulnerabilityLifecycleChange,
|
|
303
|
+
'evidence_change': CodeRepoLeakageEvidenceChange,
|
|
304
|
+
'tpr_assessment_change': ThirdPartyAssessmentChange,
|
|
305
|
+
'assessment_change': AssessmentChange,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class PanelLogV2(RFBaseModel):
|
|
310
|
+
id_: str = Field(alias='id')
|
|
311
|
+
author_id: Optional[str] = None
|
|
312
|
+
author_name: Optional[str] = None
|
|
313
|
+
created: datetime
|
|
314
|
+
changes: list
|
|
315
|
+
|
|
316
|
+
@model_validator(mode='before')
|
|
317
|
+
@classmethod
|
|
318
|
+
def validate_changes(cls, data):
|
|
319
|
+
"""Validate each panel_log_v2 changes based on the supported changes.
|
|
320
|
+
|
|
321
|
+
The list of changes is in ``TYPE_MAPPING``. Skip unsupported changes.
|
|
322
|
+
"""
|
|
323
|
+
new_changes = [
|
|
324
|
+
model_type.model_validate(change)
|
|
325
|
+
for change in data.get('changes', [])
|
|
326
|
+
if (change_type := change.get('type')) and (model_type := TYPE_MAPPING.get(change_type))
|
|
327
|
+
]
|
|
328
|
+
data['changes'] = new_changes
|
|
329
|
+
return data
|