misp-modules 2.4.196__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.
- misp_modules/__init__.py +379 -0
- misp_modules/helpers/__init__.py +1 -0
- misp_modules/helpers/cache.py +84 -0
- misp_modules/lib/__init__.py +4 -0
- misp_modules/lib/_vmray/__init__.py +0 -0
- misp_modules/lib/_vmray/parser.py +1417 -0
- misp_modules/lib/_vmray/rest_api.py +148 -0
- misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
- misp_modules/lib/cof2misp/__init__.py +0 -0
- misp_modules/lib/cof2misp/cof.py +165 -0
- misp_modules/lib/joe_mapping.py +114 -0
- misp_modules/lib/joe_parser.py +573 -0
- misp_modules/lib/lastline_api.py +841 -0
- misp_modules/lib/qintel_helper.py +263 -0
- misp_modules/lib/stix2misp.py +2080 -0
- misp_modules/lib/stix2misp_mapping.py +460 -0
- misp_modules/lib/synonymsToTagNames.json +1 -0
- misp_modules/lib/vt_graph_parser/__init__.py +8 -0
- misp_modules/lib/vt_graph_parser/errors.py +20 -0
- misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
- misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
- misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
- misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
- misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
- misp_modules/modules/__init__.py +4 -0
- misp_modules/modules/action_mod/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/utils.py +70 -0
- misp_modules/modules/action_mod/mattermost.py +113 -0
- misp_modules/modules/action_mod/slack.py +96 -0
- misp_modules/modules/action_mod/testaction.py +68 -0
- misp_modules/modules/expansion/__init__.py +36 -0
- misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
- misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
- misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
- misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
- misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
- misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
- misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
- misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
- misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
- misp_modules/modules/expansion/abuseipdb.py +146 -0
- misp_modules/modules/expansion/apiosintds.py +365 -0
- misp_modules/modules/expansion/apivoid.py +133 -0
- misp_modules/modules/expansion/assemblyline_query.py +179 -0
- misp_modules/modules/expansion/assemblyline_submit.py +100 -0
- misp_modules/modules/expansion/backscatter_io.py +84 -0
- misp_modules/modules/expansion/btc_scam_check.py +54 -0
- misp_modules/modules/expansion/btc_steroids.py +238 -0
- misp_modules/modules/expansion/censys_enrich.py +287 -0
- misp_modules/modules/expansion/circl_passivedns.py +85 -0
- misp_modules/modules/expansion/circl_passivessl.py +113 -0
- misp_modules/modules/expansion/clamav.py +129 -0
- misp_modules/modules/expansion/cluster25_expand.py +239 -0
- misp_modules/modules/expansion/countrycode.py +69 -0
- misp_modules/modules/expansion/cpe.py +140 -0
- misp_modules/modules/expansion/crowdsec.py +235 -0
- misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
- misp_modules/modules/expansion/cuckoo_submit.py +161 -0
- misp_modules/modules/expansion/cve.py +57 -0
- misp_modules/modules/expansion/cve_advanced.py +185 -0
- misp_modules/modules/expansion/cytomic_orion.py +196 -0
- misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
- misp_modules/modules/expansion/dns.py +71 -0
- misp_modules/modules/expansion/docx_enrich.py +71 -0
- misp_modules/modules/expansion/domaintools.py +290 -0
- misp_modules/modules/expansion/eql.py +91 -0
- misp_modules/modules/expansion/eupi.py +86 -0
- misp_modules/modules/expansion/extract_url_components.py +83 -0
- misp_modules/modules/expansion/farsight_passivedns.py +243 -0
- misp_modules/modules/expansion/geoip_asn.py +75 -0
- misp_modules/modules/expansion/geoip_city.py +75 -0
- misp_modules/modules/expansion/geoip_country.py +73 -0
- misp_modules/modules/expansion/google_safe_browsing.py +86 -0
- misp_modules/modules/expansion/google_search.py +57 -0
- misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
- misp_modules/modules/expansion/greynoise.py +346 -0
- misp_modules/modules/expansion/hashdd.py +53 -0
- misp_modules/modules/expansion/hashlookup.py +118 -0
- misp_modules/modules/expansion/hibp.py +62 -0
- misp_modules/modules/expansion/html_to_markdown.py +63 -0
- misp_modules/modules/expansion/hyasinsight.py +881 -0
- misp_modules/modules/expansion/intel471.py +73 -0
- misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
- misp_modules/modules/expansion/ip2locationio.py +90 -0
- misp_modules/modules/expansion/ipasn.py +70 -0
- misp_modules/modules/expansion/ipinfo.py +112 -0
- misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
- misp_modules/modules/expansion/iprep.py +98 -0
- misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
- misp_modules/modules/expansion/joesandbox_query.py +91 -0
- misp_modules/modules/expansion/joesandbox_submit.py +147 -0
- misp_modules/modules/expansion/lastline_query.py +148 -0
- misp_modules/modules/expansion/lastline_submit.py +180 -0
- misp_modules/modules/expansion/macaddress_io.py +130 -0
- misp_modules/modules/expansion/macvendors.py +55 -0
- misp_modules/modules/expansion/malshare_upload.py +107 -0
- misp_modules/modules/expansion/malwarebazaar.py +67 -0
- misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
- misp_modules/modules/expansion/mmdb_lookup.py +138 -0
- misp_modules/modules/expansion/module.py.skeleton +44 -0
- misp_modules/modules/expansion/mwdb.py +152 -0
- misp_modules/modules/expansion/ocr_enrich.py +74 -0
- misp_modules/modules/expansion/ods_enrich.py +71 -0
- misp_modules/modules/expansion/odt_enrich.py +61 -0
- misp_modules/modules/expansion/onyphe.py +243 -0
- misp_modules/modules/expansion/onyphe_full.py +387 -0
- misp_modules/modules/expansion/otx.py +167 -0
- misp_modules/modules/expansion/passive_ssh.py +150 -0
- misp_modules/modules/expansion/passivetotal.py +358 -0
- misp_modules/modules/expansion/pdf_enrich.py +58 -0
- misp_modules/modules/expansion/pptx_enrich.py +65 -0
- misp_modules/modules/expansion/qintel_qsentry.py +229 -0
- misp_modules/modules/expansion/qrcode.py +99 -0
- misp_modules/modules/expansion/ransomcoindb.py +82 -0
- misp_modules/modules/expansion/rbl.py +126 -0
- misp_modules/modules/expansion/recordedfuture.py +544 -0
- misp_modules/modules/expansion/reversedns.py +77 -0
- misp_modules/modules/expansion/securitytrails.py +571 -0
- misp_modules/modules/expansion/shodan.py +244 -0
- misp_modules/modules/expansion/sigma_queries.py +61 -0
- misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
- misp_modules/modules/expansion/sigmf_expand.py +303 -0
- misp_modules/modules/expansion/socialscan.py +108 -0
- misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
- misp_modules/modules/expansion/sourcecache.py +57 -0
- misp_modules/modules/expansion/stairwell.py +156 -0
- misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
- misp_modules/modules/expansion/threatcrowd.py +170 -0
- misp_modules/modules/expansion/threatfox.py +75 -0
- misp_modules/modules/expansion/threatminer.py +152 -0
- misp_modules/modules/expansion/triage_submit.py +119 -0
- misp_modules/modules/expansion/trustar_enrich.py +235 -0
- misp_modules/modules/expansion/urlhaus.py +164 -0
- misp_modules/modules/expansion/urlscan.py +269 -0
- misp_modules/modules/expansion/variotdbs.py +133 -0
- misp_modules/modules/expansion/virustotal.py +311 -0
- misp_modules/modules/expansion/virustotal_public.py +272 -0
- misp_modules/modules/expansion/virustotal_upload.py +91 -0
- misp_modules/modules/expansion/vmray_submit.py +161 -0
- misp_modules/modules/expansion/vmware_nsx.py +628 -0
- misp_modules/modules/expansion/vulndb.py +292 -0
- misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
- misp_modules/modules/expansion/vulners.py +81 -0
- misp_modules/modules/expansion/vysion.py +221 -0
- misp_modules/modules/expansion/whois.py +72 -0
- misp_modules/modules/expansion/whoisfreaks.py +232 -0
- misp_modules/modules/expansion/wiki.py +58 -0
- misp_modules/modules/expansion/xforceexchange.py +189 -0
- misp_modules/modules/expansion/xlsx_enrich.py +63 -0
- misp_modules/modules/expansion/yara_query.py +70 -0
- misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
- misp_modules/modules/expansion/yeti.py +196 -0
- misp_modules/modules/export_mod/__init__.py +3 -0
- misp_modules/modules/export_mod/cef_export.py +93 -0
- misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
- misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
- misp_modules/modules/export_mod/goamlexport.py +255 -0
- misp_modules/modules/export_mod/liteexport.py +97 -0
- misp_modules/modules/export_mod/mass_eql_export.py +106 -0
- misp_modules/modules/export_mod/nexthinkexport.py +131 -0
- misp_modules/modules/export_mod/osqueryexport.py +125 -0
- misp_modules/modules/export_mod/pdfexport.py +107 -0
- misp_modules/modules/export_mod/testexport.py +73 -0
- misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
- misp_modules/modules/export_mod/threat_connect_export.py +125 -0
- misp_modules/modules/export_mod/virustotal_collections.py +141 -0
- misp_modules/modules/export_mod/vt_graph.py +120 -0
- misp_modules/modules/export_mod/yara_export.py +290 -0
- misp_modules/modules/import_mod/__init__.py +22 -0
- misp_modules/modules/import_mod/cof2misp.py +264 -0
- misp_modules/modules/import_mod/csvimport.py +319 -0
- misp_modules/modules/import_mod/cuckooimport.py +749 -0
- misp_modules/modules/import_mod/email_import.py +294 -0
- misp_modules/modules/import_mod/goamlimport.py +188 -0
- misp_modules/modules/import_mod/import_blueprint.py +93 -0
- misp_modules/modules/import_mod/joe_import.py +74 -0
- misp_modules/modules/import_mod/lastline_import.py +160 -0
- misp_modules/modules/import_mod/mispjson.py +73 -0
- misp_modules/modules/import_mod/ocr.py +122 -0
- misp_modules/modules/import_mod/openiocimport.py +99 -0
- misp_modules/modules/import_mod/taxii21.py +383 -0
- misp_modules/modules/import_mod/testimport.py +73 -0
- misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
- misp_modules/modules/import_mod/url_import.py +97 -0
- misp_modules/modules/import_mod/vmray_import.py +96 -0
- misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
- misp_modules-2.4.196.dist-info/LICENSE +661 -0
- misp_modules-2.4.196.dist-info/METADATA +281 -0
- misp_modules-2.4.196.dist-info/RECORD +194 -0
- misp_modules-2.4.196.dist-info/WHEEL +4 -0
- misp_modules-2.4.196.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pymisp
|
|
3
|
+
from base64 import b64encode
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from . import check_input_attribute, checking_error, standard_error_message
|
|
6
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
7
|
+
from trustar import TruStar, Indicator
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
misperrors = {'error': "Error"}
|
|
11
|
+
mispattributes = {
|
|
12
|
+
'input': ["btc", "domain", "email-src", "filename", "hostname", "ip-src", "ip-dst", "malware-type", "md5", "sha1",
|
|
13
|
+
"sha256", "url"], 'format': 'misp_standard'}
|
|
14
|
+
|
|
15
|
+
moduleinfo = {
|
|
16
|
+
'version': '0.1',
|
|
17
|
+
'author': 'Jesse Hedden',
|
|
18
|
+
'description': 'Module to get enrich indicators with TruSTAR.',
|
|
19
|
+
'module-type': ['hover', 'expansion'],
|
|
20
|
+
'name': 'TruSTAR Enrich',
|
|
21
|
+
'logo': 'trustar.png',
|
|
22
|
+
'requirements': [],
|
|
23
|
+
'features': 'This module enriches MISP attributes with scoring and metadata from TruSTAR.\n\nThe TruSTAR indicator summary is appended to the attributes along with links to any associated reports.',
|
|
24
|
+
'references': ['https://docs.trustar.co/api/v13/indicators/get_indicator_summaries.html'],
|
|
25
|
+
'input': 'Any of the following MISP attributes:\n- btc\n- domain\n- email-src\n- filename\n- hostname\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- url',
|
|
26
|
+
'output': 'MISP attributes enriched with indicator summary data from the TruSTAR API. Data includes a severity level score and additional source and scoring info.',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
moduleconfig = ["user_api_key", "user_api_secret", "enclave_ids"]
|
|
30
|
+
|
|
31
|
+
MAX_PAGE_SIZE = 100 # Max allowable page size returned from /1.3/indicators/summaries endpoint
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TruSTARParser:
|
|
35
|
+
ENTITY_TYPE_MAPPINGS = {
|
|
36
|
+
'BITCOIN_ADDRESS': "btc",
|
|
37
|
+
'CIDR_BLOCK': "ip-src",
|
|
38
|
+
'CVE': "vulnerability",
|
|
39
|
+
'URL': "url",
|
|
40
|
+
'EMAIL_ADDRESS': "email-src",
|
|
41
|
+
'SOFTWARE': "filename",
|
|
42
|
+
'IP': "ip-src",
|
|
43
|
+
'MALWARE': "malware-type",
|
|
44
|
+
'MD5': "md5",
|
|
45
|
+
'REGISTRY_KEY': "regkey",
|
|
46
|
+
'SHA1': "sha1",
|
|
47
|
+
'SHA256': "sha256"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Relevant fields from each TruSTAR endpoint
|
|
51
|
+
SUMMARY_FIELDS = ["severityLevel", "source", "score", "attributes"]
|
|
52
|
+
METADATA_FIELDS = ["sightings", "firstSeen", "lastSeen", "tags"]
|
|
53
|
+
|
|
54
|
+
REPORT_BASE_URL = "https://station.trustar.co/constellation/reports/{}"
|
|
55
|
+
|
|
56
|
+
CLIENT_METATAG = f"MISP-{pymisp.__version__}"
|
|
57
|
+
|
|
58
|
+
def __init__(self, attribute, config):
|
|
59
|
+
config['enclave_ids'] = config.get('enclave_ids', "").strip().split(',')
|
|
60
|
+
config['client_metatag'] = self.CLIENT_METATAG
|
|
61
|
+
self.ts_client = TruStar(config=config)
|
|
62
|
+
|
|
63
|
+
self.misp_event = MISPEvent()
|
|
64
|
+
self.misp_attribute = MISPAttribute()
|
|
65
|
+
self.misp_attribute.from_dict(**attribute)
|
|
66
|
+
self.misp_event.add_attribute(**self.misp_attribute)
|
|
67
|
+
|
|
68
|
+
def get_results(self):
|
|
69
|
+
"""
|
|
70
|
+
Returns the MISP Event enriched with TruSTAR indicator summary data.
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
event = json.loads(self.misp_event.to_json())
|
|
74
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
75
|
+
return {'results': results}
|
|
76
|
+
except Exception as e:
|
|
77
|
+
misperrors['error'] += f" -- Encountered issue serializing enrichment data -- {e}"
|
|
78
|
+
return misperrors
|
|
79
|
+
|
|
80
|
+
def generate_trustar_link(self, entity_type, entity_value):
|
|
81
|
+
"""
|
|
82
|
+
Generates link to TruSTAR report of entity.
|
|
83
|
+
|
|
84
|
+
:param entity_type: <str> Type of entity.
|
|
85
|
+
:param entity_value: <str> Value of entity.
|
|
86
|
+
:return: <str> Link to indicator report in TruSTAR platform.
|
|
87
|
+
"""
|
|
88
|
+
report_id = b64encode(quote(f"{entity_type}|{entity_value}").encode()).decode()
|
|
89
|
+
|
|
90
|
+
return self.REPORT_BASE_URL.format(report_id)
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def extract_tags(enrichment_report):
|
|
94
|
+
"""
|
|
95
|
+
Extracts tags from the enrichment report in order to add them
|
|
96
|
+
to the TruSTAR MISP Object. Removes tags from report to avoid
|
|
97
|
+
redundancy.
|
|
98
|
+
|
|
99
|
+
:param: <OrderedDict> Enrichment data.
|
|
100
|
+
:return: <list> List of tags.
|
|
101
|
+
"""
|
|
102
|
+
if enrichment_report and enrichment_report.get('tags'):
|
|
103
|
+
return [tag.get('name') for tag in enrichment_report.pop('tags')]
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def generate_enrichment_report(self, summary, metadata):
|
|
107
|
+
"""
|
|
108
|
+
Extracts desired fields from summary and metadata reports and
|
|
109
|
+
generates an enrichment report.
|
|
110
|
+
|
|
111
|
+
:param summary: <trustar.IndicatorSummary> Indicator summary report.
|
|
112
|
+
:param metadata: <trustar.Indicator> Indicator metadata report.
|
|
113
|
+
:return: <str> Enrichment report.
|
|
114
|
+
"""
|
|
115
|
+
# Preserve order of fields as they exist in SUMMARY_FIELDS and METADATA_FIELDS
|
|
116
|
+
enrichment_report = OrderedDict()
|
|
117
|
+
|
|
118
|
+
if summary:
|
|
119
|
+
summary_dict = summary.to_dict()
|
|
120
|
+
enrichment_report.update(
|
|
121
|
+
{field: summary_dict[field] for field in self.SUMMARY_FIELDS if summary_dict.get(field)})
|
|
122
|
+
|
|
123
|
+
if metadata:
|
|
124
|
+
metadata_dict = metadata.to_dict()
|
|
125
|
+
enrichment_report.update(
|
|
126
|
+
{field: metadata_dict[field] for field in self.METADATA_FIELDS if metadata_dict.get(field)})
|
|
127
|
+
|
|
128
|
+
return enrichment_report
|
|
129
|
+
|
|
130
|
+
def parse_indicator_summary(self, indicator, summary, metadata):
|
|
131
|
+
"""
|
|
132
|
+
Pulls enrichment data from the TruSTAR /indicators/summaries and /indicators/metadata endpoints
|
|
133
|
+
and creates a MISP trustar_report.
|
|
134
|
+
|
|
135
|
+
:param indicator: <str> Value of the attribute
|
|
136
|
+
:summary: <trustar.IndicatorSummary> Indicator summary response object.
|
|
137
|
+
:metadata: <trustar.Indicator> Indicator response object.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# Verify that the indicator type is supported by TruSTAR
|
|
141
|
+
if summary and summary.indicator_type in self.ENTITY_TYPE_MAPPINGS:
|
|
142
|
+
indicator_type = summary.indicator_type
|
|
143
|
+
elif metadata and metadata.type in self.ENTITY_TYPE_MAPPINGS:
|
|
144
|
+
indicator_type = metadata.type
|
|
145
|
+
else:
|
|
146
|
+
misperrors['error'] += " -- Attribute not found or not supported"
|
|
147
|
+
raise Exception
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
# Extract most relevant fields from indicator summary and metadata responses
|
|
151
|
+
enrichment_report = self.generate_enrichment_report(summary, metadata)
|
|
152
|
+
tags = self.extract_tags(enrichment_report)
|
|
153
|
+
|
|
154
|
+
if enrichment_report:
|
|
155
|
+
# Create MISP trustar_report object and populate it with enrichment data
|
|
156
|
+
trustar_obj = MISPObject('trustar_report')
|
|
157
|
+
trustar_obj.add_attribute(indicator_type, attribute_type=self.ENTITY_TYPE_MAPPINGS[indicator_type],
|
|
158
|
+
value=indicator)
|
|
159
|
+
trustar_obj.add_attribute("INDICATOR_SUMMARY", attribute_type="text",
|
|
160
|
+
value=json.dumps(enrichment_report, indent=4))
|
|
161
|
+
|
|
162
|
+
report_link = self.generate_trustar_link(indicator_type, indicator)
|
|
163
|
+
trustar_obj.add_attribute("REPORT_LINK", attribute_type="link", value=report_link)
|
|
164
|
+
|
|
165
|
+
self.misp_event.add_object(**trustar_obj)
|
|
166
|
+
elif not tags:
|
|
167
|
+
# If enrichment report is empty and there are no tags, nothing to add to attribute
|
|
168
|
+
raise Exception("No relevant data found")
|
|
169
|
+
|
|
170
|
+
if tags:
|
|
171
|
+
for tag in tags:
|
|
172
|
+
self.misp_event.add_attribute_tag(tag, indicator)
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
misperrors['error'] += f" -- Error enriching attribute {indicator} -- {e}"
|
|
176
|
+
raise e
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def handler(q=False):
|
|
180
|
+
"""
|
|
181
|
+
MISP handler function. A user's API key and secret will be retrieved from the MISP
|
|
182
|
+
request and used to create a TruSTAR API client. If enclave IDs are provided, only
|
|
183
|
+
those enclaves will be queried for data. Otherwise, all of the enclaves a user has
|
|
184
|
+
access to will be queried.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
if q is False:
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
request = json.loads(q)
|
|
191
|
+
|
|
192
|
+
config = request.get('config', {})
|
|
193
|
+
if not config.get('user_api_key') or not config.get('user_api_secret'):
|
|
194
|
+
misperrors['error'] = "Your TruSTAR API key and secret are required for indicator enrichment."
|
|
195
|
+
return misperrors
|
|
196
|
+
|
|
197
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute'], requirements=('type', 'value')):
|
|
198
|
+
return {'error': f'{standard_error_message}, {checking_error}.'}
|
|
199
|
+
attribute = request['attribute']
|
|
200
|
+
if attribute['type'] not in mispattributes['input']:
|
|
201
|
+
return {'error': 'Unsupported attribute type.'}
|
|
202
|
+
trustar_parser = TruSTARParser(attribute, config)
|
|
203
|
+
metadata = None
|
|
204
|
+
summary = None
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
metadata = trustar_parser.ts_client.get_indicators_metadata([Indicator(value=attribute['value'])])[0]
|
|
208
|
+
except IndexError:
|
|
209
|
+
misperrors['error'] += f" -- No metadata found for indicator {attribute['value']}"
|
|
210
|
+
except Exception as e:
|
|
211
|
+
misperrors['error'] += f" -- Could not retrieve indicator metadata from TruSTAR {e}"
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
summary = list(
|
|
215
|
+
trustar_parser.ts_client.get_indicator_summaries([attribute['value']], page_size=MAX_PAGE_SIZE))[0]
|
|
216
|
+
except IndexError:
|
|
217
|
+
misperrors['error'] += f" -- No summary data found for indicator {attribute['value']}"
|
|
218
|
+
except Exception as e:
|
|
219
|
+
misperrors['error'] += f" -- Unable to retrieve TruSTAR summary data: {e}"
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
trustar_parser.parse_indicator_summary(attribute['value'], summary, metadata)
|
|
223
|
+
except Exception:
|
|
224
|
+
return misperrors
|
|
225
|
+
|
|
226
|
+
return trustar_parser.get_results()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def introspection():
|
|
230
|
+
return mispattributes
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def version():
|
|
234
|
+
moduleinfo['config'] = moduleconfig
|
|
235
|
+
return moduleinfo
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
from . import check_input_attribute, standard_error_message
|
|
5
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
mispattributes = {'input': ['domain', 'hostname', 'ip-src', 'ip-dst', 'md5', 'sha256', 'url'],
|
|
9
|
+
'output': ['url', 'filename', 'md5', 'sha256'],
|
|
10
|
+
'format': 'misp_standard'}
|
|
11
|
+
moduleinfo = {
|
|
12
|
+
'version': '0.1',
|
|
13
|
+
'author': 'Christian Studer',
|
|
14
|
+
'description': 'Query of the URLhaus API to get additional information about the input attribute.',
|
|
15
|
+
'module-type': ['expansion', 'hover'],
|
|
16
|
+
'name': 'URLhaus Lookup',
|
|
17
|
+
'logo': 'urlhaus.png',
|
|
18
|
+
'requirements': [],
|
|
19
|
+
'features': 'Module using the new format of modules able to return attributes and objects.\n\nThe module takes one of the attribute type specified as input, and query the URLhaus API with it. If any result is returned by the API, attributes and objects are created accordingly.',
|
|
20
|
+
'references': ['https://urlhaus.abuse.ch/'],
|
|
21
|
+
'input': 'A domain, hostname, url, ip, md5 or sha256 attribute.',
|
|
22
|
+
'output': 'MISP attributes & objects fetched from the result of the URLhaus API query.',
|
|
23
|
+
}
|
|
24
|
+
moduleconfig = []
|
|
25
|
+
|
|
26
|
+
file_keys = ('filename', 'response_size', 'response_md5', 'response_sha256')
|
|
27
|
+
file_relations = ('filename', 'size-in-bytes', 'md5', 'sha256')
|
|
28
|
+
vt_keys = ('result', 'link')
|
|
29
|
+
vt_types = ('text', 'link')
|
|
30
|
+
vt_relations = ('detection-ratio', 'permalink')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class URLhaus():
|
|
34
|
+
def __init__(self):
|
|
35
|
+
super(URLhaus, self).__init__()
|
|
36
|
+
self.misp_event = MISPEvent()
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _create_vt_object(virustotal):
|
|
40
|
+
vt_object = MISPObject('virustotal-report')
|
|
41
|
+
for key, vt_type, relation in zip(vt_keys, vt_types, vt_relations):
|
|
42
|
+
vt_object.add_attribute(relation, **{'type': vt_type, 'value': virustotal[key]})
|
|
43
|
+
return vt_object
|
|
44
|
+
|
|
45
|
+
def get_result(self):
|
|
46
|
+
event = json.loads(self.misp_event.to_json())
|
|
47
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
48
|
+
return {'results': results}
|
|
49
|
+
|
|
50
|
+
def parse_error(self, query_status):
|
|
51
|
+
if query_status == 'no_results':
|
|
52
|
+
return {'error': f'No results found on URLhaus for this {self.attribute.type} attribute'}
|
|
53
|
+
return {'error': f'Error encountered during the query of URLhaus: {query_status}'}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class HostQuery(URLhaus):
|
|
57
|
+
def __init__(self, attribute):
|
|
58
|
+
super(HostQuery, self).__init__()
|
|
59
|
+
self.attribute = MISPAttribute()
|
|
60
|
+
self.attribute.from_dict(**attribute)
|
|
61
|
+
self.url = 'https://urlhaus-api.abuse.ch/v1/host/'
|
|
62
|
+
|
|
63
|
+
def query_api(self):
|
|
64
|
+
response = requests.post(self.url, data={'host': self.attribute.value}).json()
|
|
65
|
+
if response['query_status'] != 'ok':
|
|
66
|
+
return self.parse_error(response['query_status'])
|
|
67
|
+
if 'urls' in response and response['urls']:
|
|
68
|
+
for url in response['urls']:
|
|
69
|
+
self.misp_event.add_attribute(type='url', value=url['url'])
|
|
70
|
+
return self.get_result()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PayloadQuery(URLhaus):
|
|
74
|
+
def __init__(self, attribute):
|
|
75
|
+
super(PayloadQuery, self).__init__()
|
|
76
|
+
self.attribute = MISPAttribute()
|
|
77
|
+
self.attribute.from_dict(**attribute)
|
|
78
|
+
self.url = 'https://urlhaus-api.abuse.ch/v1/payload/'
|
|
79
|
+
|
|
80
|
+
def query_api(self):
|
|
81
|
+
hash_type = self.attribute.type
|
|
82
|
+
file_object = MISPObject('file')
|
|
83
|
+
if hasattr(self.attribute, 'object_id') and hasattr(self.attribute, 'event_id') and self.attribute.event_id != '0':
|
|
84
|
+
file_object.id = self.attribute.object_id
|
|
85
|
+
response = requests.post(self.url, data={'{}_hash'.format(hash_type): self.attribute.value}).json()
|
|
86
|
+
if response['query_status'] != 'ok':
|
|
87
|
+
return self.parse_error(response['query_status'])
|
|
88
|
+
other_hash_type = 'md5' if hash_type == 'sha256' else 'sha256'
|
|
89
|
+
for key, relation in zip(('{}_hash'.format(other_hash_type), 'file_size'), (other_hash_type, 'size-in-bytes')):
|
|
90
|
+
if response[key]:
|
|
91
|
+
file_object.add_attribute(relation, **{'type': relation, 'value': response[key]})
|
|
92
|
+
if response['virustotal']:
|
|
93
|
+
vt_object = self._create_vt_object(response['virustotal'])
|
|
94
|
+
file_object.add_reference(vt_object.uuid, 'analyzed-with')
|
|
95
|
+
self.misp_event.add_object(**vt_object)
|
|
96
|
+
_filename_ = 'filename'
|
|
97
|
+
for url in response['urls']:
|
|
98
|
+
attribute = MISPAttribute()
|
|
99
|
+
attribute.from_dict(type='url', value=url['url'])
|
|
100
|
+
self.misp_event.add_attribute(**attribute)
|
|
101
|
+
file_object.add_reference(attribute.uuid, 'retrieved-from')
|
|
102
|
+
if url[_filename_]:
|
|
103
|
+
file_object.add_attribute(_filename_, **{'type': _filename_, 'value': url[_filename_]})
|
|
104
|
+
if any((file_object.attributes, file_object.references)):
|
|
105
|
+
self.misp_event.add_object(**file_object)
|
|
106
|
+
return self.get_result()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class UrlQuery(URLhaus):
|
|
110
|
+
def __init__(self, attribute):
|
|
111
|
+
super(UrlQuery, self).__init__()
|
|
112
|
+
self.attribute = MISPAttribute()
|
|
113
|
+
self.attribute.from_dict(**attribute)
|
|
114
|
+
self.url = 'https://urlhaus-api.abuse.ch/v1/url/'
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _create_file_object(payload):
|
|
118
|
+
file_object = MISPObject('file')
|
|
119
|
+
for key, relation in zip(file_keys, file_relations):
|
|
120
|
+
if payload[key]:
|
|
121
|
+
file_object.add_attribute(relation, **{'type': relation, 'value': payload[key]})
|
|
122
|
+
return file_object
|
|
123
|
+
|
|
124
|
+
def query_api(self):
|
|
125
|
+
response = requests.post(self.url, data={'url': self.attribute.value}).json()
|
|
126
|
+
if response['query_status'] != 'ok':
|
|
127
|
+
return self.parse_error(response['query_status'])
|
|
128
|
+
if 'payloads' in response and response['payloads']:
|
|
129
|
+
for payload in response['payloads']:
|
|
130
|
+
file_object = self._create_file_object(payload)
|
|
131
|
+
if payload['virustotal']:
|
|
132
|
+
vt_object = self._create_vt_object(payload['virustotal'])
|
|
133
|
+
file_object.add_reference(vt_object.uuid, 'analyzed-with')
|
|
134
|
+
self.misp_event.add_object(**vt_object)
|
|
135
|
+
if any((file_object.attributes, file_object.references)):
|
|
136
|
+
self.misp_event.add_object(**file_object)
|
|
137
|
+
return self.get_result()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
_misp_type_mapping = {'url': UrlQuery, 'md5': PayloadQuery, 'sha256': PayloadQuery,
|
|
141
|
+
'domain': HostQuery, 'hostname': HostQuery,
|
|
142
|
+
'ip-src': HostQuery, 'ip-dst': HostQuery}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def handler(q=False):
|
|
146
|
+
if q is False:
|
|
147
|
+
return False
|
|
148
|
+
request = json.loads(q)
|
|
149
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
150
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
151
|
+
attribute = request['attribute']
|
|
152
|
+
if attribute['type'] not in mispattributes['input']:
|
|
153
|
+
return {'error': 'Unsupported attribute type.'}
|
|
154
|
+
urlhaus_parser = _misp_type_mapping[attribute['type']](attribute)
|
|
155
|
+
return urlhaus_parser.query_api()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def introspection():
|
|
159
|
+
return mispattributes
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def version():
|
|
163
|
+
moduleinfo['config'] = moduleconfig
|
|
164
|
+
return moduleinfo
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger('urlscan')
|
|
8
|
+
log.setLevel(logging.DEBUG)
|
|
9
|
+
ch = logging.StreamHandler(sys.stdout)
|
|
10
|
+
ch.setLevel(logging.DEBUG)
|
|
11
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
12
|
+
ch.setFormatter(formatter)
|
|
13
|
+
log.addHandler(ch)
|
|
14
|
+
|
|
15
|
+
moduleinfo = {
|
|
16
|
+
'version': '0.1',
|
|
17
|
+
'author': 'Dave Johnson',
|
|
18
|
+
'description': 'An expansion module to query urlscan.io.',
|
|
19
|
+
'module-type': ['expansion'],
|
|
20
|
+
'name': 'URLScan Lookup',
|
|
21
|
+
'logo': 'urlscan.jpg',
|
|
22
|
+
'requirements': ['An access to the urlscan.io API'],
|
|
23
|
+
'features': 'This module takes a MISP attribute as input and queries urlscan.io with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute.',
|
|
24
|
+
'references': ['https://urlscan.io/'],
|
|
25
|
+
'input': 'A domain, hostname or url attribute.',
|
|
26
|
+
'output': 'MISP attributes mapped from the result of the query on urlscan.io.',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
moduleconfig = ['apikey']
|
|
30
|
+
misperrors = {'error': 'Error'}
|
|
31
|
+
mispattributes = {
|
|
32
|
+
'input': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url'],
|
|
33
|
+
'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link', 'hash']
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def handler(q=False):
|
|
38
|
+
if q is False:
|
|
39
|
+
return False
|
|
40
|
+
request = json.loads(q)
|
|
41
|
+
if not request.get('config') or not request['config'].get('apikey'):
|
|
42
|
+
misperrors['error'] = 'Urlscan apikey is missing'
|
|
43
|
+
return misperrors
|
|
44
|
+
client = urlscanAPI(request['config']['apikey'])
|
|
45
|
+
|
|
46
|
+
r = {'results': []}
|
|
47
|
+
|
|
48
|
+
if 'ip-src' in request:
|
|
49
|
+
r['results'] += lookup_indicator(client, request['ip-src'])
|
|
50
|
+
if 'ip-dst' in request:
|
|
51
|
+
r['results'] += lookup_indicator(client, request['ip-dst'])
|
|
52
|
+
if 'domain' in request:
|
|
53
|
+
r['results'] += lookup_indicator(client, request['domain'])
|
|
54
|
+
if 'hostname' in request:
|
|
55
|
+
r['results'] += lookup_indicator(client, request['hostname'])
|
|
56
|
+
if 'url' in request:
|
|
57
|
+
r['results'] += lookup_indicator(client, request['url'])
|
|
58
|
+
|
|
59
|
+
# Return any errors generated from lookup to the UI and remove duplicates
|
|
60
|
+
|
|
61
|
+
uniq = []
|
|
62
|
+
log.debug(r['results'])
|
|
63
|
+
for item in r['results']:
|
|
64
|
+
log.debug(item)
|
|
65
|
+
if 'error' in item:
|
|
66
|
+
misperrors['error'] = item['error']
|
|
67
|
+
return misperrors
|
|
68
|
+
if item not in uniq:
|
|
69
|
+
uniq.append(item)
|
|
70
|
+
r['results'] = uniq
|
|
71
|
+
return r
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def lookup_indicator(client, query):
|
|
75
|
+
result = client.search_url(query)
|
|
76
|
+
log.debug('RESULTS: ' + json.dumps(result))
|
|
77
|
+
r = []
|
|
78
|
+
misp_comment = "{}: Enriched via the urlscan module".format(query)
|
|
79
|
+
|
|
80
|
+
# Determine if the page is reachable
|
|
81
|
+
for request in result['data']['requests']:
|
|
82
|
+
if request['response'].get('failed'):
|
|
83
|
+
if request['response']['failed']['errorText']:
|
|
84
|
+
log.debug('The page could not load')
|
|
85
|
+
r.append(
|
|
86
|
+
{'error': 'Domain could not be resolved: {}'.format(request['response']['failed']['errorText'])})
|
|
87
|
+
|
|
88
|
+
if result.get('page'):
|
|
89
|
+
if result['page'].get('domain'):
|
|
90
|
+
misp_val = result['page']['domain']
|
|
91
|
+
r.append({'types': 'domain',
|
|
92
|
+
'categories': ['Network activity'],
|
|
93
|
+
'values': misp_val,
|
|
94
|
+
'comment': misp_comment})
|
|
95
|
+
|
|
96
|
+
if result['page'].get('ip'):
|
|
97
|
+
misp_val = result['page']['ip']
|
|
98
|
+
r.append({'types': 'ip-dst',
|
|
99
|
+
'categories': ['Network activity'],
|
|
100
|
+
'values': misp_val,
|
|
101
|
+
'comment': misp_comment})
|
|
102
|
+
|
|
103
|
+
if result['page'].get('country'):
|
|
104
|
+
misp_val = 'country: ' + result['page']['country']
|
|
105
|
+
if result['page'].get('city'):
|
|
106
|
+
misp_val += ', city: ' + result['page']['city']
|
|
107
|
+
r.append({'types': 'text',
|
|
108
|
+
'categories': ['External analysis'],
|
|
109
|
+
'values': misp_val,
|
|
110
|
+
'comment': misp_comment})
|
|
111
|
+
|
|
112
|
+
if result['page'].get('asn'):
|
|
113
|
+
misp_val = result['page']['asn']
|
|
114
|
+
r.append({'types': 'AS', 'categories': ['External analysis'], 'values': misp_val, 'comment': misp_comment})
|
|
115
|
+
|
|
116
|
+
if result['page'].get('asnname'):
|
|
117
|
+
misp_val = result['page']['asnname']
|
|
118
|
+
r.append({'types': 'text',
|
|
119
|
+
'categories': ['External analysis'],
|
|
120
|
+
'values': misp_val,
|
|
121
|
+
'comment': misp_comment})
|
|
122
|
+
|
|
123
|
+
if result.get('stats'):
|
|
124
|
+
if result['stats'].get('malicious'):
|
|
125
|
+
log.debug('There is something in results > stats > malicious')
|
|
126
|
+
threat_list = set()
|
|
127
|
+
|
|
128
|
+
if 'matches' in result['meta']['processors']['gsb']['data']:
|
|
129
|
+
for item in result['meta']['processors']['gsb']['data']['matches']:
|
|
130
|
+
if item['threatType']:
|
|
131
|
+
threat_list.add(item['threatType'])
|
|
132
|
+
|
|
133
|
+
threat_list = ', '.join(threat_list)
|
|
134
|
+
log.debug('threat_list values are: \'' + threat_list + '\'')
|
|
135
|
+
|
|
136
|
+
if threat_list:
|
|
137
|
+
misp_val = '{} threat(s) detected'.format(threat_list)
|
|
138
|
+
r.append({'types': 'text',
|
|
139
|
+
'categories': ['External analysis'],
|
|
140
|
+
'values': misp_val,
|
|
141
|
+
'comment': misp_comment})
|
|
142
|
+
|
|
143
|
+
if result.get('lists'):
|
|
144
|
+
if result['lists'].get('urls'):
|
|
145
|
+
for url in result['lists']['urls']:
|
|
146
|
+
url = url.lower()
|
|
147
|
+
if 'office' in url:
|
|
148
|
+
misp_val = "Possible Office-themed phishing"
|
|
149
|
+
elif 'o365' in url or '0365' in url:
|
|
150
|
+
misp_val = "Possible O365-themed phishing"
|
|
151
|
+
elif 'microsoft' in url:
|
|
152
|
+
misp_val = "Possible Microsoft-themed phishing"
|
|
153
|
+
elif 'paypal' in url:
|
|
154
|
+
misp_val = "Possible PayPal-themed phishing"
|
|
155
|
+
elif 'onedrive' in url:
|
|
156
|
+
misp_val = "Possible OneDrive-themed phishing"
|
|
157
|
+
elif 'docusign' in url:
|
|
158
|
+
misp_val = "Possible DocuSign-themed phishing"
|
|
159
|
+
r.append({'types': 'text',
|
|
160
|
+
'categories': ['External analysis'],
|
|
161
|
+
'values': misp_val,
|
|
162
|
+
'comment': misp_comment})
|
|
163
|
+
|
|
164
|
+
if result.get('task'):
|
|
165
|
+
if result['task'].get('reportURL'):
|
|
166
|
+
misp_val = result['task']['reportURL']
|
|
167
|
+
r.append({'types': 'link',
|
|
168
|
+
'categories': ['External analysis'],
|
|
169
|
+
'values': misp_val,
|
|
170
|
+
'comment': misp_comment})
|
|
171
|
+
|
|
172
|
+
if result['task'].get('screenshotURL'):
|
|
173
|
+
image_url = result['task']['screenshotURL']
|
|
174
|
+
r.append({'types': 'link',
|
|
175
|
+
'categories': ['External analysis'],
|
|
176
|
+
'values': image_url,
|
|
177
|
+
'comment': misp_comment})
|
|
178
|
+
# ## TO DO ###
|
|
179
|
+
# ## Add ability to add an in-line screenshot of the target website into an attribute
|
|
180
|
+
# screenshot = requests.get(image_url).content
|
|
181
|
+
# r.append({'types': ['attachment'],
|
|
182
|
+
# 'categories': ['External analysis'],
|
|
183
|
+
# 'values': image_url,
|
|
184
|
+
# 'image': str(base64.b64encode(screenshot), 'utf-8'),
|
|
185
|
+
# 'comment': 'Screenshot of website'})
|
|
186
|
+
|
|
187
|
+
return r
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def introspection():
|
|
191
|
+
return mispattributes
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def version():
|
|
195
|
+
moduleinfo['config'] = moduleconfig
|
|
196
|
+
return moduleinfo
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class urlscanAPI():
|
|
200
|
+
def __init__(self, apikey=None, uuid=None):
|
|
201
|
+
self.key = apikey
|
|
202
|
+
self.uuid = uuid
|
|
203
|
+
|
|
204
|
+
def request(self, query):
|
|
205
|
+
log.debug('From request function with the parameter: ' + query)
|
|
206
|
+
payload = {'url': query}
|
|
207
|
+
headers = {'API-Key': self.key,
|
|
208
|
+
'Content-Type': "application/json",
|
|
209
|
+
'Cache-Control': "no-cache"}
|
|
210
|
+
|
|
211
|
+
# Troubleshooting problems with initial search request
|
|
212
|
+
log.debug('PAYLOAD: ' + json.dumps(payload))
|
|
213
|
+
log.debug('HEADERS: ' + json.dumps(headers))
|
|
214
|
+
|
|
215
|
+
search_url_string = "https://urlscan.io/api/v1/scan/"
|
|
216
|
+
response = requests.request("POST",
|
|
217
|
+
search_url_string,
|
|
218
|
+
data=json.dumps(payload),
|
|
219
|
+
headers=headers)
|
|
220
|
+
|
|
221
|
+
# HTTP 400 - Bad Request
|
|
222
|
+
if response.status_code == 400:
|
|
223
|
+
raise Exception('HTTP Error 400 - Bad Request')
|
|
224
|
+
|
|
225
|
+
# HTTP 404 - Not found
|
|
226
|
+
if response.status_code == 404:
|
|
227
|
+
raise Exception('HTTP Error 404 - These are not the droids you\'re looking for')
|
|
228
|
+
|
|
229
|
+
# Any other status code
|
|
230
|
+
if response.status_code != 200:
|
|
231
|
+
raise Exception('HTTP Error ' + str(response.status_code))
|
|
232
|
+
|
|
233
|
+
if response.text:
|
|
234
|
+
response = json.loads(response.content.decode("utf-8"))
|
|
235
|
+
time.sleep(3)
|
|
236
|
+
self.uuid = response['uuid']
|
|
237
|
+
|
|
238
|
+
# Strings for to check for errors on the results page
|
|
239
|
+
# Null response string for any unavailable resources
|
|
240
|
+
null_response_string = '"status": 404'
|
|
241
|
+
# Redirect string accounting for 301/302/303/307/308 status codes
|
|
242
|
+
redirect_string = '"status": 30'
|
|
243
|
+
# Normal response string with 200 status code
|
|
244
|
+
normal_response_string = '"status": 200'
|
|
245
|
+
|
|
246
|
+
results_url_string = "https://urlscan.io/api/v1/result/" + self.uuid
|
|
247
|
+
log.debug('Results URL: ' + results_url_string)
|
|
248
|
+
|
|
249
|
+
# Need to wait for results to process and check if they are valid
|
|
250
|
+
tries = 10
|
|
251
|
+
while tries >= 0:
|
|
252
|
+
results = requests.request("GET", results_url_string)
|
|
253
|
+
log.debug('Made a GET request')
|
|
254
|
+
results = results.content.decode("utf-8")
|
|
255
|
+
# checking if there is a 404 status code and no available resources
|
|
256
|
+
if null_response_string in results and \
|
|
257
|
+
redirect_string not in results and \
|
|
258
|
+
normal_response_string not in results:
|
|
259
|
+
log.debug('Results not processed. Please check again later.')
|
|
260
|
+
time.sleep(3)
|
|
261
|
+
tries -= 1
|
|
262
|
+
else:
|
|
263
|
+
return json.loads(results)
|
|
264
|
+
|
|
265
|
+
raise Exception('Results contained a 404 status error and could not be processed.')
|
|
266
|
+
|
|
267
|
+
def search_url(self, query):
|
|
268
|
+
log.debug('From search_url with parameter: ' + query)
|
|
269
|
+
return self.request(query)
|