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,346 @@
|
|
|
1
|
+
import ipaddress
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from greynoise import GreyNoise
|
|
8
|
+
except ImportError:
|
|
9
|
+
print("greynoise module not installed.")
|
|
10
|
+
from pymisp import MISPAttribute, MISPEvent, MISPObject
|
|
11
|
+
|
|
12
|
+
from . import check_input_attribute, standard_error_message
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("greynoise")
|
|
15
|
+
logger.setLevel(logging.INFO)
|
|
16
|
+
|
|
17
|
+
misperrors = {"error": "Error"}
|
|
18
|
+
mispattributes = {"input": ["ip-src", "ip-dst", "vulnerability"], "format": "misp_standard"}
|
|
19
|
+
moduleinfo = {
|
|
20
|
+
'version': '1.2',
|
|
21
|
+
'author': 'Brad Chiappetta <brad@greynoise.io>',
|
|
22
|
+
'description': 'Module to query IP and CVE information from GreyNoise',
|
|
23
|
+
'module-type': ['expansion', 'hover'],
|
|
24
|
+
'name': 'GreyNoise Lookup',
|
|
25
|
+
'logo': 'greynoise.png',
|
|
26
|
+
'requirements': ['A Greynoise API key. Both Enterprise (Paid) and Community (Free) API keys are supported, however Community API users will only be able to perform IP lookups.'],
|
|
27
|
+
'features': 'This module supports: 1) Query an IP from GreyNoise to see if it is internet background noise or a common business service 2) Query a CVE from GreyNoise to see the total number of internet scanners looking for the CVE in the last 7 days.',
|
|
28
|
+
'references': ['https://greynoise.io/', 'https://docs.greyniose.io/', 'https://www.greynoise.io/viz/account/'],
|
|
29
|
+
'input': 'An IP address or CVE ID',
|
|
30
|
+
'output': 'IP Lookup information or CVE scanning profile for past 7 days',
|
|
31
|
+
}
|
|
32
|
+
moduleconfig = ["api_key", "api_type"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GreyNoiseParser:
|
|
36
|
+
def __init__(self, attribute):
|
|
37
|
+
self.misp_event = MISPEvent()
|
|
38
|
+
self.attribute = MISPAttribute()
|
|
39
|
+
self.attribute.from_dict(**attribute)
|
|
40
|
+
self.misp_event.add_attribute(**self.attribute)
|
|
41
|
+
self.ip_address_enrich_mapping = {
|
|
42
|
+
"noise": {"type": "boolean", "object_relation": "noise"},
|
|
43
|
+
"riot": {"type": "boolean", "object_relation": "riot"},
|
|
44
|
+
"classification": {"type": "text", "object_relation": "classification"},
|
|
45
|
+
"actor": {"type": "text", "object_relation": "actor"},
|
|
46
|
+
"trust_level": {"type": "text", "object_relation": "trust-level"},
|
|
47
|
+
"name": {"type": "text", "object_relation": "provider"},
|
|
48
|
+
"first_seen": {"type": "datetime", "object_relation": "first-seen"},
|
|
49
|
+
"last_seen": {"type": "datetime", "object_relation": "last-seen"},
|
|
50
|
+
"link": {"type": "url", "object_relation": "link"},
|
|
51
|
+
"last_updated": {"type": "datetime", "object_relation": "last-seen"},
|
|
52
|
+
}
|
|
53
|
+
self.ip_address_hover_mapping = {
|
|
54
|
+
"noise": {"type": "boolean", "object_relation": "noise"},
|
|
55
|
+
"riot": {"type": "boolean", "object_relation": "riot"},
|
|
56
|
+
"classification": {"type": "text", "object_relation": "classification"},
|
|
57
|
+
"actor": {"type": "text", "object_relation": "actor"},
|
|
58
|
+
"tags": {"type": "text", "object_relation": "tags"},
|
|
59
|
+
"cve": {"type": "text", "object_relation": "cve"},
|
|
60
|
+
"vpn": {"type": "text", "object_relation": "vpn"},
|
|
61
|
+
"vpn_service": {"type": "text", "object_relation": "vpn_service"},
|
|
62
|
+
"bot": {"type": "text", "object_relation": "bot"},
|
|
63
|
+
"first_seen": {"type": "datetime", "object_relation": "first-seen"},
|
|
64
|
+
"last_seen": {"type": "datetime", "object_relation": "last-seen"},
|
|
65
|
+
"spoofable": {"type": "datetime", "object_relation": "spoofable"},
|
|
66
|
+
"link": {"type": "url", "object_relation": "link"},
|
|
67
|
+
"category": {"type": "text", "object_relation": "category"},
|
|
68
|
+
"name": {"type": "text", "object_relation": "provider"},
|
|
69
|
+
"trust_level": {"type": "text", "object_relation": "trust-level"},
|
|
70
|
+
"last_updated": {"type": "datetime", "object_relation": "last_updated"},
|
|
71
|
+
}
|
|
72
|
+
self.ip_address_metadata_mapping = {
|
|
73
|
+
"tor": {"type": "text", "object_relation": "tor"},
|
|
74
|
+
"asn": {"type": "AS", "object_relation": "asn"},
|
|
75
|
+
"city": {"type": "text", "object_relation": "city"},
|
|
76
|
+
"country_code": {"type": "text", "object_relation": "country-code"},
|
|
77
|
+
"country": {"type": "text", "object_relation": "country"},
|
|
78
|
+
"organization": {"type": "text", "object_relation": "organization"},
|
|
79
|
+
"destination_country_codes": {"type": "text", "object_relation": "destination-country-codes"},
|
|
80
|
+
"destination_countries": {"type": "text", "object_relation": "destination-countries"},
|
|
81
|
+
"category": {"type": "text", "object_relation": "category"},
|
|
82
|
+
"rdns": {"type": "text", "object_relation": "rdns"},
|
|
83
|
+
}
|
|
84
|
+
self.vulnerability_mapping = {
|
|
85
|
+
"id": {"type": "text", "object_relation": "id"},
|
|
86
|
+
"details": {"type": "text", "object_relation": "details"},
|
|
87
|
+
"count": {"type": "text", "object_relation": "total-count"},
|
|
88
|
+
"benign": {"type": "text", "object_relation": "benign-count"},
|
|
89
|
+
"malicious": {"type": "text", "object_relation": "malicious-count"},
|
|
90
|
+
"unknown": {"type": "text", "object_relation": "unknown-count"},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def query_greynoise_ip_hover(self, api_key, api_type):
|
|
94
|
+
if api_type == "enterprise":
|
|
95
|
+
logger.info(f"Starting hover enrichment for: {self.attribute.value} via GreyNoise ENT API")
|
|
96
|
+
integration_name = "greynoise-misp-module-{}".format(moduleinfo["version"])
|
|
97
|
+
session = GreyNoise(api_key=api_key, integration_name=integration_name)
|
|
98
|
+
quick_response = session.quick(self.attribute.value)
|
|
99
|
+
if len(quick_response) != 1:
|
|
100
|
+
misperrors["error"] = "Quick IP lookup returned unexpected response"
|
|
101
|
+
return misperrors
|
|
102
|
+
else:
|
|
103
|
+
quick_response = quick_response[0]
|
|
104
|
+
context_response = session.ip(self.attribute.value)
|
|
105
|
+
riot_response = session.riot(self.attribute.value)
|
|
106
|
+
|
|
107
|
+
if riot_response and "trust_level" in riot_response:
|
|
108
|
+
if riot_response["trust_level"] == "1":
|
|
109
|
+
riot_response["trust_level"] = "1 - Reasonably Ignore"
|
|
110
|
+
if riot_response["trust_level"] == "2":
|
|
111
|
+
riot_response["trust_level"] = "2 - Commonly Seen"
|
|
112
|
+
|
|
113
|
+
if context_response and riot_response:
|
|
114
|
+
response = context_response.copy()
|
|
115
|
+
response.update(riot_response)
|
|
116
|
+
response.update(quick_response)
|
|
117
|
+
elif context_response:
|
|
118
|
+
response = context_response.copy()
|
|
119
|
+
response.update(quick_response)
|
|
120
|
+
elif riot_response:
|
|
121
|
+
response = riot_response.copy()
|
|
122
|
+
response.update(quick_response)
|
|
123
|
+
|
|
124
|
+
response["link"] = "https://viz.greynoise.io/ip/" + self.attribute.value
|
|
125
|
+
|
|
126
|
+
ip_address_attributes = []
|
|
127
|
+
for feature, mapping in self.ip_address_hover_mapping.items():
|
|
128
|
+
logger.debug(f"Checking feature {feature}")
|
|
129
|
+
if response.get(feature):
|
|
130
|
+
if feature in ["cve", "tags"]:
|
|
131
|
+
response[feature] = ", ".join(response[feature])
|
|
132
|
+
if feature == "vpn_service" and response[feature] == "N/A":
|
|
133
|
+
continue
|
|
134
|
+
if feature == "actor" and response[feature] == "unknown":
|
|
135
|
+
continue
|
|
136
|
+
attribute = {"value": response[feature]}
|
|
137
|
+
logger.debug(f"Adding Feature: {feature}, Attribute: {attribute}")
|
|
138
|
+
attribute.update(mapping)
|
|
139
|
+
ip_address_attributes.append(attribute)
|
|
140
|
+
if "metadata" in context_response:
|
|
141
|
+
for feature, mapping in self.ip_address_metadata_mapping.items():
|
|
142
|
+
logger.debug(f"Checking metadata feature {feature}")
|
|
143
|
+
if response["metadata"].get(feature):
|
|
144
|
+
if feature in ["destination_countries", "destination_country_codes"]:
|
|
145
|
+
response["metadata"][feature] = ", ".join(response["metadata"][feature])
|
|
146
|
+
attribute = {"value": response["metadata"][feature]}
|
|
147
|
+
logger.debug(f"Adding Feature: {feature}, Attribute: {attribute}")
|
|
148
|
+
attribute.update(mapping)
|
|
149
|
+
ip_address_attributes.append(attribute)
|
|
150
|
+
if ip_address_attributes:
|
|
151
|
+
logger.debug("creating greynoise ip object")
|
|
152
|
+
gn_ip_object = MISPObject("greynoise-ip-details")
|
|
153
|
+
for attribute in ip_address_attributes:
|
|
154
|
+
logger.debug(f"adding attribute {attribute}")
|
|
155
|
+
gn_ip_object.add_attribute(**attribute)
|
|
156
|
+
logger.debug(f"attribute id: {self.attribute.uuid}")
|
|
157
|
+
gn_ip_object.add_reference(self.attribute.uuid, "describes")
|
|
158
|
+
self.misp_event.add_object(gn_ip_object)
|
|
159
|
+
else:
|
|
160
|
+
logger.info(f"Starting hover enrichment for: {self.attribute.value} via GreyNoise Community API")
|
|
161
|
+
integration_name = "greynoise-community-misp-module-{}".format(moduleinfo["version"])
|
|
162
|
+
session = GreyNoise(api_key=api_key, integration_name=integration_name, offering="community")
|
|
163
|
+
community_response = session.ip(self.attribute.value)
|
|
164
|
+
|
|
165
|
+
if "noise" in community_response and community_response["noise"]:
|
|
166
|
+
community_response["actor"] = community_response["name"]
|
|
167
|
+
community_response.pop("name")
|
|
168
|
+
|
|
169
|
+
ip_address_attributes = []
|
|
170
|
+
for feature, mapping in self.ip_address_hover_mapping.items():
|
|
171
|
+
if community_response.get(feature):
|
|
172
|
+
if feature == "actor" and community_response[feature] == "unknown":
|
|
173
|
+
continue
|
|
174
|
+
attribute = {"value": community_response[feature]}
|
|
175
|
+
attribute.update(mapping)
|
|
176
|
+
ip_address_attributes.append(attribute)
|
|
177
|
+
if ip_address_attributes:
|
|
178
|
+
ip_address_object = MISPObject("greynoise-ip-details")
|
|
179
|
+
for attribute in ip_address_attributes:
|
|
180
|
+
ip_address_object.add_attribute(**attribute)
|
|
181
|
+
ip_address_object.add_reference(self.attribute.uuid, "describes")
|
|
182
|
+
self.misp_event.add_object(ip_address_object)
|
|
183
|
+
|
|
184
|
+
def query_greynoise_ip_expansion(self, api_key, api_type):
|
|
185
|
+
if api_type == "enterprise":
|
|
186
|
+
logger.info(f"Starting expansion enrichment for: {self.attribute.value} via GreyNoise ENT API")
|
|
187
|
+
integration_name = "greynoise-misp-module-{}".format(moduleinfo["version"])
|
|
188
|
+
session = GreyNoise(api_key=api_key, integration_name=integration_name)
|
|
189
|
+
quick_response = session.quick(self.attribute.value)
|
|
190
|
+
if len(quick_response) != 1:
|
|
191
|
+
misperrors["error"] = "Quick IP lookup returned unexpected response"
|
|
192
|
+
return misperrors
|
|
193
|
+
else:
|
|
194
|
+
quick_response = quick_response[0]
|
|
195
|
+
context_response = session.ip(self.attribute.value)
|
|
196
|
+
riot_response = session.riot(self.attribute.value)
|
|
197
|
+
|
|
198
|
+
if riot_response and "trust_level" in riot_response:
|
|
199
|
+
if riot_response["trust_level"] == "1":
|
|
200
|
+
riot_response["trust_level"] = "1 - Reasonably Ignore"
|
|
201
|
+
if riot_response["trust_level"] == "2":
|
|
202
|
+
riot_response["trust_level"] = "2 - Commonly Seen"
|
|
203
|
+
|
|
204
|
+
if context_response and riot_response:
|
|
205
|
+
response = context_response.copy()
|
|
206
|
+
response.update(riot_response)
|
|
207
|
+
response.update(quick_response)
|
|
208
|
+
elif context_response:
|
|
209
|
+
response = context_response.copy()
|
|
210
|
+
response.update(quick_response)
|
|
211
|
+
elif riot_response:
|
|
212
|
+
response = riot_response.copy()
|
|
213
|
+
response.update(quick_response)
|
|
214
|
+
|
|
215
|
+
response["link"] = "https://viz.greynoise.io/ip/" + self.attribute.value
|
|
216
|
+
|
|
217
|
+
ip_address_attributes = []
|
|
218
|
+
for feature, mapping in self.ip_address_enrich_mapping.items():
|
|
219
|
+
logger.debug(f"Checking feature {feature}")
|
|
220
|
+
if response.get(feature):
|
|
221
|
+
if feature == "actor" and response[feature] == "unknown":
|
|
222
|
+
continue
|
|
223
|
+
attribute = {"value": response[feature]}
|
|
224
|
+
logger.debug(f"Adding Feature: {feature}, Attribute: {attribute}")
|
|
225
|
+
attribute.update(mapping)
|
|
226
|
+
ip_address_attributes.append(attribute)
|
|
227
|
+
if ip_address_attributes:
|
|
228
|
+
logger.debug("creating greynoise ip object")
|
|
229
|
+
gn_ip_object = MISPObject("greynoise-ip")
|
|
230
|
+
for attribute in ip_address_attributes:
|
|
231
|
+
logger.debug(f"adding attribute {attribute}")
|
|
232
|
+
gn_ip_object.add_attribute(**attribute)
|
|
233
|
+
logger.debug(f"attribute id: {self.attribute.uuid}")
|
|
234
|
+
gn_ip_object.add_reference(self.attribute.uuid, "describes")
|
|
235
|
+
self.misp_event.add_object(gn_ip_object)
|
|
236
|
+
else:
|
|
237
|
+
logger.info(f"Starting expansion enrichment for: {self.attribute.value} via GreyNoise Community API")
|
|
238
|
+
integration_name = "greynoise-community-misp-module-{}".format(moduleinfo["version"])
|
|
239
|
+
session = GreyNoise(api_key=api_key, integration_name=integration_name, offering="community")
|
|
240
|
+
community_response = session.ip(self.attribute.value)
|
|
241
|
+
|
|
242
|
+
if "noise" in community_response and community_response["noise"]:
|
|
243
|
+
community_response["actor"] = community_response["name"]
|
|
244
|
+
community_response.pop("name")
|
|
245
|
+
|
|
246
|
+
ip_address_attributes = []
|
|
247
|
+
for feature, mapping in self.ip_address_enrich_mapping.items():
|
|
248
|
+
if community_response.get(feature):
|
|
249
|
+
if feature == "actor" and community_response[feature] == "unknown":
|
|
250
|
+
continue
|
|
251
|
+
attribute = {"value": community_response[feature]}
|
|
252
|
+
attribute.update(mapping)
|
|
253
|
+
ip_address_attributes.append(attribute)
|
|
254
|
+
if ip_address_attributes:
|
|
255
|
+
ip_address_object = MISPObject("greynoise-ip")
|
|
256
|
+
for attribute in ip_address_attributes:
|
|
257
|
+
ip_address_object.add_attribute(**attribute)
|
|
258
|
+
ip_address_object.add_reference(self.attribute.uuid, "describes")
|
|
259
|
+
self.misp_event.add_object(ip_address_object)
|
|
260
|
+
|
|
261
|
+
def query_greynoise_vulnerability(self, api_key, api_type):
|
|
262
|
+
if api_type == "enterprise":
|
|
263
|
+
logger.info(f"Starting expansion enrichment for: {self.attribute.value} via GreyNoise ENT API")
|
|
264
|
+
integration_name = "greynoise-misp-module-{}".format(moduleinfo["version"])
|
|
265
|
+
session = GreyNoise(api_key=api_key, integration_name=integration_name)
|
|
266
|
+
querystring = f"last_seen:1w cve:{self.attribute.value}"
|
|
267
|
+
else:
|
|
268
|
+
misperrors["error"] = "Vulnerability Not Supported with Community API Key"
|
|
269
|
+
return misperrors
|
|
270
|
+
|
|
271
|
+
response = session.stats(querystring)
|
|
272
|
+
|
|
273
|
+
if "stats" in response:
|
|
274
|
+
response["details"] = (
|
|
275
|
+
"The IP count below reflects the number of IPs seen "
|
|
276
|
+
"by GreyNoise in the last 7 days scanning for this CVE."
|
|
277
|
+
)
|
|
278
|
+
response["id"] = self.attribute.value
|
|
279
|
+
classifications = response["stats"].get("classifications")
|
|
280
|
+
for item in classifications:
|
|
281
|
+
if item["classification"] == "benign":
|
|
282
|
+
value = item["count"]
|
|
283
|
+
response["benign"] = value
|
|
284
|
+
if item["classification"] == "unknown":
|
|
285
|
+
value = item["count"]
|
|
286
|
+
response["unknown"] = value
|
|
287
|
+
if item["classification"] == "malicious":
|
|
288
|
+
value = item["count"]
|
|
289
|
+
response["malicious"] = value
|
|
290
|
+
vulnerability_attributes = []
|
|
291
|
+
for feature, mapping in self.vulnerability_mapping.items():
|
|
292
|
+
if response.get(feature):
|
|
293
|
+
attribute = {"value": response[feature]}
|
|
294
|
+
attribute.update(mapping)
|
|
295
|
+
vulnerability_attributes.append(attribute)
|
|
296
|
+
if vulnerability_attributes:
|
|
297
|
+
vulnerability_object = MISPObject("greynoise-vuln-info")
|
|
298
|
+
for attribute in vulnerability_attributes:
|
|
299
|
+
vulnerability_object.add_attribute(**attribute)
|
|
300
|
+
vulnerability_object.add_reference(self.attribute.uuid, "describes")
|
|
301
|
+
self.misp_event.add_object(vulnerability_object)
|
|
302
|
+
|
|
303
|
+
def get_result(self):
|
|
304
|
+
event = json.loads(self.misp_event.to_json())
|
|
305
|
+
results = {key: event[key] for key in ("Attribute", "Object") if (key in event and event[key])}
|
|
306
|
+
return {"results": results}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def handler(q=False):
|
|
310
|
+
if q is False:
|
|
311
|
+
return False
|
|
312
|
+
request = json.loads(q)
|
|
313
|
+
if not request.get("config", {}).get("api_key"):
|
|
314
|
+
return {"error": "GreyNoise API Key required, but missing"}
|
|
315
|
+
if not request.get("config", {}).get("api_type"):
|
|
316
|
+
return {"error": "GreyNoise API type of enterprise or community required, but missing"}
|
|
317
|
+
if not request.get("attribute") or not check_input_attribute(request["attribute"]):
|
|
318
|
+
return {"error": f"{standard_error_message}, which should contain at least a type, a value and an uuid."}
|
|
319
|
+
attribute = request["attribute"]
|
|
320
|
+
if attribute["type"] not in mispattributes["input"]:
|
|
321
|
+
return {"error": "Unsupported attribute type."}
|
|
322
|
+
greynoise_parser = GreyNoiseParser(attribute)
|
|
323
|
+
|
|
324
|
+
if attribute["type"] in ["ip-dst", "ip-src"]:
|
|
325
|
+
try:
|
|
326
|
+
ipaddress.IPv4Address(attribute["value"])
|
|
327
|
+
if "persistent" in request:
|
|
328
|
+
greynoise_parser.query_greynoise_ip_hover(request["config"]["api_key"], request["config"]["api_type"])
|
|
329
|
+
else:
|
|
330
|
+
greynoise_parser.query_greynoise_ip_expansion(request["config"]["api_key"], request["config"]["api_type"])
|
|
331
|
+
except ValueError:
|
|
332
|
+
return {"error": "Not a valid IPv4 address"}
|
|
333
|
+
|
|
334
|
+
if attribute["type"] == "vulnerability":
|
|
335
|
+
greynoise_parser.query_greynoise_vulnerability(request["config"]["api_key"], request["config"]["api_type"])
|
|
336
|
+
|
|
337
|
+
return greynoise_parser.get_result()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def introspection():
|
|
341
|
+
return mispattributes
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def version():
|
|
345
|
+
moduleinfo["config"] = moduleconfig
|
|
346
|
+
return moduleinfo
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
misperrors = {'error': 'Error'}
|
|
5
|
+
mispattributes = {'input': ['md5'], 'output': ['text']}
|
|
6
|
+
moduleinfo = {
|
|
7
|
+
'version': '0.2',
|
|
8
|
+
'author': 'Alexandre Dulaunoy',
|
|
9
|
+
'description': 'A hover module to check hashes against hashdd.com including NSLR dataset.',
|
|
10
|
+
'module-type': ['hover'],
|
|
11
|
+
'name': 'Hashdd Lookup',
|
|
12
|
+
'logo': '',
|
|
13
|
+
'requirements': [],
|
|
14
|
+
'features': 'This module takes a hash attribute as input to check its known level, using the hashdd API. This information is then displayed.',
|
|
15
|
+
'references': ['https://hashdd.com/'],
|
|
16
|
+
'input': 'A hash MISP attribute (md5).',
|
|
17
|
+
'output': 'Text describing the known level of the hash in the hashdd databases.',
|
|
18
|
+
}
|
|
19
|
+
moduleconfig = []
|
|
20
|
+
hashddapi_url = 'https://api.hashdd.com/v1/knownlevel/nsrl/'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def handler(q=False):
|
|
24
|
+
if q is False:
|
|
25
|
+
return False
|
|
26
|
+
v = None
|
|
27
|
+
request = json.loads(q)
|
|
28
|
+
for input_type in mispattributes['input']:
|
|
29
|
+
if request.get(input_type):
|
|
30
|
+
v = request[input_type].upper()
|
|
31
|
+
break
|
|
32
|
+
if v is None:
|
|
33
|
+
misperrors['error'] = 'Hash value is missing.'
|
|
34
|
+
return misperrors
|
|
35
|
+
r = requests.get(hashddapi_url + v)
|
|
36
|
+
if r.status_code == 200:
|
|
37
|
+
state = json.loads(r.text)
|
|
38
|
+
summary = state['knownlevel'] if state and state['result'] == "SUCCESS" else state['message']
|
|
39
|
+
else:
|
|
40
|
+
misperrors['error'] = '{} API not accessible'.format(hashddapi_url)
|
|
41
|
+
return misperrors['error']
|
|
42
|
+
|
|
43
|
+
r = {'results': [{'types': mispattributes['output'], 'values': summary}]}
|
|
44
|
+
return r
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def introspection():
|
|
48
|
+
return mispattributes
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def version():
|
|
52
|
+
moduleinfo['config'] = moduleconfig
|
|
53
|
+
return moduleinfo
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from . import check_input_attribute, standard_error_message
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from pymisp import MISPEvent, MISPObject
|
|
6
|
+
|
|
7
|
+
misperrors = {'error': 'Error'}
|
|
8
|
+
mispattributes = {'input': ['md5', 'sha1', 'sha256'], 'format': 'misp_standard'}
|
|
9
|
+
moduleinfo = {
|
|
10
|
+
'version': '2',
|
|
11
|
+
'author': 'Alexandre Dulaunoy',
|
|
12
|
+
'description': 'An expansion module to query the CIRCL hashlookup services to find it if a hash is part of a known set such as NSRL.',
|
|
13
|
+
'module-type': ['expansion', 'hover'],
|
|
14
|
+
'name': 'CIRCL Hashlookup Lookup',
|
|
15
|
+
'logo': 'circl.png',
|
|
16
|
+
'requirements': [],
|
|
17
|
+
'features': 'The module takes file hashes as input such as a MD5 or SHA1.\n It queries the public CIRCL.lu hashlookup service and return all the hits if the hashes are known in an existing dataset. The module can be configured with a custom hashlookup url if required.\n The module can be used an hover module but also an expansion model to add related MISP objects.\n',
|
|
18
|
+
'references': ['https://www.circl.lu/services/hashlookup/'],
|
|
19
|
+
'input': 'File hashes (MD5, SHA1)',
|
|
20
|
+
'output': 'Object with the filename associated hashes if the hash is part of a known set.',
|
|
21
|
+
}
|
|
22
|
+
moduleconfig = ["custom_API"]
|
|
23
|
+
hashlookup_url = 'https://hashlookup.circl.lu/'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HashlookupParser():
|
|
27
|
+
def __init__(self, attribute, hashlookupresult, api_url):
|
|
28
|
+
self.attribute = attribute
|
|
29
|
+
self.hashlookupresult = hashlookupresult
|
|
30
|
+
self.api_url = api_url
|
|
31
|
+
self.misp_event = MISPEvent()
|
|
32
|
+
self.misp_event.add_attribute(**attribute)
|
|
33
|
+
self.references = defaultdict(list)
|
|
34
|
+
|
|
35
|
+
def get_result(self):
|
|
36
|
+
if self.references:
|
|
37
|
+
self.__build_references()
|
|
38
|
+
event = json.loads(self.misp_event.to_json())
|
|
39
|
+
results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
|
|
40
|
+
return {'results': results}
|
|
41
|
+
|
|
42
|
+
def parse_hashlookup_information(self):
|
|
43
|
+
hashlookup_object = MISPObject('hashlookup')
|
|
44
|
+
if 'source' in self.hashlookupresult:
|
|
45
|
+
hashlookup_object.add_attribute('source', **{'type': 'text', 'value': self.hashlookupresult['source']})
|
|
46
|
+
if 'KnownMalicious' in self.hashlookupresult:
|
|
47
|
+
hashlookup_object.add_attribute('KnownMalicious', **{'type': 'text', 'value': self.hashlookupresult['KnownMalicious']})
|
|
48
|
+
if 'MD5' in self.hashlookupresult:
|
|
49
|
+
hashlookup_object.add_attribute('MD5', **{'type': 'md5', 'value': self.hashlookupresult['MD5']})
|
|
50
|
+
# SHA-1 is the default value in hashlookup it must always be present
|
|
51
|
+
hashlookup_object.add_attribute('SHA-1', **{'type': 'sha1', 'value': self.hashlookupresult['SHA-1']})
|
|
52
|
+
if 'SHA-256' in self.hashlookupresult:
|
|
53
|
+
hashlookup_object.add_attribute('SHA-256', **{'type': 'sha256', 'value': self.hashlookupresult['SHA-256']})
|
|
54
|
+
if 'SSDEEP' in self.hashlookupresult:
|
|
55
|
+
hashlookup_object.add_attribute('SSDEEP', **{'type': 'ssdeep', 'value': self.hashlookupresult['SSDEEP']})
|
|
56
|
+
if 'TLSH' in self.hashlookupresult:
|
|
57
|
+
hashlookup_object.add_attribute('TLSH', **{'type': 'tlsh', 'value': self.hashlookupresult['TLSH']})
|
|
58
|
+
if 'FileName' in self.hashlookupresult:
|
|
59
|
+
hashlookup_object.add_attribute('FileName', **{'type': 'filename', 'value': self.hashlookupresult['FileName']})
|
|
60
|
+
if 'FileSize' in self.hashlookupresult:
|
|
61
|
+
hashlookup_object.add_attribute('FileSize', **{'type': 'size-in-bytes', 'value': self.hashlookupresult['FileSize']})
|
|
62
|
+
hashlookup_object.add_reference(self.attribute['uuid'], 'related-to')
|
|
63
|
+
self.misp_event.add_object(hashlookup_object)
|
|
64
|
+
|
|
65
|
+
def __build_references(self):
|
|
66
|
+
for object_uuid, references in self.references.items():
|
|
67
|
+
for misp_object in self.misp_event.objects:
|
|
68
|
+
if misp_object.uuid == object_uuid:
|
|
69
|
+
for reference in references:
|
|
70
|
+
misp_object.add_reference(**reference)
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
def check_url(url):
|
|
74
|
+
return "{}/".format(url) if not url.endswith('/') else url
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def handler(q=False):
|
|
78
|
+
if q is False:
|
|
79
|
+
return False
|
|
80
|
+
request = json.loads(q)
|
|
81
|
+
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
82
|
+
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
83
|
+
attribute = request['attribute']
|
|
84
|
+
if attribute.get('type') == 'md5':
|
|
85
|
+
pass
|
|
86
|
+
elif attribute.get('type') == 'sha1':
|
|
87
|
+
pass
|
|
88
|
+
elif attribute.get('type') == 'sha256':
|
|
89
|
+
pass
|
|
90
|
+
else:
|
|
91
|
+
misperrors['error'] = 'md5 or sha1 or sha256 is missing.'
|
|
92
|
+
return misperrors
|
|
93
|
+
api_url = check_url(request['config']['custom_API']) if request['config'].get('custom_API') else hashlookup_url
|
|
94
|
+
r = requests.get("{}/lookup/{}/{}".format(api_url, attribute.get('type'), attribute['value']))
|
|
95
|
+
if r.status_code == 200:
|
|
96
|
+
hashlookupresult = r.json()
|
|
97
|
+
if not hashlookupresult:
|
|
98
|
+
misperrors['error'] = 'Empty result'
|
|
99
|
+
return misperrors
|
|
100
|
+
elif r.status_code == 404:
|
|
101
|
+
misperrors['error'] = 'Non existing hash'
|
|
102
|
+
return misperrors
|
|
103
|
+
else:
|
|
104
|
+
misperrors['error'] = 'API not accessible'
|
|
105
|
+
return misperrors
|
|
106
|
+
parser = HashlookupParser(attribute, hashlookupresult, api_url)
|
|
107
|
+
parser.parse_hashlookup_information()
|
|
108
|
+
result = parser.get_result()
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def introspection():
|
|
113
|
+
return mispattributes
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def version():
|
|
117
|
+
moduleinfo['config'] = moduleconfig
|
|
118
|
+
return moduleinfo
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
misperrors = {'error': 'Error'}
|
|
6
|
+
mispattributes = {'input': ['email-dst', 'email-src'], 'output': ['text']}
|
|
7
|
+
moduleinfo = {
|
|
8
|
+
'version': '0.2',
|
|
9
|
+
'author': 'Corsin Camichel, Aurélien Schwab',
|
|
10
|
+
'description': 'Module to access haveibeenpwned.com API.',
|
|
11
|
+
'module-type': ['hover'],
|
|
12
|
+
'name': 'Have I Been Pwned Lookup',
|
|
13
|
+
'logo': 'hibp.png',
|
|
14
|
+
'requirements': [],
|
|
15
|
+
'features': 'The module takes an email address as input and queries haveibeenpwned.com API to find additional information about it. This additional information actually tells if any account using the email address has already been compromised in a data breach.',
|
|
16
|
+
'references': ['https://haveibeenpwned.com/'],
|
|
17
|
+
'input': 'An email address',
|
|
18
|
+
'output': 'Additional information about the email address.',
|
|
19
|
+
}
|
|
20
|
+
moduleconfig = ['api_key']
|
|
21
|
+
|
|
22
|
+
haveibeenpwned_api_url = 'https://haveibeenpwned.com/api/v3/breachedaccount/'
|
|
23
|
+
API_KEY = "" # details at https://www.troyhunt.com/authentication-and-the-have-i-been-pwned-api/
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def handler(q=False):
|
|
27
|
+
if q is False:
|
|
28
|
+
return False
|
|
29
|
+
request = json.loads(q)
|
|
30
|
+
for input_type in mispattributes['input']:
|
|
31
|
+
if input_type in request:
|
|
32
|
+
email = request[input_type]
|
|
33
|
+
break
|
|
34
|
+
else:
|
|
35
|
+
misperrors['error'] = "Unsupported attributes type"
|
|
36
|
+
return misperrors
|
|
37
|
+
|
|
38
|
+
if request.get('config') is None or request['config'].get('api_key') is None:
|
|
39
|
+
misperrors['error'] = 'Have I Been Pwned authentication is incomplete (no API key)'
|
|
40
|
+
return misperrors
|
|
41
|
+
else:
|
|
42
|
+
API_KEY = request['config'].get('api_key')
|
|
43
|
+
|
|
44
|
+
r = requests.get(haveibeenpwned_api_url + email, headers={'hibp-api-key': API_KEY})
|
|
45
|
+
if r.status_code == 200:
|
|
46
|
+
breaches = json.loads(r.text)
|
|
47
|
+
if breaches:
|
|
48
|
+
return {'results': [{'types': mispattributes['output'], 'values': breaches}]}
|
|
49
|
+
elif r.status_code == 404:
|
|
50
|
+
return {'results': [{'types': mispattributes['output'], 'values': 'OK (Not Found)'}]}
|
|
51
|
+
else:
|
|
52
|
+
misperrors['error'] = f'haveibeenpwned.com API not accessible (HTTP {str(r.status_code)})'
|
|
53
|
+
return misperrors['error']
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def introspection():
|
|
57
|
+
return mispattributes
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def version():
|
|
61
|
+
moduleinfo['config'] = moduleconfig
|
|
62
|
+
return moduleinfo
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from markdownify import markdownify
|
|
4
|
+
from bs4 import BeautifulSoup
|
|
5
|
+
|
|
6
|
+
misperrors = {'error': 'Error'}
|
|
7
|
+
mispattributes = {'input': ['url'], 'output': ['text']}
|
|
8
|
+
moduleinfo = {
|
|
9
|
+
'version': '0.1',
|
|
10
|
+
'author': 'Sami Mokaddem',
|
|
11
|
+
'description': 'Expansion module to fetch the html content from an url and convert it into markdown.',
|
|
12
|
+
'module-type': ['expansion'],
|
|
13
|
+
'name': 'HTML to Markdown',
|
|
14
|
+
'logo': '',
|
|
15
|
+
'requirements': ['The markdownify python library'],
|
|
16
|
+
'features': 'The module take an URL as input and the HTML content is fetched from it. This content is then converted into markdown that is returned as text.',
|
|
17
|
+
'references': [],
|
|
18
|
+
'input': 'URL attribute.',
|
|
19
|
+
'output': 'Markdown content converted from the HTML fetched from the url.',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def fetchHTML(url):
|
|
24
|
+
r = requests.get(url)
|
|
25
|
+
return r.text
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def stripUselessTags(html):
|
|
29
|
+
soup = BeautifulSoup(html, 'html.parser')
|
|
30
|
+
toRemove = ['script', 'head', 'header', 'footer', 'meta', 'link']
|
|
31
|
+
for tag in soup.find_all(toRemove):
|
|
32
|
+
tag.decompose()
|
|
33
|
+
return str(soup)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def convertHTML(html):
|
|
37
|
+
toStrip = ['a', 'img']
|
|
38
|
+
return markdownify(html, heading_style='ATX', strip=toStrip)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def handler(q=False):
|
|
42
|
+
if q is False:
|
|
43
|
+
return False
|
|
44
|
+
request = json.loads(q)
|
|
45
|
+
if request.get('url'):
|
|
46
|
+
url = request['url']
|
|
47
|
+
else:
|
|
48
|
+
return False
|
|
49
|
+
html = fetchHTML(url)
|
|
50
|
+
html = stripUselessTags(html)
|
|
51
|
+
markdown = convertHTML(html)
|
|
52
|
+
|
|
53
|
+
r = {'results': [{'types': mispattributes['output'],
|
|
54
|
+
'values':[str(markdown)]}]}
|
|
55
|
+
return r
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def introspection():
|
|
59
|
+
return mispattributes
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def version():
|
|
63
|
+
return moduleinfo
|