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,238 @@
1
+ import json
2
+ import requests
3
+ import time
4
+
5
+ misperrors = {'error': 'Error'}
6
+ mispattributes = {'input': ['btc'], 'output': ['text']}
7
+ moduleinfo = {
8
+ 'version': '0.1',
9
+ 'author': 'Sascha Rommelfangen',
10
+ 'description': 'An expansion hover module to get a blockchain balance from a BTC address in MISP.',
11
+ 'module-type': ['hover'],
12
+ 'name': 'BTC Steroids',
13
+ 'logo': 'bitcoin.png',
14
+ 'requirements': [],
15
+ 'features': '',
16
+ 'references': [],
17
+ 'input': 'btc address attribute.',
18
+ 'output': 'Text to describe the blockchain balance and the transactions related to the btc address in input.',
19
+ }
20
+
21
+ moduleconfig = []
22
+
23
+ blockchain_firstseen = 'https://blockchain.info/q/addressfirstseen/'
24
+ blockchain_balance = 'https://blockchain.info/q/addressbalance/'
25
+ blockchain_totalreceived = 'https://blockchain.info/q/getreceivedbyaddress/'
26
+ blockchain_all = 'https://blockchain.info/rawaddr/{}?filter=5{}'
27
+ converter = 'https://min-api.cryptocompare.com/data/pricehistorical?fsym=BTC&tsyms=USD,EUR&ts={}'
28
+ converter_rls = 'https://min-api.cryptocompare.com/stats/rate/limit'
29
+ result_text = ""
30
+ g_rate_limit = 300
31
+ start_time = 0
32
+ conversion_rates = {}
33
+
34
+
35
+ def get_consumption(output=False):
36
+ try:
37
+ req = requests.get(converter_rls)
38
+ jreq = req.json()
39
+ minute = str(jreq['Data']['calls_left']['minute'])
40
+ hour = str(jreq['Data']['calls_left']['hour'])
41
+ except Exception:
42
+ minute = str(-1)
43
+ hour = str(-1)
44
+ # Debug out for the console
45
+ print("Calls left this minute / hour: " + minute + " / " + hour)
46
+ return minute, hour
47
+
48
+
49
+ def convert(btc, timestamp):
50
+ global g_rate_limit
51
+ global start_time
52
+ global now
53
+ global conversion_rates
54
+ date = time.strftime('%Y-%m-%d', time.localtime(timestamp))
55
+ # Lookup conversion rates in the cache:
56
+ if date in conversion_rates:
57
+ (usd, eur) = conversion_rates[date]
58
+ else:
59
+ # If not cached, we have to get the converion rates
60
+ # We have to be careful with rate limiting on the server side
61
+ if g_rate_limit == 300:
62
+ minute, hour = get_consumption()
63
+ g_rate_limit -= 1
64
+ now = time.time()
65
+ # delta = now - start_time
66
+ # print(g_rate_limit)
67
+ if g_rate_limit <= 10:
68
+ minute, hour = get_consumption(output=True)
69
+ if int(minute) <= 10:
70
+ # print(minute)
71
+ # get_consumption(output=True)
72
+ time.sleep(3)
73
+ else:
74
+ mprint(minute)
75
+ start_time = time.time()
76
+ g_rate_limit = int(minute)
77
+ try:
78
+ req = requests.get(converter.format(timestamp))
79
+ jreq = req.json()
80
+ usd = jreq['BTC']['USD']
81
+ eur = jreq['BTC']['EUR']
82
+ # Since we have the rates, store them in the cache
83
+ conversion_rates[date] = (usd, eur)
84
+ except Exception as ex:
85
+ mprint(ex)
86
+ get_consumption(output=True)
87
+ # Actually convert and return the values
88
+ u = usd * btc
89
+ e = eur * btc
90
+ return u, e
91
+
92
+
93
+ def mprint(input):
94
+ # Prepare the final print
95
+ global result_text
96
+ result_text = result_text + "\n" + str(input)
97
+
98
+
99
+ def handler(q=False):
100
+ global result_text
101
+ global conversion_rates
102
+ result_text = ""
103
+ # start_time = time.time()
104
+ # now = time.time()
105
+ if q is False:
106
+ return False
107
+ request = json.loads(q)
108
+ click = False
109
+ # This means the magnifying glass has been clicked
110
+ if request.get('persistent') == 1:
111
+ click = True
112
+ # Otherwise the attribute was only hovered over
113
+ if request.get('btc'):
114
+ btc = request['btc']
115
+ else:
116
+ return False
117
+ mprint("\nAddress:\t" + btc)
118
+ try:
119
+ req = requests.get(blockchain_all.format(btc, "&limit=50"))
120
+ jreq = req.json()
121
+ except Exception:
122
+ # print(e)
123
+ print(req.text)
124
+ result_text = "Not a valid BTC address"
125
+ r = {
126
+ 'results': [
127
+ {
128
+ 'types': ['text'],
129
+ 'values':[
130
+ str(result_text)
131
+ ]
132
+ }
133
+ ]
134
+ }
135
+ return r
136
+
137
+ n_tx = jreq['n_tx']
138
+ balance = float(jreq['final_balance'] / 100000000)
139
+ rcvd = float(jreq['total_received'] / 100000000)
140
+ sent = float(jreq['total_sent'] / 100000000)
141
+ output = 'Balance:\t{0:.10f} BTC (+{1:.10f} BTC / -{2:.10f} BTC)'
142
+ mprint(output.format(balance, rcvd, sent))
143
+ if click is False:
144
+ mprint("Transactions:\t" + str(n_tx) + "\t (previewing up to 5 most recent)")
145
+ else:
146
+ mprint("Transactions:\t" + str(n_tx))
147
+ if n_tx > 0:
148
+ mprint("======================================================================================")
149
+ i = 0
150
+ while i < n_tx:
151
+ if click is False:
152
+ try:
153
+ req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i)))
154
+ except Exception as e:
155
+ # Lazy retry - cries for a function
156
+ print(e)
157
+ time.sleep(3)
158
+ req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i)))
159
+ if n_tx > 5:
160
+ n_tx = 5
161
+ else:
162
+ try:
163
+ req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i)))
164
+ except Exception as e:
165
+ # Lazy retry - cries for a function
166
+ print(e)
167
+ time.sleep(3)
168
+ req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i)))
169
+ jreq = req.json()
170
+ if jreq['txs']:
171
+ for transactions in jreq['txs']:
172
+ sum = 0
173
+ sum_counter = 0
174
+ for tx in transactions['inputs']:
175
+ script_old = tx['script']
176
+ try:
177
+ addr_in = tx['prev_out']['addr']
178
+ except KeyError:
179
+ addr_in = None
180
+ try:
181
+ prev_out = tx['prev_out']['value']
182
+ except KeyError:
183
+ prev_out = None
184
+ if prev_out != 0 and addr_in == btc:
185
+ datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time'])))
186
+ value = float(tx['prev_out']['value'] / 100000000)
187
+ u, e = convert(value, transactions['time'])
188
+ mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0'))
189
+ if script_old != tx['script']:
190
+ i += 1
191
+ else:
192
+ sum_counter += 1
193
+ sum += value
194
+ if sum_counter > 1:
195
+ u, e = convert(sum, transactions['time'])
196
+ mprint("\t\t\t\t\t----------------------------------------------")
197
+ mprint("#" + str(n_tx - i) + "\t\t\t\t Sum:\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR\n".format(sum, u, e).rstrip('0'))
198
+ for tx in transactions['out']:
199
+ try:
200
+ addr_out = tx['addr']
201
+ except KeyError:
202
+ addr_out = None
203
+ try:
204
+ prev_out = tx['prev_out']['value']
205
+ except KeyError:
206
+ prev_out = None
207
+ if prev_out != 0 and addr_out == btc:
208
+ datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time'])))
209
+ value = float(tx['value'] / 100000000)
210
+ u, e = convert(value, transactions['time'])
211
+ mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t {0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0'))
212
+ # i += 1
213
+ i += 1
214
+
215
+ r = {
216
+ 'results': [
217
+ {
218
+ 'types': ['text'],
219
+ 'values':[
220
+ str(result_text)
221
+ ]
222
+ }
223
+ ]
224
+ }
225
+ # Debug output on the console
226
+ print(result_text)
227
+ # Unset the result for the next request
228
+ result_text = ""
229
+ return r
230
+
231
+
232
+ def introspection():
233
+ return mispattributes
234
+
235
+
236
+ def version():
237
+ moduleinfo['config'] = moduleconfig
238
+ return moduleinfo
@@ -0,0 +1,287 @@
1
+ # encoding: utf-8
2
+ import json
3
+ import configparser
4
+ import base64
5
+ import codecs
6
+ import censys.common.config
7
+ from dateutil.parser import isoparse
8
+ from . import check_input_attribute, standard_error_message
9
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
10
+
11
+ try:
12
+ #needed in order to overwrite the censys module intent of creating config files in the home folder of the proccess owner
13
+ #--
14
+ def get_config_over() -> configparser.ConfigParser:
15
+ config = configparser.ConfigParser()
16
+ config[censys.common.config.DEFAULT] = censys.common.config.default_config
17
+ return config
18
+ censys.common.config.get_config = get_config_over
19
+ #--
20
+
21
+ from censys.search import CensysHosts
22
+ from censys.search import CensysCertificates
23
+ from censys.common.base import *
24
+ except ImportError:
25
+ print("Censys module not installed. Try 'pip install censys'")
26
+
27
+ misperrors = {'error': 'Error'}
28
+ moduleconfig = ['api_id', 'api_secret']
29
+ mispattributes = {'input': ['ip-src', 'ip-dst', 'domain', 'hostname', 'hostname|port', 'domain|ip', 'ip-dst|port', 'ip-src|port',
30
+ 'x509-fingerprint-md5', 'x509-fingerprint-sha1', 'x509-fingerprint-sha256'], 'format': 'misp_standard'}
31
+ moduleinfo = {
32
+ 'version': '0.1',
33
+ 'author': 'Loïc Fortemps',
34
+ 'description': 'An expansion module to enrich attributes in MISP by quering the censys.io API',
35
+ 'module-type': ['expansion', 'hover'],
36
+ 'name': 'Censys Enrich',
37
+ 'logo': '',
38
+ 'requirements': ['API credentials to censys.io'],
39
+ 'features': 'This module takes an IP, hostname or a certificate fingerprint and attempts to enrich it by querying the Censys API.',
40
+ 'references': ['https://www.censys.io'],
41
+ 'input': 'IP, domain or certificate fingerprint (md5, sha1 or sha256)',
42
+ 'output': 'MISP objects retrieved from censys, including open ports, ASN, Location of the IP, x509 details',
43
+ }
44
+
45
+ api_id = None
46
+ api_secret = None
47
+
48
+ def handler(q=False):
49
+ global api_id, api_secret
50
+ if q is False:
51
+ return False
52
+ request = json.loads(q)
53
+
54
+ if request.get('config'):
55
+ if (request['config'].get('api_id') is None) or (request['config'].get('api_secret') is None):
56
+ misperrors['error'] = "Censys API credentials are missing"
57
+ return misperrors
58
+ else:
59
+ misperrors['error'] = "Please provide config options"
60
+ return misperrors
61
+
62
+ api_id = request['config']['api_id']
63
+ api_secret = request['config']['api_secret']
64
+
65
+ if not request.get('attribute') or not check_input_attribute(request['attribute']):
66
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
67
+ attribute = request['attribute']
68
+ if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
69
+ return {'error': 'Unsupported attribute type.'}
70
+
71
+ attribute = MISPAttribute()
72
+ attribute.from_dict(**request['attribute'])
73
+ # Lists to accomodate multi-types attribute
74
+ types = list()
75
+ values = list()
76
+ results = list()
77
+
78
+ if "|" in attribute.type:
79
+ t_1, t_2 = attribute.type.split('|')
80
+ v_1, v_2 = attribute.value.split('|')
81
+ # We cannot use the port information
82
+ if t_2 == "port":
83
+ types.append(t_1)
84
+ values.append(v_1)
85
+ else:
86
+ types = [t_1, t_2]
87
+ values = [v_1, v_2]
88
+ else:
89
+ types.append(attribute.type)
90
+ values.append(attribute.value)
91
+
92
+ found = False
93
+ for t in types:
94
+ try:
95
+ value = values.pop(0)
96
+ # ip, ip-src or ip-dst
97
+ if t[:2] == "ip":
98
+ r = CensysHosts(api_id, api_secret).view(value)
99
+ results.append(parse_response(r, attribute))
100
+ found = True
101
+ elif t == 'domain' or t == "hostname":
102
+ # get ips
103
+ endpoint = CensysHosts(api_id, api_secret)
104
+ for r_list in endpoint.search(query=value, per_page=5, pages=1):
105
+ for r in r_list:
106
+ results.append(parse_response(r, attribute))
107
+ found = True
108
+ elif 'x509-fingerprint-sha256' in t:
109
+ # use api_v1 as Certificates endpoint in api_v2 doesn't yet provide all the details
110
+ r = CensysCertificates(api_id, api_secret).view(value)
111
+ results.append(parse_response(r, attribute))
112
+ found = True
113
+ except CensysException as e:
114
+ misperrors['error'] = "ERROR: param {} / response: {}".format(value, e)
115
+ return misperrors
116
+
117
+ if not found:
118
+ misperrors['error'] = "Nothing could be found on Censys"
119
+ return misperrors
120
+
121
+ return {'results': remove_duplicates(results)}
122
+
123
+
124
+ def parse_response(censys_output, attribute):
125
+ misp_event = MISPEvent()
126
+ misp_event.add_attribute(**attribute)
127
+ # Generic fields (for IP/Websites)
128
+ if censys_output.get('autonomous_system'):
129
+ cen_as = censys_output.get('autonomous_system')
130
+ asn_object = MISPObject('asn')
131
+ asn_object.add_attribute('asn', value=cen_as.get("asn"))
132
+ asn_object.add_attribute('description', value=cen_as.get('name'))
133
+ asn_object.add_attribute('subnet-announced', value=cen_as.get('routed_prefix'))
134
+ asn_object.add_attribute('country', value=cen_as.get('country_code'))
135
+ asn_object.add_reference(attribute.uuid, 'associated-to')
136
+ misp_event.add_object(**asn_object)
137
+
138
+ if censys_output.get('ip') and len(censys_output.get('services')): #"ports" in censys_output
139
+ ip_object = MISPObject('ip-port')
140
+ ip_object.add_attribute('ip', value=censys_output.get('ip'))
141
+ for serv in censys_output.get('services'):
142
+ if serv.get('port'):
143
+ ip_object.add_attribute('dst-port', value=serv.get('port'))
144
+ ip_object.add_reference(attribute.uuid, 'associated-to')
145
+ misp_event.add_object(**ip_object)
146
+
147
+ # We explore all ports to find https or ssh services
148
+ for serv in censys_output.get('services', []):
149
+ if not isinstance(serv, dict):
150
+ continue
151
+ if serv.get('service_name').lower() == 'http' and serv.get('certificate', None):
152
+ try:
153
+ cert = serv.get('certificate', None)
154
+ if cert:
155
+ # TODO switch to api_v2 once available
156
+ # use api_v1 as Certificates endpoint in api_v2 doesn't yet provide all the details
157
+ cert_details = CensysCertificates(api_id, api_secret).view(cert)
158
+ cert_obj = get_certificate_object(cert_details, attribute)
159
+ misp_event.add_object(**cert_obj)
160
+ except KeyError:
161
+ print("Error !")
162
+ if serv.get('ssh') and serv.get('service_name').lower() == 'ssh':
163
+ try:
164
+ cert = serv.get('ssh').get('server_host_key').get('fingerprint_sha256')
165
+ # TODO enable once the type is merged
166
+ # misp_event.add_attribute(type='hasshserver-sha256', value=cert['fingerprint_sha256'])
167
+ except KeyError:
168
+ pass
169
+
170
+ # Info from certificate query
171
+ if "parsed" in censys_output:
172
+ cert_obj = get_certificate_object(censys_output, attribute)
173
+ misp_event.add_object(**cert_obj)
174
+
175
+ # Location can be present for IP/Websites results
176
+ if "location" in censys_output:
177
+ loc_obj = MISPObject('geolocation')
178
+ loc = censys_output['location']
179
+ loc_obj.add_attribute('latitude', value=loc.get('coordinates', {}).get('latitude', None))
180
+ loc_obj.add_attribute('longitude', value=loc.get('coordinates', {}).get('longitude', None))
181
+ if 'city' in loc:
182
+ loc_obj.add_attribute('city', value=loc.get('city'))
183
+ loc_obj.add_attribute('country', value=loc.get('country'))
184
+ if 'postal_code' in loc:
185
+ loc_obj.add_attribute('zipcode', value=loc.get('postal_code'))
186
+ if 'province' in loc:
187
+ loc_obj.add_attribute('region', value=loc.get('province'))
188
+ loc_obj.add_reference(attribute.uuid, 'associated-to')
189
+ misp_event.add_object(**loc_obj)
190
+
191
+ event = json.loads(misp_event.to_json())
192
+ return {'Object': event.get('Object', []), 'Attribute': event.get('Attribute', [])}
193
+
194
+
195
+ # In case of multiple enrichment (ip and domain), we need to filter out similar objects
196
+ # TODO: make it more granular
197
+ def remove_duplicates(results):
198
+ # Only one enrichment was performed so no duplicate
199
+ if len(results) == 1:
200
+ return results[0]
201
+ else:
202
+ final_result = results[0]
203
+ for i,result in enumerate(results[1:]):
204
+ obj_l = results[i+1].get('Object', [])
205
+ for o2 in obj_l:
206
+ if o2['name'] == "asn":
207
+ key = "asn"
208
+ elif o2['name'] == "ip-port":
209
+ key = "ip"
210
+ elif o2['name'] == "x509":
211
+ key = "x509-fingerprint-sha256"
212
+ elif o2['name'] == "geolocation":
213
+ key = "latitude"
214
+ if not check_if_present(o2, key, final_result.get('Object', [])):
215
+ final_result['Object'].append(o2)
216
+
217
+ return final_result
218
+
219
+
220
+ def check_if_present(object, attribute_name, list_objects):
221
+ """
222
+ Assert if a given object is present in the list.
223
+
224
+ This function check if object (json format) is present in list_objects
225
+ using attribute_name for the matching
226
+ """
227
+ for o in list_objects:
228
+ # We first look for a match on the name
229
+ if o['name'] == object['name']:
230
+ for attr in object['Attribute']:
231
+ # Within the attributes, we look for the one to compare
232
+ if attr['type'] == attribute_name:
233
+ # Then we check the attributes of the other object and look for a match
234
+ for attr2 in o['Attribute']:
235
+ if attr2['type'] == attribute_name and attr2['value'] == attr['value']:
236
+ return True
237
+
238
+ return False
239
+
240
+
241
+ def get_certificate_object(cert, attribute):
242
+ parsed = cert['parsed']
243
+ cert_object = MISPObject('x509')
244
+ cert_object.add_attribute('x509-fingerprint-sha256', value=parsed['fingerprint_sha256'])
245
+ cert_object.add_attribute('x509-fingerprint-sha1', value=parsed['fingerprint_sha1'])
246
+ cert_object.add_attribute('x509-fingerprint-md5', value=parsed['fingerprint_md5'])
247
+ cert_object.add_attribute('serial-number', value=parsed['serial_number'])
248
+ cert_object.add_attribute('version', value=parsed['version'])
249
+ cert_object.add_attribute('subject', value=parsed['subject_dn'])
250
+ cert_object.add_attribute('issuer', value=parsed['issuer_dn'])
251
+ cert_object.add_attribute('validity-not-before', value=isoparse(parsed['validity']['start']))
252
+ cert_object.add_attribute('validity-not-after', value=isoparse(parsed['validity']['end']))
253
+ cert_object.add_attribute('self_signed', value=parsed['signature']['self_signed'])
254
+ cert_object.add_attribute('signature_algorithm', value=parsed['signature']['signature_algorithm']['name'])
255
+
256
+ cert_object.add_attribute('pubkey-info-algorithm', value=parsed['subject_key_info']['key_algorithm']['name'])
257
+
258
+ if 'rsa_public_key' in parsed['subject_key_info']:
259
+ pub_key = parsed['subject_key_info']['rsa_public_key']
260
+ cert_object.add_attribute('pubkey-info-size', value=pub_key['length'])
261
+ cert_object.add_attribute('pubkey-info-exponent', value=pub_key['exponent'])
262
+ hex_mod = codecs.encode(base64.b64decode(pub_key['modulus']), 'hex').decode()
263
+ cert_object.add_attribute('pubkey-info-modulus', value=hex_mod)
264
+
265
+ if "extensions" in parsed and "subject_alt_name" in parsed["extensions"]:
266
+ san = parsed["extensions"]["subject_alt_name"]
267
+ if "dns_names" in san:
268
+ for dns in san['dns_names']:
269
+ cert_object.add_attribute('dns_names', value=dns)
270
+ if "ip_addresses" in san:
271
+ for ip in san['ip_addresses']:
272
+ cert_object.add_attribute('ip', value=ip)
273
+
274
+ if "raw" in cert:
275
+ cert_object.add_attribute('raw-base64', value=cert['raw'])
276
+
277
+ cert_object.add_reference(attribute.uuid, 'associated-to')
278
+ return cert_object
279
+
280
+
281
+ def introspection():
282
+ return mispattributes
283
+
284
+
285
+ def version():
286
+ moduleinfo['config'] = moduleconfig
287
+ return moduleinfo
@@ -0,0 +1,85 @@
1
+ import pypdns
2
+ from . import check_input_attribute, standard_error_message
3
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
4
+
5
+ mispattributes = {'input': ['hostname', 'domain', 'ip-src', 'ip-dst', 'ip-src|port', 'ip-dst|port'], 'format': 'misp_standard'}
6
+ moduleinfo = {
7
+ 'version': '0.2',
8
+ 'author': 'Alexandre Dulaunoy',
9
+ 'description': 'Module to access CIRCL Passive DNS.',
10
+ 'module-type': ['expansion', 'hover'],
11
+ 'name': 'CIRCL Passive DNS',
12
+ 'logo': 'passivedns.png',
13
+ 'requirements': ['pypdns: Passive DNS python library', 'A CIRCL passive DNS account with username & password'],
14
+ 'features': 'This module takes a hostname, domain or ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive DNS REST API to get the asssociated passive dns entries and return them as MISP objects.\n\nTo make it work a username and a password are thus required to authenticate to the CIRCL Passive DNS API.',
15
+ 'references': ['https://www.circl.lu/services/passive-dns/', 'https://datatracker.ietf.org/doc/draft-dulaunoy-dnsop-passive-dns-cof/'],
16
+ 'input': 'Hostname, domain, or ip-address attribute.',
17
+ 'output': '',
18
+ 'ouput': 'Passive DNS objects related to the input attribute.',
19
+ }
20
+ moduleconfig = ['username', 'password']
21
+
22
+
23
+ class PassiveDNSParser:
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.pdns = pypdns.PyPDNS(basic_auth=authentication)
30
+
31
+ def get_results(self):
32
+ if hasattr(self, 'result'):
33
+ return self.result
34
+ event = self.misp_event.to_dict()
35
+ results = {key: event[key] for key in ('Attribute', 'Object')}
36
+ return {'results': results}
37
+
38
+ def parse(self):
39
+ value = self.attribute.value.split('|')[0] if '|' in self.attribute.type else self.attribute.value
40
+
41
+ try:
42
+ results = self.pdns.query(value)
43
+ except Exception:
44
+ self.result = {'error': 'There is an authentication error, please make sure you supply correct credentials.'}
45
+ return
46
+
47
+ if not results:
48
+ self.result = {'error': 'Not found'}
49
+ return
50
+
51
+ mapping = {'count': 'counter', 'origin': 'text',
52
+ 'time_first': 'datetime', 'rrtype': 'text',
53
+ 'rrname': 'text', 'rdata': 'text',
54
+ 'time_last': 'datetime'}
55
+ for result in results:
56
+ pdns_object = MISPObject('passive-dns')
57
+ for relation, attribute_type in mapping.items():
58
+ pdns_object.add_attribute(relation, type=attribute_type, value=result[relation])
59
+ pdns_object.add_reference(self.attribute.uuid, 'associated-to')
60
+ self.misp_event.add_object(**pdns_object)
61
+
62
+
63
+ def dict_handler(request: dict):
64
+ if not request.get('config'):
65
+ return {'error': 'CIRCL Passive DNS authentication is missing.'}
66
+ if not request['config'].get('username') or not request['config'].get('password'):
67
+ return {'error': 'CIRCL Passive DNS authentication is incomplete, please provide your username and password.'}
68
+ authentication = (request['config']['username'], request['config']['password'])
69
+ if not request.get('attribute') or not check_input_attribute(request['attribute']):
70
+ return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
71
+ attribute = request['attribute']
72
+ if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
73
+ return {'error': 'Unsupported attribute type.'}
74
+ pdns_parser = PassiveDNSParser(attribute, authentication)
75
+ pdns_parser.parse()
76
+ return pdns_parser.get_results()
77
+
78
+
79
+ def introspection():
80
+ return mispattributes
81
+
82
+
83
+ def version():
84
+ moduleinfo['config'] = moduleconfig
85
+ return moduleinfo