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,272 @@
1
+ import json
2
+ import logging
3
+ import vt
4
+ from . import check_input_attribute, standard_error_message
5
+ from urllib.parse import urlparse
6
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
7
+
8
+ misperrors = {'error': 'Error'}
9
+ mispattributes = {'input': ['hostname', 'domain', "ip-src", "ip-dst", "md5", "sha1", "sha256", "url"],
10
+ 'format': 'misp_standard'}
11
+ moduleinfo = {
12
+ 'version': '2',
13
+ 'author': 'Christian Studer',
14
+ 'description': 'Enrich observables with the VirusTotal v3 public API',
15
+ 'module-type': ['expansion', 'hover'],
16
+ 'name': 'VirusTotal Public API Lookup',
17
+ 'logo': 'virustotal.png',
18
+ 'requirements': ['An access to the VirusTotal API (apikey)'],
19
+ 'features': 'New format of modules able to return attributes and objects.\n\nA module to take a MISP attribute as input and query the VirusTotal API to get additional data about it.\n\nCompared to the [more advanced VirusTotal expansion module](https://github.com/MISP/misp-modules/blob/main/misp_modules/modules/expansion/virustotal.py), this module is made for VirusTotal users who have a low request rate limit.\n\nThus, it only queries the API once and returns the results that is parsed into MISP attributes and objects.',
20
+ 'references': ['https://www.virustotal.com', 'https://docs.virustotal.com/reference/overview'],
21
+ 'input': 'A domain, hostname, ip, url or hash (md5, sha1, sha256 or sha512) attribute.',
22
+ 'output': 'MISP attributes and objects resulting from the parsing of the VirusTotal report concerning the input attribute.',
23
+ }
24
+
25
+ moduleconfig = ['apikey', 'proxy_host', 'proxy_port', 'proxy_username', 'proxy_password']
26
+
27
+ LOGGER = logging.getLogger('virus_total_public')
28
+ LOGGER.setLevel(logging.INFO)
29
+
30
+
31
+ DEFAULT_RESULTS_LIMIT = 10
32
+
33
+
34
+ class VirusTotalParser:
35
+ def __init__(self, client: vt.Client, limit: int) -> None:
36
+ self.client = client
37
+ self.limit = limit or DEFAULT_RESULTS_LIMIT
38
+ self.misp_event = MISPEvent()
39
+ self.attribute = MISPAttribute()
40
+ self.parsed_objects = {}
41
+ self.input_types_mapping = {'ip-src': self.parse_ip, 'ip-dst': self.parse_ip,
42
+ 'domain': self.parse_domain, 'hostname': self.parse_domain,
43
+ 'md5': self.parse_hash, 'sha1': self.parse_hash,
44
+ 'sha256': self.parse_hash, 'url': self.parse_url}
45
+ self.proxies = None
46
+
47
+ @staticmethod
48
+ def get_total_analysis(analysis: dict, known_distributors: dict = None) -> int:
49
+ if not analysis:
50
+ return 0
51
+ count = sum([analysis['undetected'], analysis['suspicious'], analysis['harmless']])
52
+ return count if known_distributors else count + analysis['malicious']
53
+
54
+ def query_api(self, attribute: dict) -> None:
55
+ self.attribute.from_dict(**attribute)
56
+ self.input_types_mapping[self.attribute.type](self.attribute.value)
57
+
58
+ def get_result(self) -> dict:
59
+ event = json.loads(self.misp_event.to_json())
60
+ results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
61
+ return {'results': results}
62
+
63
+ def add_vt_report(self, report: vt.Object) -> str:
64
+ analysis = report.get('last_analysis_stats')
65
+ total = self.get_total_analysis(analysis, report.get('known_distributors'))
66
+ permalink = f'https://www.virustotal.com/gui/{report.type}/{report.id}'
67
+
68
+ vt_object = MISPObject('virustotal-report')
69
+ vt_object.add_attribute('permalink', type='link', value=permalink)
70
+ detection_ratio = f"{analysis['malicious']}/{total}" if analysis else '-/-'
71
+ vt_object.add_attribute('detection-ratio', type='text', value=detection_ratio, disable_correlation=True)
72
+ self.misp_event.add_object(**vt_object)
73
+ return vt_object.uuid
74
+
75
+ def create_misp_object(self, report: vt.Object) -> MISPObject:
76
+ misp_object = None
77
+ vt_uuid = self.add_vt_report(report)
78
+ if report.type == 'file':
79
+ misp_object = MISPObject('file')
80
+ for hash_type in ('md5', 'sha1', 'sha256', 'tlsh',
81
+ 'vhash', 'ssdeep', 'imphash'):
82
+ misp_object.add_attribute(**{'type': hash_type,
83
+ 'object_relation': hash_type,
84
+ 'value': report.get(hash_type)})
85
+ elif report.type == 'domain':
86
+ misp_object = MISPObject('domain-ip')
87
+ misp_object.add_attribute('domain', type='domain', value=report.id)
88
+ elif report.type == 'ip_address':
89
+ misp_object = MISPObject('domain-ip')
90
+ misp_object.add_attribute('ip', type='ip-dst', value=report.id)
91
+ elif report.type == 'url':
92
+ misp_object = MISPObject('url')
93
+ misp_object.add_attribute('url', type='url', value=report.id)
94
+ misp_object.add_reference(vt_uuid, 'analyzed-with')
95
+ return misp_object
96
+
97
+ ################################################################################
98
+ #### Main parsing functions #### # noqa
99
+ ################################################################################
100
+
101
+ def parse_domain(self, domain: str) -> str:
102
+ domain_report = self.client.get_object(f'/domains/{domain}')
103
+
104
+ # DOMAIN
105
+ domain_object = self.create_misp_object(domain_report)
106
+
107
+ # WHOIS
108
+ if domain_report.whois:
109
+ whois_object = MISPObject('whois')
110
+ whois_object.add_attribute('text', type='text', value=domain_report.whois)
111
+ self.misp_event.add_object(**whois_object)
112
+
113
+ # SIBLINGS AND SUBDOMAINS
114
+ for relationship_name, misp_name in [('siblings', 'sibling-of'), ('subdomains', 'subdomain')]:
115
+ rel_iterator = self.client.iterator(f'/domains/{domain_report.id}/{relationship_name}', limit=self.limit)
116
+ for item in rel_iterator:
117
+ attr = MISPAttribute()
118
+ attr.from_dict(**dict(type='domain', value=item.id))
119
+ self.misp_event.add_attribute(**attr)
120
+ domain_object.add_reference(attr.uuid, misp_name)
121
+
122
+ # RESOLUTIONS
123
+ resolutions_iterator = self.client.iterator(f'/domains/{domain_report.id}/resolutions', limit=self.limit)
124
+ for resolution in resolutions_iterator:
125
+ domain_object.add_attribute('ip', type='ip-dst', value=resolution.ip_address)
126
+
127
+ # COMMUNICATING AND REFERRER FILES
128
+ for relationship_name, misp_name in [
129
+ ('communicating_files', 'communicates-with'),
130
+ ('referrer_files', 'referring')
131
+ ]:
132
+ files_iterator = self.client.iterator(f'/domains/{domain_report.id}/{relationship_name}', limit=self.limit)
133
+ for file in files_iterator:
134
+ file_object = self.create_misp_object(file)
135
+ file_object.add_reference(domain_object.uuid, misp_name)
136
+ self.misp_event.add_object(**file_object)
137
+
138
+ self.misp_event.add_object(**domain_object)
139
+ return domain_object.uuid
140
+
141
+ def parse_hash(self, file_hash: str) -> str:
142
+ file_report = self.client.get_object(f'/files/{file_hash}')
143
+ file_object = self.create_misp_object(file_report)
144
+
145
+ # COMMUNICATING, DOWNLOADED AND REFERRER FILES
146
+ for relationship_name, misp_name in [
147
+ ('contacted_urls', 'communicates-with'),
148
+ ('contacted_domains', 'communicates-with'),
149
+ ('contacted_ips', 'communicates-with')
150
+ ]:
151
+ related_files_iterator = self.client.iterator(f'/files/{file_report.id}/{relationship_name}', limit=self.limit)
152
+ for related_file in related_files_iterator:
153
+ related_file_object = self.create_misp_object(related_file)
154
+ related_file_object.add_reference(file_object.uuid, misp_name)
155
+ self.misp_event.add_object(**related_file_object)
156
+
157
+ self.misp_event.add_object(**file_object)
158
+ return file_object.uuid
159
+
160
+ def parse_ip(self, ip: str) -> str:
161
+ ip_report = self.client.get_object(f'/ip_addresses/{ip}')
162
+
163
+ # IP
164
+ ip_object = self.create_misp_object(ip_report)
165
+
166
+ # ASN
167
+ asn_object = MISPObject('asn')
168
+ asn_object.add_attribute('asn', type='AS', value=ip_report.asn)
169
+ asn_object.add_attribute('subnet-announced', type='ip-src', value=ip_report.network)
170
+ asn_object.add_attribute('country', type='text', value=ip_report.country)
171
+ self.misp_event.add_object(**asn_object)
172
+
173
+ # RESOLUTIONS
174
+ resolutions_iterator = self.client.iterator(f'/ip_addresses/{ip_report.id}/resolutions', limit=self.limit)
175
+ for resolution in resolutions_iterator:
176
+ ip_object.add_attribute('domain', type='domain', value=resolution.host_name)
177
+
178
+ self.misp_event.add_object(**ip_object)
179
+ return ip_object.uuid
180
+
181
+ def parse_url(self, url: str) -> str:
182
+ url_id = vt.url_id(url)
183
+ url_report = self.client.get_object(f'/urls/{url_id}')
184
+ url_object = self.create_misp_object(url_report)
185
+ self.misp_event.add_object(**url_object)
186
+ return url_object.uuid
187
+
188
+
189
+ def get_proxy_settings(config: dict) -> dict:
190
+ """Returns proxy settings in the requests format.
191
+ If no proxy settings are set, return None."""
192
+ proxies = None
193
+ host = config.get('proxy_host')
194
+ port = config.get('proxy_port')
195
+ username = config.get('proxy_username')
196
+ password = config.get('proxy_password')
197
+
198
+ if host:
199
+ if not port:
200
+ misperrors['error'] = 'The virustotal_proxy_host config is set, ' \
201
+ 'please also set the virustotal_proxy_port.'
202
+ raise KeyError
203
+ parsed = urlparse(host)
204
+ if 'http' in parsed.scheme:
205
+ scheme = 'http'
206
+ else:
207
+ scheme = parsed.scheme
208
+ netloc = parsed.netloc
209
+ host = f'{netloc}:{port}'
210
+
211
+ if username:
212
+ if not password:
213
+ misperrors['error'] = 'The virustotal_proxy_username config is set, ' \
214
+ 'please also set the virustotal_proxy_password.'
215
+ raise KeyError
216
+ auth = f'{username}:{password}'
217
+ host = auth + '@' + host
218
+
219
+ proxies = {
220
+ 'http': f'{scheme}://{host}',
221
+ 'https': f'{scheme}://{host}'
222
+ }
223
+ return proxies
224
+
225
+
226
+ def parse_error(status_code: int) -> str:
227
+ status_mapping = {204: 'VirusTotal request rate limit exceeded.',
228
+ 400: 'Incorrect request, please check the arguments.',
229
+ 403: 'You don\'t have enough privileges to make the request.'}
230
+ if status_code in status_mapping:
231
+ return status_mapping[status_code]
232
+ return "VirusTotal may not be accessible."
233
+
234
+
235
+ def handler(q=False):
236
+ if q is False:
237
+ return False
238
+ request = json.loads(q)
239
+ if not request.get('config') or not request['config'].get('apikey'):
240
+ misperrors['error'] = 'A VirusTotal api key is required for this module.'
241
+ return misperrors
242
+ if not request.get('attribute') or not check_input_attribute(request['attribute']):
243
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
244
+ if request['attribute']['type'] not in mispattributes['input']:
245
+ return {'error': 'Unsupported attribute type.'}
246
+
247
+ event_limit = request['config'].get('event_limit')
248
+ attribute = request['attribute']
249
+ proxy_settings = get_proxy_settings(request.get('config'))
250
+
251
+ try:
252
+ client = vt.Client(request['config']['apikey'],
253
+ headers={
254
+ 'x-tool': 'MISPModuleVirusTotalPublicExpansion',
255
+ },
256
+ proxy=proxy_settings['http'] if proxy_settings else None)
257
+ parser = VirusTotalParser(client, int(event_limit) if event_limit else None)
258
+ parser.query_api(attribute)
259
+ except vt.APIError as ex:
260
+ misperrors['error'] = ex.message
261
+ return misperrors
262
+
263
+ return parser.get_result()
264
+
265
+
266
+ def introspection():
267
+ return mispattributes
268
+
269
+
270
+ def version():
271
+ moduleinfo['config'] = moduleconfig
272
+ return moduleinfo
@@ -0,0 +1,91 @@
1
+ import json
2
+ import sys
3
+ import base64
4
+ import io
5
+ import zipfile
6
+ import requests
7
+ import hashlib
8
+
9
+ misperrors = {'error': 'Error'}
10
+ mispattributes = {'input': ['attachment', 'malware-sample'], 'output': ['link']}
11
+ moduleinfo = {
12
+ 'version': '1',
13
+ 'author': 'Karen Yousefi',
14
+ 'description': 'Module to push malware samples to VirusTotal',
15
+ 'module-type': ['expansion'],
16
+ 'name': 'VirusTotal Upload',
17
+ 'requirements': ['requests library'],
18
+ 'logo': 'virustotal.png',
19
+ }
20
+
21
+ moduleconfig = ['virustotal_apikey']
22
+
23
+
24
+ def handler(q=False):
25
+ if q is False:
26
+ return False
27
+ request = json.loads(q)
28
+
29
+ try:
30
+ data = request.get("data")
31
+ if 'malware-sample' in request:
32
+ sample_filename = request.get("malware-sample").split("|", 1)[0]
33
+ data = base64.b64decode(data)
34
+ fl = io.BytesIO(data)
35
+ zf = zipfile.ZipFile(fl)
36
+ sample_hashname = zf.namelist()[0]
37
+ data = zf.read(sample_hashname, b"infected")
38
+ zf.close()
39
+ elif 'attachment' in request:
40
+ sample_filename = request.get("attachment")
41
+ data = base64.b64decode(data)
42
+ else:
43
+ misperrors['error'] = "No malware sample or attachment supplied"
44
+ return misperrors
45
+ except Exception:
46
+ misperrors['error'] = "Unable to process submitted sample data"
47
+ return misperrors
48
+
49
+ if request["config"].get("virustotal_apikey") is None:
50
+ misperrors["error"] = "Missing VirusTotal API key"
51
+ return misperrors
52
+
53
+ virustotal_apikey = request["config"].get("virustotal_apikey")
54
+
55
+ try:
56
+ url = "https://www.virustotal.com/api/v3/files"
57
+ headers = {
58
+ "accept": "application/json",
59
+ "x-apikey": virustotal_apikey,
60
+ }
61
+ files = {"file": (sample_filename, data)}
62
+ response = requests.post(url, headers=headers, files=files)
63
+ response.raise_for_status()
64
+
65
+ # Calculate SHA256 of the file
66
+ sha256 = hashlib.sha256(data).hexdigest()
67
+
68
+ virustotal_link = f"https://www.virustotal.com/gui/file/{sha256}"
69
+ except Exception as e:
70
+ misperrors['error'] = f"Unable to send sample to VirusTotal: {str(e)}"
71
+ return misperrors
72
+
73
+ r = {
74
+ 'results': [
75
+ {
76
+ 'types': 'link',
77
+ 'values': virustotal_link,
78
+ 'comment': 'Link to VirusTotal analysis',
79
+ }
80
+ ]
81
+ }
82
+ return r
83
+
84
+
85
+ def introspection():
86
+ return mispattributes
87
+
88
+
89
+ def version():
90
+ moduleinfo['config'] = moduleconfig
91
+ return moduleinfo
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env python3
2
+
3
+ '''
4
+ Submit sample to VMRay.
5
+
6
+ Requires "vmray_rest_api"
7
+
8
+ The expansion module vmray_submit and import module vmray_import are a two step
9
+ process to import data from VMRay.
10
+ You can automate this by setting the PyMISP example script 'vmray_automation'
11
+ as a cron job
12
+
13
+ '''
14
+
15
+ import json
16
+ import base64
17
+ from distutils.util import strtobool
18
+
19
+ import io
20
+ import zipfile
21
+
22
+ from _vmray.rest_api import VMRayRESTAPI
23
+
24
+ misperrors = {'error': 'Error'}
25
+ mispattributes = {'input': ['attachment', 'malware-sample'], 'output': ['text', 'sha1', 'sha256', 'md5', 'link']}
26
+ moduleinfo = {
27
+ 'version': '0.3',
28
+ 'author': 'Koen Van Impe',
29
+ 'description': 'Module to submit a sample to VMRay.',
30
+ 'module-type': ['expansion'],
31
+ 'name': 'VMRay Submit',
32
+ 'logo': 'vmray.png',
33
+ 'requirements': ['An access to the VMRay API (apikey & url)'],
34
+ 'features': 'This module takes an attachment or malware-sample attribute as input to query the VMRay API.\n\nThe sample contained within the attribute in then enriched with data from VMRay mapped into MISP attributes.',
35
+ 'references': ['https://www.vmray.com/'],
36
+ 'input': 'An attachment or malware-sample attribute.',
37
+ 'output': 'MISP attributes mapped from the result of the query on VMRay API, included in the following list:\n- text\n- sha1\n- sha256\n- md5\n- link',
38
+ }
39
+ moduleconfig = ['apikey', 'url', 'shareable', 'do_not_reanalyze', 'do_not_include_vmrayjobids']
40
+
41
+
42
+ include_vmrayjobids = False
43
+
44
+
45
+ def handler(q=False):
46
+ global include_vmrayjobids
47
+
48
+ if q is False:
49
+ return False
50
+ request = json.loads(q)
51
+
52
+ try:
53
+ data = request.get("data")
54
+ if 'malware-sample' in request:
55
+ # malicious samples are encrypted with zip (password infected) and then base64 encoded
56
+ sample_filename = request.get("malware-sample").split("|", 1)[0]
57
+ data = base64.b64decode(data)
58
+ fl = io.BytesIO(data)
59
+ zf = zipfile.ZipFile(fl)
60
+ sample_hashname = zf.namelist()[0]
61
+ data = zf.read(sample_hashname, b"infected")
62
+ zf.close()
63
+ elif 'attachment' in request:
64
+ # All attachments get base64 encoded
65
+ sample_filename = request.get("attachment")
66
+ data = base64.b64decode(data)
67
+
68
+ else:
69
+ misperrors['error'] = "No malware sample or attachment supplied"
70
+ return misperrors
71
+ except Exception:
72
+ misperrors['error'] = "Unable to process submited sample data"
73
+ return misperrors
74
+
75
+ if (request["config"].get("apikey") is None) or (request["config"].get("url") is None):
76
+ misperrors["error"] = "Missing API key or server URL (hint: try cloud.vmray.com)"
77
+ return misperrors
78
+
79
+ api = VMRayRESTAPI(request["config"].get("url"), request["config"].get("apikey"), False)
80
+
81
+ shareable = request["config"].get("shareable")
82
+ do_not_reanalyze = request["config"].get("do_not_reanalyze")
83
+ do_not_include_vmrayjobids = request["config"].get("do_not_include_vmrayjobids")
84
+
85
+ try:
86
+ shareable = bool(strtobool(shareable)) # Do we want the sample to be shared?
87
+ reanalyze = not bool(strtobool(do_not_reanalyze)) # Always reanalyze the sample?
88
+ include_vmrayjobids = not bool(strtobool(do_not_include_vmrayjobids)) # Include the references to VMRay job IDs
89
+ except ValueError:
90
+ misperrors["error"] = "Error while processing settings. Please double-check your values."
91
+ return misperrors
92
+
93
+ if data and sample_filename:
94
+ args = {}
95
+ args["shareable"] = shareable
96
+ args["sample_file"] = {'data': io.BytesIO(data), 'filename': sample_filename}
97
+ args["reanalyze"] = reanalyze
98
+
99
+ try:
100
+ vmraydata = vmraySubmit(api, args)
101
+ if vmraydata["errors"] and "Submission not stored" not in vmraydata["errors"][0]["error_msg"]:
102
+ misperrors['error'] = "VMRay: %s" % vmraydata["errors"][0]["error_msg"]
103
+ return misperrors
104
+ else:
105
+ return vmrayProcess(vmraydata)
106
+ except Exception:
107
+ misperrors['error'] = "Problem when calling API."
108
+ return misperrors
109
+ else:
110
+ misperrors['error'] = "No sample data or filename."
111
+ return misperrors
112
+
113
+
114
+ def introspection():
115
+ return mispattributes
116
+
117
+
118
+ def version():
119
+ moduleinfo['config'] = moduleconfig
120
+ return moduleinfo
121
+
122
+
123
+ def vmrayProcess(vmraydata):
124
+ ''' Process the JSON file returned by vmray'''
125
+ if vmraydata:
126
+ try:
127
+ sample = vmraydata["samples"][0]
128
+ jobs = vmraydata["jobs"]
129
+
130
+ # Result received?
131
+ if sample:
132
+ r = {'results': []}
133
+ r['results'].append({'types': 'md5', 'values': sample['sample_md5hash']})
134
+ r['results'].append({'types': 'sha1', 'values': sample['sample_sha1hash']})
135
+ r['results'].append({'types': 'sha256', 'values': sample['sample_sha256hash']})
136
+ r['results'].append({'types': 'text', 'values': 'VMRay Sample ID: %s' % sample['sample_id'], 'tags': 'workflow:state="incomplete"'})
137
+ r['results'].append({'types': 'link', 'values': sample['sample_webif_url']})
138
+
139
+ # Include data from different jobs
140
+ if include_vmrayjobids and len(jobs) > 0:
141
+ for job in jobs:
142
+ job_id = job["job_id"]
143
+ job_vm_name = job["job_vm_name"]
144
+ job_configuration_name = job["job_configuration_name"]
145
+ r["results"].append({"types": "text", "values": "VMRay Job ID %s (%s - %s)" % (job_id, job_vm_name, job_configuration_name)})
146
+ return r
147
+ else:
148
+ misperrors['error'] = "No valid results returned."
149
+ return misperrors
150
+ except Exception:
151
+ misperrors['error'] = "No valid submission data returned."
152
+ return misperrors
153
+ else:
154
+ misperrors['error'] = "Unable to parse results."
155
+ return misperrors
156
+
157
+
158
+ def vmraySubmit(api, args):
159
+ ''' Submit the sample to VMRay'''
160
+ vmraydata = api.call("POST", "/rest/sample/submit", args)
161
+ return vmraydata