misp-modules 2.4.196__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misp_modules/__init__.py +379 -0
- misp_modules/helpers/__init__.py +1 -0
- misp_modules/helpers/cache.py +84 -0
- misp_modules/lib/__init__.py +4 -0
- misp_modules/lib/_vmray/__init__.py +0 -0
- misp_modules/lib/_vmray/parser.py +1417 -0
- misp_modules/lib/_vmray/rest_api.py +148 -0
- misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
- misp_modules/lib/cof2misp/__init__.py +0 -0
- misp_modules/lib/cof2misp/cof.py +165 -0
- misp_modules/lib/joe_mapping.py +114 -0
- misp_modules/lib/joe_parser.py +573 -0
- misp_modules/lib/lastline_api.py +841 -0
- misp_modules/lib/qintel_helper.py +263 -0
- misp_modules/lib/stix2misp.py +2080 -0
- misp_modules/lib/stix2misp_mapping.py +460 -0
- misp_modules/lib/synonymsToTagNames.json +1 -0
- misp_modules/lib/vt_graph_parser/__init__.py +8 -0
- misp_modules/lib/vt_graph_parser/errors.py +20 -0
- misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
- misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
- misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
- misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
- misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
- misp_modules/modules/__init__.py +4 -0
- misp_modules/modules/action_mod/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/utils.py +70 -0
- misp_modules/modules/action_mod/mattermost.py +113 -0
- misp_modules/modules/action_mod/slack.py +96 -0
- misp_modules/modules/action_mod/testaction.py +68 -0
- misp_modules/modules/expansion/__init__.py +36 -0
- misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
- misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
- misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
- misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
- misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
- misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
- misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
- misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
- misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
- misp_modules/modules/expansion/abuseipdb.py +146 -0
- misp_modules/modules/expansion/apiosintds.py +365 -0
- misp_modules/modules/expansion/apivoid.py +133 -0
- misp_modules/modules/expansion/assemblyline_query.py +179 -0
- misp_modules/modules/expansion/assemblyline_submit.py +100 -0
- misp_modules/modules/expansion/backscatter_io.py +84 -0
- misp_modules/modules/expansion/btc_scam_check.py +54 -0
- misp_modules/modules/expansion/btc_steroids.py +238 -0
- misp_modules/modules/expansion/censys_enrich.py +287 -0
- misp_modules/modules/expansion/circl_passivedns.py +85 -0
- misp_modules/modules/expansion/circl_passivessl.py +113 -0
- misp_modules/modules/expansion/clamav.py +129 -0
- misp_modules/modules/expansion/cluster25_expand.py +239 -0
- misp_modules/modules/expansion/countrycode.py +69 -0
- misp_modules/modules/expansion/cpe.py +140 -0
- misp_modules/modules/expansion/crowdsec.py +235 -0
- misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
- misp_modules/modules/expansion/cuckoo_submit.py +161 -0
- misp_modules/modules/expansion/cve.py +57 -0
- misp_modules/modules/expansion/cve_advanced.py +185 -0
- misp_modules/modules/expansion/cytomic_orion.py +196 -0
- misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
- misp_modules/modules/expansion/dns.py +71 -0
- misp_modules/modules/expansion/docx_enrich.py +71 -0
- misp_modules/modules/expansion/domaintools.py +290 -0
- misp_modules/modules/expansion/eql.py +91 -0
- misp_modules/modules/expansion/eupi.py +86 -0
- misp_modules/modules/expansion/extract_url_components.py +83 -0
- misp_modules/modules/expansion/farsight_passivedns.py +243 -0
- misp_modules/modules/expansion/geoip_asn.py +75 -0
- misp_modules/modules/expansion/geoip_city.py +75 -0
- misp_modules/modules/expansion/geoip_country.py +73 -0
- misp_modules/modules/expansion/google_safe_browsing.py +86 -0
- misp_modules/modules/expansion/google_search.py +57 -0
- misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
- misp_modules/modules/expansion/greynoise.py +346 -0
- misp_modules/modules/expansion/hashdd.py +53 -0
- misp_modules/modules/expansion/hashlookup.py +118 -0
- misp_modules/modules/expansion/hibp.py +62 -0
- misp_modules/modules/expansion/html_to_markdown.py +63 -0
- misp_modules/modules/expansion/hyasinsight.py +881 -0
- misp_modules/modules/expansion/intel471.py +73 -0
- misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
- misp_modules/modules/expansion/ip2locationio.py +90 -0
- misp_modules/modules/expansion/ipasn.py +70 -0
- misp_modules/modules/expansion/ipinfo.py +112 -0
- misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
- misp_modules/modules/expansion/iprep.py +98 -0
- misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
- misp_modules/modules/expansion/joesandbox_query.py +91 -0
- misp_modules/modules/expansion/joesandbox_submit.py +147 -0
- misp_modules/modules/expansion/lastline_query.py +148 -0
- misp_modules/modules/expansion/lastline_submit.py +180 -0
- misp_modules/modules/expansion/macaddress_io.py +130 -0
- misp_modules/modules/expansion/macvendors.py +55 -0
- misp_modules/modules/expansion/malshare_upload.py +107 -0
- misp_modules/modules/expansion/malwarebazaar.py +67 -0
- misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
- misp_modules/modules/expansion/mmdb_lookup.py +138 -0
- misp_modules/modules/expansion/module.py.skeleton +44 -0
- misp_modules/modules/expansion/mwdb.py +152 -0
- misp_modules/modules/expansion/ocr_enrich.py +74 -0
- misp_modules/modules/expansion/ods_enrich.py +71 -0
- misp_modules/modules/expansion/odt_enrich.py +61 -0
- misp_modules/modules/expansion/onyphe.py +243 -0
- misp_modules/modules/expansion/onyphe_full.py +387 -0
- misp_modules/modules/expansion/otx.py +167 -0
- misp_modules/modules/expansion/passive_ssh.py +150 -0
- misp_modules/modules/expansion/passivetotal.py +358 -0
- misp_modules/modules/expansion/pdf_enrich.py +58 -0
- misp_modules/modules/expansion/pptx_enrich.py +65 -0
- misp_modules/modules/expansion/qintel_qsentry.py +229 -0
- misp_modules/modules/expansion/qrcode.py +99 -0
- misp_modules/modules/expansion/ransomcoindb.py +82 -0
- misp_modules/modules/expansion/rbl.py +126 -0
- misp_modules/modules/expansion/recordedfuture.py +544 -0
- misp_modules/modules/expansion/reversedns.py +77 -0
- misp_modules/modules/expansion/securitytrails.py +571 -0
- misp_modules/modules/expansion/shodan.py +244 -0
- misp_modules/modules/expansion/sigma_queries.py +61 -0
- misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
- misp_modules/modules/expansion/sigmf_expand.py +303 -0
- misp_modules/modules/expansion/socialscan.py +108 -0
- misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
- misp_modules/modules/expansion/sourcecache.py +57 -0
- misp_modules/modules/expansion/stairwell.py +156 -0
- misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
- misp_modules/modules/expansion/threatcrowd.py +170 -0
- misp_modules/modules/expansion/threatfox.py +75 -0
- misp_modules/modules/expansion/threatminer.py +152 -0
- misp_modules/modules/expansion/triage_submit.py +119 -0
- misp_modules/modules/expansion/trustar_enrich.py +235 -0
- misp_modules/modules/expansion/urlhaus.py +164 -0
- misp_modules/modules/expansion/urlscan.py +269 -0
- misp_modules/modules/expansion/variotdbs.py +133 -0
- misp_modules/modules/expansion/virustotal.py +311 -0
- misp_modules/modules/expansion/virustotal_public.py +272 -0
- misp_modules/modules/expansion/virustotal_upload.py +91 -0
- misp_modules/modules/expansion/vmray_submit.py +161 -0
- misp_modules/modules/expansion/vmware_nsx.py +628 -0
- misp_modules/modules/expansion/vulndb.py +292 -0
- misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
- misp_modules/modules/expansion/vulners.py +81 -0
- misp_modules/modules/expansion/vysion.py +221 -0
- misp_modules/modules/expansion/whois.py +72 -0
- misp_modules/modules/expansion/whoisfreaks.py +232 -0
- misp_modules/modules/expansion/wiki.py +58 -0
- misp_modules/modules/expansion/xforceexchange.py +189 -0
- misp_modules/modules/expansion/xlsx_enrich.py +63 -0
- misp_modules/modules/expansion/yara_query.py +70 -0
- misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
- misp_modules/modules/expansion/yeti.py +196 -0
- misp_modules/modules/export_mod/__init__.py +3 -0
- misp_modules/modules/export_mod/cef_export.py +93 -0
- misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
- misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
- misp_modules/modules/export_mod/goamlexport.py +255 -0
- misp_modules/modules/export_mod/liteexport.py +97 -0
- misp_modules/modules/export_mod/mass_eql_export.py +106 -0
- misp_modules/modules/export_mod/nexthinkexport.py +131 -0
- misp_modules/modules/export_mod/osqueryexport.py +125 -0
- misp_modules/modules/export_mod/pdfexport.py +107 -0
- misp_modules/modules/export_mod/testexport.py +73 -0
- misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
- misp_modules/modules/export_mod/threat_connect_export.py +125 -0
- misp_modules/modules/export_mod/virustotal_collections.py +141 -0
- misp_modules/modules/export_mod/vt_graph.py +120 -0
- misp_modules/modules/export_mod/yara_export.py +290 -0
- misp_modules/modules/import_mod/__init__.py +22 -0
- misp_modules/modules/import_mod/cof2misp.py +264 -0
- misp_modules/modules/import_mod/csvimport.py +319 -0
- misp_modules/modules/import_mod/cuckooimport.py +749 -0
- misp_modules/modules/import_mod/email_import.py +294 -0
- misp_modules/modules/import_mod/goamlimport.py +188 -0
- misp_modules/modules/import_mod/import_blueprint.py +93 -0
- misp_modules/modules/import_mod/joe_import.py +74 -0
- misp_modules/modules/import_mod/lastline_import.py +160 -0
- misp_modules/modules/import_mod/mispjson.py +73 -0
- misp_modules/modules/import_mod/ocr.py +122 -0
- misp_modules/modules/import_mod/openiocimport.py +99 -0
- misp_modules/modules/import_mod/taxii21.py +383 -0
- misp_modules/modules/import_mod/testimport.py +73 -0
- misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
- misp_modules/modules/import_mod/url_import.py +97 -0
- misp_modules/modules/import_mod/vmray_import.py +96 -0
- misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
- misp_modules-2.4.196.dist-info/LICENSE +661 -0
- misp_modules-2.4.196.dist-info/METADATA +281 -0
- misp_modules-2.4.196.dist-info/RECORD +194 -0
- misp_modules-2.4.196.dist-info/WHEEL +4 -0
- misp_modules-2.4.196.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,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
|