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.
Files changed (194) hide show
  1. misp_modules/__init__.py +379 -0
  2. misp_modules/helpers/__init__.py +1 -0
  3. misp_modules/helpers/cache.py +84 -0
  4. misp_modules/lib/__init__.py +4 -0
  5. misp_modules/lib/_vmray/__init__.py +0 -0
  6. misp_modules/lib/_vmray/parser.py +1417 -0
  7. misp_modules/lib/_vmray/rest_api.py +148 -0
  8. misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
  9. misp_modules/lib/cof2misp/__init__.py +0 -0
  10. misp_modules/lib/cof2misp/cof.py +165 -0
  11. misp_modules/lib/joe_mapping.py +114 -0
  12. misp_modules/lib/joe_parser.py +573 -0
  13. misp_modules/lib/lastline_api.py +841 -0
  14. misp_modules/lib/qintel_helper.py +263 -0
  15. misp_modules/lib/stix2misp.py +2080 -0
  16. misp_modules/lib/stix2misp_mapping.py +460 -0
  17. misp_modules/lib/synonymsToTagNames.json +1 -0
  18. misp_modules/lib/vt_graph_parser/__init__.py +8 -0
  19. misp_modules/lib/vt_graph_parser/errors.py +20 -0
  20. misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
  21. misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
  22. misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
  23. misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
  24. misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
  25. misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
  26. misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
  27. misp_modules/modules/__init__.py +4 -0
  28. misp_modules/modules/action_mod/__init__.py +1 -0
  29. misp_modules/modules/action_mod/_utils/__init__.py +1 -0
  30. misp_modules/modules/action_mod/_utils/utils.py +70 -0
  31. misp_modules/modules/action_mod/mattermost.py +113 -0
  32. misp_modules/modules/action_mod/slack.py +96 -0
  33. misp_modules/modules/action_mod/testaction.py +68 -0
  34. misp_modules/modules/expansion/__init__.py +36 -0
  35. misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
  36. misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
  37. misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
  38. misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
  39. misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
  40. misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
  41. misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
  42. misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
  43. misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
  44. misp_modules/modules/expansion/abuseipdb.py +146 -0
  45. misp_modules/modules/expansion/apiosintds.py +365 -0
  46. misp_modules/modules/expansion/apivoid.py +133 -0
  47. misp_modules/modules/expansion/assemblyline_query.py +179 -0
  48. misp_modules/modules/expansion/assemblyline_submit.py +100 -0
  49. misp_modules/modules/expansion/backscatter_io.py +84 -0
  50. misp_modules/modules/expansion/btc_scam_check.py +54 -0
  51. misp_modules/modules/expansion/btc_steroids.py +238 -0
  52. misp_modules/modules/expansion/censys_enrich.py +287 -0
  53. misp_modules/modules/expansion/circl_passivedns.py +85 -0
  54. misp_modules/modules/expansion/circl_passivessl.py +113 -0
  55. misp_modules/modules/expansion/clamav.py +129 -0
  56. misp_modules/modules/expansion/cluster25_expand.py +239 -0
  57. misp_modules/modules/expansion/countrycode.py +69 -0
  58. misp_modules/modules/expansion/cpe.py +140 -0
  59. misp_modules/modules/expansion/crowdsec.py +235 -0
  60. misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
  61. misp_modules/modules/expansion/cuckoo_submit.py +161 -0
  62. misp_modules/modules/expansion/cve.py +57 -0
  63. misp_modules/modules/expansion/cve_advanced.py +185 -0
  64. misp_modules/modules/expansion/cytomic_orion.py +196 -0
  65. misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
  66. misp_modules/modules/expansion/dns.py +71 -0
  67. misp_modules/modules/expansion/docx_enrich.py +71 -0
  68. misp_modules/modules/expansion/domaintools.py +290 -0
  69. misp_modules/modules/expansion/eql.py +91 -0
  70. misp_modules/modules/expansion/eupi.py +86 -0
  71. misp_modules/modules/expansion/extract_url_components.py +83 -0
  72. misp_modules/modules/expansion/farsight_passivedns.py +243 -0
  73. misp_modules/modules/expansion/geoip_asn.py +75 -0
  74. misp_modules/modules/expansion/geoip_city.py +75 -0
  75. misp_modules/modules/expansion/geoip_country.py +73 -0
  76. misp_modules/modules/expansion/google_safe_browsing.py +86 -0
  77. misp_modules/modules/expansion/google_search.py +57 -0
  78. misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
  79. misp_modules/modules/expansion/greynoise.py +346 -0
  80. misp_modules/modules/expansion/hashdd.py +53 -0
  81. misp_modules/modules/expansion/hashlookup.py +118 -0
  82. misp_modules/modules/expansion/hibp.py +62 -0
  83. misp_modules/modules/expansion/html_to_markdown.py +63 -0
  84. misp_modules/modules/expansion/hyasinsight.py +881 -0
  85. misp_modules/modules/expansion/intel471.py +73 -0
  86. misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
  87. misp_modules/modules/expansion/ip2locationio.py +90 -0
  88. misp_modules/modules/expansion/ipasn.py +70 -0
  89. misp_modules/modules/expansion/ipinfo.py +112 -0
  90. misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
  91. misp_modules/modules/expansion/iprep.py +98 -0
  92. misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
  93. misp_modules/modules/expansion/joesandbox_query.py +91 -0
  94. misp_modules/modules/expansion/joesandbox_submit.py +147 -0
  95. misp_modules/modules/expansion/lastline_query.py +148 -0
  96. misp_modules/modules/expansion/lastline_submit.py +180 -0
  97. misp_modules/modules/expansion/macaddress_io.py +130 -0
  98. misp_modules/modules/expansion/macvendors.py +55 -0
  99. misp_modules/modules/expansion/malshare_upload.py +107 -0
  100. misp_modules/modules/expansion/malwarebazaar.py +67 -0
  101. misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
  102. misp_modules/modules/expansion/mmdb_lookup.py +138 -0
  103. misp_modules/modules/expansion/module.py.skeleton +44 -0
  104. misp_modules/modules/expansion/mwdb.py +152 -0
  105. misp_modules/modules/expansion/ocr_enrich.py +74 -0
  106. misp_modules/modules/expansion/ods_enrich.py +71 -0
  107. misp_modules/modules/expansion/odt_enrich.py +61 -0
  108. misp_modules/modules/expansion/onyphe.py +243 -0
  109. misp_modules/modules/expansion/onyphe_full.py +387 -0
  110. misp_modules/modules/expansion/otx.py +167 -0
  111. misp_modules/modules/expansion/passive_ssh.py +150 -0
  112. misp_modules/modules/expansion/passivetotal.py +358 -0
  113. misp_modules/modules/expansion/pdf_enrich.py +58 -0
  114. misp_modules/modules/expansion/pptx_enrich.py +65 -0
  115. misp_modules/modules/expansion/qintel_qsentry.py +229 -0
  116. misp_modules/modules/expansion/qrcode.py +99 -0
  117. misp_modules/modules/expansion/ransomcoindb.py +82 -0
  118. misp_modules/modules/expansion/rbl.py +126 -0
  119. misp_modules/modules/expansion/recordedfuture.py +544 -0
  120. misp_modules/modules/expansion/reversedns.py +77 -0
  121. misp_modules/modules/expansion/securitytrails.py +571 -0
  122. misp_modules/modules/expansion/shodan.py +244 -0
  123. misp_modules/modules/expansion/sigma_queries.py +61 -0
  124. misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
  125. misp_modules/modules/expansion/sigmf_expand.py +303 -0
  126. misp_modules/modules/expansion/socialscan.py +108 -0
  127. misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
  128. misp_modules/modules/expansion/sourcecache.py +57 -0
  129. misp_modules/modules/expansion/stairwell.py +156 -0
  130. misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
  131. misp_modules/modules/expansion/threatcrowd.py +170 -0
  132. misp_modules/modules/expansion/threatfox.py +75 -0
  133. misp_modules/modules/expansion/threatminer.py +152 -0
  134. misp_modules/modules/expansion/triage_submit.py +119 -0
  135. misp_modules/modules/expansion/trustar_enrich.py +235 -0
  136. misp_modules/modules/expansion/urlhaus.py +164 -0
  137. misp_modules/modules/expansion/urlscan.py +269 -0
  138. misp_modules/modules/expansion/variotdbs.py +133 -0
  139. misp_modules/modules/expansion/virustotal.py +311 -0
  140. misp_modules/modules/expansion/virustotal_public.py +272 -0
  141. misp_modules/modules/expansion/virustotal_upload.py +91 -0
  142. misp_modules/modules/expansion/vmray_submit.py +161 -0
  143. misp_modules/modules/expansion/vmware_nsx.py +628 -0
  144. misp_modules/modules/expansion/vulndb.py +292 -0
  145. misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
  146. misp_modules/modules/expansion/vulners.py +81 -0
  147. misp_modules/modules/expansion/vysion.py +221 -0
  148. misp_modules/modules/expansion/whois.py +72 -0
  149. misp_modules/modules/expansion/whoisfreaks.py +232 -0
  150. misp_modules/modules/expansion/wiki.py +58 -0
  151. misp_modules/modules/expansion/xforceexchange.py +189 -0
  152. misp_modules/modules/expansion/xlsx_enrich.py +63 -0
  153. misp_modules/modules/expansion/yara_query.py +70 -0
  154. misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
  155. misp_modules/modules/expansion/yeti.py +196 -0
  156. misp_modules/modules/export_mod/__init__.py +3 -0
  157. misp_modules/modules/export_mod/cef_export.py +93 -0
  158. misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
  159. misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
  160. misp_modules/modules/export_mod/goamlexport.py +255 -0
  161. misp_modules/modules/export_mod/liteexport.py +97 -0
  162. misp_modules/modules/export_mod/mass_eql_export.py +106 -0
  163. misp_modules/modules/export_mod/nexthinkexport.py +131 -0
  164. misp_modules/modules/export_mod/osqueryexport.py +125 -0
  165. misp_modules/modules/export_mod/pdfexport.py +107 -0
  166. misp_modules/modules/export_mod/testexport.py +73 -0
  167. misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
  168. misp_modules/modules/export_mod/threat_connect_export.py +125 -0
  169. misp_modules/modules/export_mod/virustotal_collections.py +141 -0
  170. misp_modules/modules/export_mod/vt_graph.py +120 -0
  171. misp_modules/modules/export_mod/yara_export.py +290 -0
  172. misp_modules/modules/import_mod/__init__.py +22 -0
  173. misp_modules/modules/import_mod/cof2misp.py +264 -0
  174. misp_modules/modules/import_mod/csvimport.py +319 -0
  175. misp_modules/modules/import_mod/cuckooimport.py +749 -0
  176. misp_modules/modules/import_mod/email_import.py +294 -0
  177. misp_modules/modules/import_mod/goamlimport.py +188 -0
  178. misp_modules/modules/import_mod/import_blueprint.py +93 -0
  179. misp_modules/modules/import_mod/joe_import.py +74 -0
  180. misp_modules/modules/import_mod/lastline_import.py +160 -0
  181. misp_modules/modules/import_mod/mispjson.py +73 -0
  182. misp_modules/modules/import_mod/ocr.py +122 -0
  183. misp_modules/modules/import_mod/openiocimport.py +99 -0
  184. misp_modules/modules/import_mod/taxii21.py +383 -0
  185. misp_modules/modules/import_mod/testimport.py +73 -0
  186. misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
  187. misp_modules/modules/import_mod/url_import.py +97 -0
  188. misp_modules/modules/import_mod/vmray_import.py +96 -0
  189. misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
  190. misp_modules-2.4.196.dist-info/LICENSE +661 -0
  191. misp_modules-2.4.196.dist-info/METADATA +281 -0
  192. misp_modules-2.4.196.dist-info/RECORD +194 -0
  193. misp_modules-2.4.196.dist-info/WHEEL +4 -0
  194. 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