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,113 @@
1
+ import json
2
+ import pypssl
3
+ from . import check_input_attribute, standard_error_message
4
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
5
+
6
+ mispattributes = {'input': ['ip-src', 'ip-dst', 'ip-src|port', 'ip-dst|port'], 'format': 'misp_standard'}
7
+ moduleinfo = {
8
+ 'version': '0.2',
9
+ 'author': 'Raphaël Vinot',
10
+ 'description': 'Modules to access CIRCL Passive SSL.',
11
+ 'module-type': ['expansion', 'hover'],
12
+ 'name': 'CIRCL Passive SSL',
13
+ 'logo': 'passivessl.png',
14
+ 'requirements': ['pypssl: Passive SSL python library', 'A CIRCL passive SSL account with username & password'],
15
+ 'features': 'This module takes an ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive SSL REST API to gather the related certificates and return the corresponding MISP objects.\n\nTo make it work a username and a password are required to authenticate to the CIRCL Passive SSL API.',
16
+ 'references': ['https://www.circl.lu/services/passive-ssl/'],
17
+ 'input': 'IP address attribute.',
18
+ 'output': 'x509 certificate objects seen by the IP address(es).',
19
+ }
20
+ moduleconfig = ['username', 'password']
21
+
22
+
23
+ class PassiveSSLParser():
24
+ def __init__(self, attribute, authentication):
25
+ self.misp_event = MISPEvent()
26
+ self.attribute = MISPAttribute()
27
+ self.attribute.from_dict(**attribute)
28
+ self.misp_event.add_attribute(**self.attribute)
29
+ self.pssl = pypssl.PyPSSL(basic_auth=authentication)
30
+ self.cert_hash = 'x509-fingerprint-sha1'
31
+ self.cert_type = 'pem'
32
+ self.mapping = {'issuer': ('text', 'issuer'),
33
+ 'keylength': ('text', 'pubkey-info-size'),
34
+ 'not_after': ('datetime', 'validity-not-after'),
35
+ 'not_before': ('datetime', 'validity-not-before'),
36
+ 'subject': ('text', 'subject')}
37
+
38
+ def get_results(self):
39
+ if hasattr(self, 'result'):
40
+ return self.result
41
+ event = json.loads(self.misp_event.to_json())
42
+ results = {key: event[key] for key in ('Attribute', 'Object')}
43
+ return {'results': results}
44
+
45
+ def parse(self):
46
+ value = self.attribute.value.split('|')[0] if '|' in self.attribute.type else self.attribute.value
47
+
48
+ try:
49
+ results = self.pssl.query(value)
50
+ except Exception:
51
+ self.result = {'error': 'There is an authentication error, please make sure you supply correct credentials.'}
52
+ return
53
+
54
+ if not results:
55
+ self.result = {'error': 'Not found'}
56
+ return
57
+
58
+ if 'error' in results:
59
+ self.result = {'error': results['error']}
60
+ return
61
+
62
+ for ip_address, certificates in results.items():
63
+ ip_uuid = self._handle_ip_attribute(ip_address)
64
+ for certificate in certificates['certificates']:
65
+ self._handle_certificate(certificate, ip_uuid)
66
+
67
+ def _handle_certificate(self, certificate, ip_uuid):
68
+ x509 = MISPObject('x509')
69
+ x509.add_attribute(self.cert_hash, type=self.cert_hash, value=certificate)
70
+ cert_details = self.pssl.fetch_cert(certificate)
71
+ info = cert_details['info']
72
+ for feature, mapping in self.mapping.items():
73
+ attribute_type, object_relation = mapping
74
+ x509.add_attribute(object_relation, type=attribute_type, value=info[feature])
75
+ x509.add_attribute(self.cert_type, type='text', value=self.cert_type)
76
+ x509.add_reference(ip_uuid, 'seen-by')
77
+ self.misp_event.add_object(**x509)
78
+
79
+ def _handle_ip_attribute(self, ip_address):
80
+ if ip_address == self.attribute.value:
81
+ return self.attribute.uuid
82
+ ip_attribute = MISPAttribute()
83
+ ip_attribute.from_dict(**{'type': self.attribute.type, 'value': ip_address})
84
+ self.misp_event.add_attribute(**ip_attribute)
85
+ return ip_attribute.uuid
86
+
87
+
88
+ def handler(q=False):
89
+ if q is False:
90
+ return False
91
+ request = json.loads(q)
92
+ if not request.get('config'):
93
+ return {'error': 'CIRCL Passive SSL authentication is missing.'}
94
+ if not request['config'].get('username') or not request['config'].get('password'):
95
+ return {'error': 'CIRCL Passive SSL authentication is incomplete, please provide your username and password.'}
96
+ authentication = (request['config']['username'], request['config']['password'])
97
+ if not request.get('attribute') or not check_input_attribute(request['attribute']):
98
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
99
+ attribute = request['attribute']
100
+ if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
101
+ return {'error': 'Unsupported attribute type.'}
102
+ pssl_parser = PassiveSSLParser(attribute, authentication)
103
+ pssl_parser.parse()
104
+ return pssl_parser.get_results()
105
+
106
+
107
+ def introspection():
108
+ return mispattributes
109
+
110
+
111
+ def version():
112
+ moduleinfo['config'] = moduleconfig
113
+ return moduleinfo
@@ -0,0 +1,129 @@
1
+ import base64
2
+ import io
3
+ import logging
4
+ import sys
5
+ import zipfile
6
+ import clamd
7
+ from . import check_input_attribute, standard_error_message
8
+ from typing import Optional
9
+ from pymisp import MISPEvent, MISPObject
10
+
11
+ log = logging.getLogger("clamav")
12
+ log.setLevel(logging.DEBUG)
13
+ sh = logging.StreamHandler(sys.stdout)
14
+ sh.setLevel(logging.DEBUG)
15
+ fmt = logging.Formatter(
16
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
17
+ )
18
+ sh.setFormatter(fmt)
19
+ log.addHandler(sh)
20
+
21
+ moduleinfo = {
22
+ 'version': '0.1',
23
+ 'author': 'Jakub Onderka',
24
+ 'description': 'Submit file to ClamAV',
25
+ 'module-type': ['expansion'],
26
+ 'name': 'ClaamAV',
27
+ 'logo': '',
28
+ 'requirements': [],
29
+ 'features': '',
30
+ 'references': [],
31
+ 'input': '',
32
+ 'output': '',
33
+ }
34
+ moduleconfig = ["connection"]
35
+ mispattributes = {
36
+ "input": ["attachment", "malware-sample"],
37
+ "format": "misp_standard"
38
+ }
39
+
40
+
41
+ def create_response(original_attribute: dict, software: str, signature: Optional[str] = None) -> dict:
42
+ misp_event = MISPEvent()
43
+ if signature:
44
+ misp_event.add_attribute(**original_attribute)
45
+
46
+ av_signature_object = MISPObject("av-signature")
47
+ av_signature_object.add_attribute("signature", signature)
48
+ av_signature_object.add_attribute("software", software)
49
+ av_signature_object.add_reference(original_attribute["uuid"], "belongs-to")
50
+ misp_event.add_object(av_signature_object)
51
+
52
+ event = misp_event.to_dict()
53
+ results = {key: event[key] for key in ('Attribute', 'Object') if (key in event and event[key])}
54
+ return {"results": results}
55
+
56
+
57
+ def connect_to_clamav(connection_string: str) -> clamd.ClamdNetworkSocket:
58
+ if connection_string.startswith("unix://"):
59
+ return clamd.ClamdUnixSocket(connection_string.replace("unix://", ""))
60
+ elif ":" in connection_string:
61
+ host, port = connection_string.split(":")
62
+ return clamd.ClamdNetworkSocket(host, int(port))
63
+ else:
64
+ raise Exception("ClamAV connection string is invalid. It must be unix socket path with 'unix://' prefix or IP:PORT.")
65
+
66
+
67
+ def dict_handler(request: dict):
68
+ connection_string: str = request["config"].get("connection")
69
+ if not connection_string:
70
+ return {"error": "No ClamAV connection string provided"}
71
+
72
+ attribute = request.get("attribute")
73
+ if not attribute:
74
+ return {"error": "No attribute provided"}
75
+
76
+ if not check_input_attribute(request['attribute']):
77
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
78
+
79
+ if attribute["type"] not in mispattributes["input"]:
80
+ return {"error": "Invalid attribute type provided, expected 'malware-sample' or 'attachment'"}
81
+
82
+ attribute_data = attribute.get("data")
83
+ if not attribute_data:
84
+ return {"error": "No attribute data provided"}
85
+
86
+ try:
87
+ clamav = connect_to_clamav(connection_string)
88
+ software_version = clamav.version()
89
+ except Exception:
90
+ logging.exception("Could not connect to ClamAV")
91
+ return {"error": "Could not connect to ClamAV"}
92
+
93
+ try:
94
+ data = base64.b64decode(attribute_data, validate=True)
95
+ except Exception:
96
+ logging.exception("Provided data is not valid base64 encoded string")
97
+ return {"error": "Provided data is not valid base64 encoded string"}
98
+
99
+ if attribute["type"] == "malware-sample":
100
+ try:
101
+ with zipfile.ZipFile(io.BytesIO(data)) as zipf:
102
+ data = zipf.read(zipf.namelist()[0], pwd=b"infected")
103
+ except Exception:
104
+ logging.exception("Could not extract malware sample from ZIP file")
105
+ return {"error": "Could not extract malware sample from ZIP file"}
106
+
107
+ try:
108
+ status, reason = clamav.instream(io.BytesIO(data))["stream"]
109
+ except Exception:
110
+ logging.exception("Could not send attribute data to ClamAV. Maybe file is too big?")
111
+ return {"error": "Could not send attribute data to ClamAV. Maybe file is too big?"}
112
+
113
+ if status == "ERROR":
114
+ return {"error": "ClamAV returned error message: {}".format(reason)}
115
+ elif status == "OK":
116
+ return {"results": {}}
117
+ elif status == "FOUND":
118
+ return create_response(attribute, software_version, reason)
119
+ else:
120
+ return {"error": "ClamAV returned invalid status {}: {}".format(status, reason)}
121
+
122
+
123
+ def introspection():
124
+ return mispattributes
125
+
126
+
127
+ def version():
128
+ moduleinfo["config"] = moduleconfig
129
+ return moduleinfo
@@ -0,0 +1,239 @@
1
+ import json
2
+ import requests
3
+ import uuid
4
+ from . import check_input_attribute, standard_error_message
5
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
6
+
7
+ moduleinfo = {
8
+ 'version': '0.1',
9
+ 'author': 'Milo Volpicelli',
10
+ 'description': 'Module to query Cluster25 CTI.',
11
+ 'module-type': ['expansion', 'hover'],
12
+ 'name': 'Cluster25 Expand',
13
+ 'logo': 'cluster25.png',
14
+ 'requirements': ['A Cluster25 API access (API id & key)'],
15
+ 'features': 'This module takes a MISP attribute value as input to query the Cluster25CTI API. The result is then mapped into compatible MISP Objects and relative attributes.\n',
16
+ 'references': [''],
17
+ 'input': 'An Indicator value of type included in the following list:\n- domain\n- email-src\n- email-dst\n- filename\n- md5\n- sha1\n- sha256\n- ip-src\n- ip-dst\n- url\n- vulnerability\n- btc\n- xmr\n ja3-fingerprint-md5',
18
+ 'output': 'A series of c25 MISP Objects with colletion of attributes mapped from Cluster25 CTI query result.',
19
+ }
20
+ moduleconfig = ['api_id', 'apikey', 'base_url']
21
+ misperrors = {'error': 'Error'}
22
+ misp_type_in = ['domain', 'email-src', 'email-dst', 'filename', 'md5', 'sha1', 'sha256', 'ip-src', 'ip-dst', 'url',
23
+ 'vulnerability', 'btc', 'xmr', 'ja3-fingerprint-md5']
24
+
25
+ mapping_out = { # mapping between the MISP attributes type and the compatible Cluster25 indicator types.
26
+ 'domain': {'type': 'domain', 'to_ids': True},
27
+ 'email-src': {'type': 'email-src', 'to_ids': True},
28
+ 'email-dst': {'type': 'email-dst', 'to_ids': True},
29
+ 'filename': {'type': 'filename', 'to_ids': True},
30
+ 'md5': {'type': 'md5', 'to_ids': True},
31
+ 'sha1': {'type': 'sha1', 'to_ids': True},
32
+ 'sha256': {'type': 'sha256', 'to_ids': True},
33
+ 'ip-src': {'type': 'ip-src', 'to_ids': True},
34
+ 'ip-dst': {'type': 'ip-dst', 'to_ids': True},
35
+ 'url': {'type': 'url', 'to_ids': True},
36
+ 'cve': {'type': 'vulnerability', 'to_ids': True},
37
+ 'btcaddress': {'type': 'btc', 'to_ids': True},
38
+ 'xmraddress': {'type': 'xmr', 'to_ids': True},
39
+ 'ja3': {'type': 'ja3-fingerprint-md5', 'to_ids': True},
40
+ }
41
+ misp_type_out = [item['type'] for item in mapping_out.values()]
42
+ misp_attributes = {'input': misp_type_in, 'format': 'misp_standard'}
43
+
44
+
45
+ def handler(q=False):
46
+ if q is False:
47
+ return False
48
+ request = json.loads(q)
49
+ # validate Cluster25 params
50
+ if request.get('config'):
51
+ if request['config'].get('apikey') is None:
52
+ misperrors['error'] = 'Cluster25 apikey is missing'
53
+ return misperrors
54
+ if request['config'].get('api_id') is None:
55
+ misperrors['error'] = 'Cluster25 api_id is missing'
56
+ return misperrors
57
+ if request['config'].get('base_url') is None:
58
+ misperrors['error'] = 'Cluster25 base_url is missing'
59
+ return misperrors
60
+
61
+ # validate attribute
62
+ if not request.get('attribute') or not check_input_attribute(request['attribute']):
63
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
64
+ attribute = request.get('attribute')
65
+ if not any(input_type == attribute.get('type') for input_type in misp_type_in):
66
+ return {'error': 'Unsupported attribute type.'}
67
+
68
+ client = Cluster25CTI(request['config']['api_id'], request['config']['apikey'], request['config']['base_url'])
69
+
70
+ return lookup_indicator(client, request.get('attribute'))
71
+
72
+
73
+ def format_content(content):
74
+ if isinstance(content, str) or isinstance(content, bool) or isinstance(content, int) or isinstance(content, float):
75
+ return content
76
+ ret = ""
77
+ tmp_ret = []
78
+ if content is None:
79
+ return ret
80
+ is_dict = isinstance(content, dict)
81
+ is_list = isinstance(content, list)
82
+ for index, key in enumerate(content):
83
+ if is_dict:
84
+ if isinstance(content[key], dict):
85
+ ret = format_content(content[key])
86
+ elif isinstance(content[key], list):
87
+ for list_item in content[key]:
88
+ tmp_ret.append(format_content(list_item))
89
+ else:
90
+ tmp_ret.append(f"{key}: {content[key]}")
91
+ elif is_list:
92
+ if isinstance(content[index], str):
93
+ ret = ", ".join(content)
94
+ else:
95
+ ret = format_content(content)
96
+ if tmp_ret:
97
+ ret = " ".join(tmp_ret)
98
+ return ret
99
+
100
+
101
+ def lookup_indicator(client, attr):
102
+ result = client.investigate(attr)
103
+ if result.get('error'):
104
+ return result
105
+ misp_event = MISPEvent()
106
+ attribute = MISPAttribute()
107
+ attribute.from_dict(**attr)
108
+ misp_event.add_attribute(**attribute)
109
+
110
+ misp_object_g = MISPObject('c25_generic_info')
111
+ misp_object_g.template_uuid = uuid.uuid4()
112
+ misp_object_g.description = 'c25_generic_info'
113
+ setattr(misp_object_g, 'meta-category', 'network')
114
+
115
+ misp_objects = []
116
+ for ind, entry in enumerate(result):
117
+ if isinstance(result[entry], dict):
118
+ tmp_obj = MISPObject(f"c25_{entry}")
119
+ tmp_obj.template_uuid = uuid.uuid4()
120
+ tmp_obj.description = f"c25_{entry}"
121
+ setattr(tmp_obj, 'meta-category', 'network')
122
+ tmp_obj.add_reference(attribute['uuid'], 'related-to')
123
+ for key in result[entry]:
124
+ if isinstance(result[entry][key], dict):
125
+ for index, item in enumerate(result[entry][key]):
126
+ if result[entry][key][item]:
127
+ tmp_obj.add_attribute(f"{entry}_{key}_{item}", **{'type': 'text', 'value': format_content(
128
+ result[entry][key][item])})
129
+
130
+ elif isinstance(result[entry][key], list):
131
+ for index, item in enumerate(result[entry][key]):
132
+ if isinstance(item, dict):
133
+ tmp_obj_2 = MISPObject(f"c25_{entry}_{key}_{index + 1}")
134
+ tmp_obj_2.template_uuid = uuid.uuid4()
135
+ tmp_obj_2.description = f"c25_{entry}_{key}"
136
+ setattr(tmp_obj_2, 'meta-category', 'network')
137
+ tmp_obj_2.add_reference(attribute['uuid'], 'related-to')
138
+ for sub_key in item:
139
+ if isinstance(item[sub_key], list):
140
+ for sub_item in item[sub_key]:
141
+ if isinstance(sub_item, dict):
142
+ tmp_obj_3 = MISPObject(f"c25_{entry}_{sub_key}_{index + 1}")
143
+ tmp_obj_3.template_uuid = uuid.uuid4()
144
+ tmp_obj_3.description = f"c25_{entry}_{sub_key}"
145
+ setattr(tmp_obj_3, 'meta-category', 'network')
146
+ tmp_obj_3.add_reference(attribute['uuid'], 'related-to')
147
+ for sub_sub_key in sub_item:
148
+ if isinstance(sub_item[sub_sub_key], list):
149
+ for idx, sub_sub_item in enumerate(sub_item[sub_sub_key]):
150
+ if sub_sub_item.get("name"):
151
+ sub_sub_item = sub_sub_item.get("name")
152
+ tmp_obj_3.add_attribute(f"{sub_sub_key}_{idx + 1}",
153
+ **{'type': 'text',
154
+ 'value': format_content(
155
+ sub_sub_item)})
156
+ else:
157
+ tmp_obj_3.add_attribute(sub_sub_key,
158
+ **{'type': 'text',
159
+ 'value': format_content(
160
+ sub_item[sub_sub_key])})
161
+ misp_objects.append(tmp_obj_3)
162
+ else:
163
+ tmp_obj_2.add_attribute(sub_key, **{'type': 'text',
164
+ 'value': format_content(sub_item)})
165
+
166
+ elif item[sub_key]:
167
+ tmp_obj_2.add_attribute(sub_key,
168
+ **{'type': 'text', 'value': format_content(item[sub_key])})
169
+ misp_objects.append(tmp_obj_2)
170
+ elif item is not None:
171
+ tmp_obj.add_attribute(f"{entry}_{key}", **{'type': 'text', 'value': format_content(item)})
172
+ elif result[entry][key] is not None:
173
+ tmp_obj.add_attribute(key, **{'type': 'text', 'value': result[entry][key]})
174
+
175
+ if tmp_obj.attributes:
176
+ misp_objects.append(tmp_obj)
177
+
178
+ elif isinstance(result[entry], list):
179
+ for index, key in enumerate(result[entry]):
180
+ if isinstance(key, dict):
181
+ tmp_obj = MISPObject(f"c25_{entry}_{index + 1}")
182
+ tmp_obj.template_uuid = uuid.uuid4()
183
+ tmp_obj.description = f"c25_{entry}_{index + 1}"
184
+ setattr(tmp_obj, 'meta-category', 'network')
185
+ tmp_obj.add_reference(attribute['uuid'], 'related-to')
186
+ for item in key:
187
+ if key[item]:
188
+ tmp_obj.add_attribute(item, **{'type': 'text', 'value': format_content(key[item])})
189
+ tmp_obj.add_reference(attribute['uuid'], 'related-to')
190
+ misp_objects.append(tmp_obj)
191
+ elif key is not None:
192
+ misp_object_g.add_attribute(f"{entry}_{index + 1}",
193
+ **{'type': 'text', 'value': format_content(key)})
194
+ else:
195
+ if result[entry]:
196
+ misp_object_g.add_attribute(entry, **{'type': 'text', 'value': result[entry]})
197
+
198
+ misp_object_g.add_reference(attribute['uuid'], 'related-to')
199
+ misp_event.add_object(misp_object_g)
200
+ for misp_object in misp_objects:
201
+ misp_event.add_object(misp_object)
202
+
203
+ event = json.loads(misp_event.to_json())
204
+ results = {key: event[key] for key in ('Attribute', 'Object')}
205
+ return {'results': results}
206
+
207
+
208
+ def introspection():
209
+ return misp_attributes
210
+
211
+
212
+ def version():
213
+ moduleinfo['config'] = moduleconfig
214
+ return moduleinfo
215
+
216
+
217
+ class Cluster25CTI:
218
+ def __init__(self, customer_id=None, customer_key=None, base_url=None):
219
+ self.client_id = customer_id
220
+ self.client_secret = customer_key
221
+ self.base_url = base_url
222
+ self.current_token = self._get_cluster25_token()
223
+ self.headers = {"Authorization": f"Bearer {self.current_token}"}
224
+
225
+ def _get_cluster25_token(self):
226
+ payload = {"client_id": self.client_id, "client_secret": self.client_secret}
227
+ r = requests.post(url=f"{self.base_url}/token", json=payload, headers={"Content-Type": "application/json"})
228
+ if r.status_code != 200:
229
+ return {'error': f"Unable to retrieve the token from C25 platform, status {r.status_code}"}
230
+ return r.json()["data"]["token"]
231
+
232
+ def investigate(self, indicator) -> dict:
233
+ params = {'indicator': indicator.get('value')}
234
+ r = requests.get(url=f"{self.base_url}/investigate", params=params, headers=self.headers)
235
+ if r.status_code != 200:
236
+ return {'error': f"Unable to retrieve investigate result for indicator '{indicator.get('value')}' "
237
+ f"from C25 platform, status {r.status_code}"}
238
+ return r.json()["data"]
239
+
@@ -0,0 +1,69 @@
1
+ import json
2
+ import requests
3
+
4
+ misperrors = {'error': 'Error'}
5
+ mispattributes = {'input': ['hostname', 'domain']}
6
+
7
+ # possible module-types: 'expansion', 'hover' or both
8
+ moduleinfo = {
9
+ 'version': '1',
10
+ 'author': 'Hannah Ward',
11
+ 'description': 'Module to expand country codes.',
12
+ 'module-type': ['hover'],
13
+ 'name': 'Country Code',
14
+ 'logo': '',
15
+ 'requirements': [],
16
+ 'features': 'The module takes a domain or a hostname as input, and returns the country it belongs to.\n\nFor non country domains, a list of the most common possible extensions is used.',
17
+ 'references': [],
18
+ 'input': 'Hostname or domain attribute.',
19
+ 'output': 'Text with the country code the input belongs to.',
20
+ }
21
+
22
+ # config fields that your code expects from the site admin
23
+ moduleconfig = []
24
+
25
+ common_tlds = {"com": "Commercial (Worldwide)",
26
+ "org": "Organisation (Worldwide)",
27
+ "net": "Network (Worldwide)",
28
+ "int": "International (Worldwide)",
29
+ "edu": "Education (Usually USA)",
30
+ "gov": "Government (USA)"
31
+ }
32
+
33
+
34
+ def parse_country_code(extension):
35
+ # Retrieve a json full of country info
36
+ try:
37
+ codes = requests.get("http://www.geognos.com/api/en/countries/info/all.json").json()
38
+ except Exception:
39
+ return "http://www.geognos.com/api/en/countries/info/all.json not reachable"
40
+ if not codes.get('StatusMsg') or not codes["StatusMsg"] == "OK":
41
+ return 'Not able to get the countrycode references from http://www.geognos.com/api/en/countries/info/all.json'
42
+ for country in codes['Results'].values():
43
+ if country['CountryCodes']['tld'] == extension:
44
+ return country['Name']
45
+ return "Unknown"
46
+
47
+
48
+ def handler(q=False):
49
+ if q is False:
50
+ return False
51
+ request = json.loads(q)
52
+ domain = request["domain"] if "domain" in request else request["hostname"]
53
+
54
+ # Get the extension
55
+ ext = domain.split(".")[-1]
56
+
57
+ # Check if it's a common, non country one
58
+ val = common_tlds[ext] if ext in common_tlds.keys() else parse_country_code(ext)
59
+ r = {'results': [{'types': ['text'], 'values':[val]}]}
60
+ return r
61
+
62
+
63
+ def introspection():
64
+ return mispattributes
65
+
66
+
67
+ def version():
68
+ moduleinfo['config'] = moduleconfig
69
+ return moduleinfo