mihari 1.0.1 → 1.3.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -1
- data/README.md +42 -25
- data/docker/Dockerfile +2 -2
- data/lib/mihari.rb +3 -1
- data/lib/mihari/alert_viewer.rb +12 -8
- data/lib/mihari/analyzers/binaryedge.rb +1 -1
- data/lib/mihari/analyzers/censys.rb +1 -1
- data/lib/mihari/analyzers/circl.rb +1 -1
- data/lib/mihari/analyzers/onyphe.rb +1 -1
- data/lib/mihari/analyzers/otx.rb +74 -0
- data/lib/mihari/analyzers/passive_dns.rb +2 -1
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/securitytrails.rb +1 -1
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +1 -1
- data/lib/mihari/analyzers/shodan.rb +1 -1
- data/lib/mihari/analyzers/spyse.rb +77 -0
- data/lib/mihari/analyzers/virustotal.rb +1 -1
- data/lib/mihari/analyzers/zoomeye.rb +1 -1
- data/lib/mihari/cli.rb +30 -4
- data/lib/mihari/config.rb +4 -0
- data/lib/mihari/configurable.rb +1 -1
- data/lib/mihari/database.rb +33 -10
- data/lib/mihari/emitters/{sqlite.rb → database.rb} +1 -1
- data/lib/mihari/emitters/misp.rb +1 -1
- data/lib/mihari/emitters/slack.rb +3 -1
- data/lib/mihari/emitters/the_hive.rb +1 -1
- data/lib/mihari/notifiers/slack.rb +3 -0
- data/lib/mihari/retriable.rb +2 -2
- data/lib/mihari/slack_monkeypatch.rb +16 -0
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +7 -4
- data/renovate.json +5 -0
- metadata +60 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d6b5d42dc4fbe937cc101f7dbdfa1491104f2b6f70fdf690b2ad51db02304c5
|
4
|
+
data.tar.gz: 32b5df5f6970d6aaf6f9d5c57ca9bc86d8e43f0298f22c6c9e811cf60a6d1089
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16cdfd458c0d464eb618fc5aae8386c1cb37fd554fd6aba275323cb539a08916f3cd855e4e5683085e03f206debe879af6f2e3c7a42b1cd0b2a6395d86e3f55a
|
7
|
+
data.tar.gz: 93f9e9e2165e79ef6602d1628b5689e0993ae4d88605db4f6aa4ff79dc6a21d25437997a1402f76ce3a5e2e9155ad68a441a8cc8351b54c5f4d1c3a72eb82df4
|
data/.travis.yml
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
---
|
2
|
-
sudo: false
|
3
2
|
language: ruby
|
4
3
|
cache: bundler
|
4
|
+
services:
|
5
|
+
- postgresql
|
5
6
|
rvm:
|
6
7
|
- 2.6
|
7
8
|
- 2.7
|
9
|
+
env:
|
10
|
+
- DATABASE=":memory:"
|
11
|
+
- DATABASE="postgresql://postgres@0.0.0.0:5432/travis_ci_test"
|
8
12
|
before_install: gem install bundler -v 2.1
|
13
|
+
before_script: psql -c 'create database travis_ci_test;' -U postgres
|
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Mihari is a helper to run queries & manage results continuously. Mihari can be u
|
|
11
11
|
## How it works
|
12
12
|
|
13
13
|
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs and hashes) from the results.
|
14
|
-
- Mihari checks whether a DB (SQLite3) contains the artifacts or not.
|
14
|
+
- Mihari checks whether a DB (SQLite3 or PostgreSQL) contains the artifacts or not.
|
15
15
|
- If it doesn't contain the artifacts:
|
16
16
|
- Mihari creates an alert on TheHive. (Optional)
|
17
17
|
- Mihari sends a notification to Slack. (Optional)
|
@@ -33,6 +33,17 @@ Mihari is a helper to run queries & manage results continuously. Mihari can be u
|
|
33
33
|
|
34
34
|

|
35
35
|
|
36
|
+
## Requirements
|
37
|
+
|
38
|
+
- Ruby 2.6+
|
39
|
+
- SQLite3
|
40
|
+
- libpq
|
41
|
+
|
42
|
+
```bash
|
43
|
+
# For Debian / Ubuntu
|
44
|
+
apt-get install sqlite3 libsqlite3-dev libpq-dev
|
45
|
+
```
|
46
|
+
|
36
47
|
## Installation
|
37
48
|
|
38
49
|
```bash
|
@@ -56,9 +67,11 @@ Mihari supports the following services by default.
|
|
56
67
|
- [DN Pedia](https://dnpedia.com/)
|
57
68
|
- [dnstwister](https://dnstwister.report/)
|
58
69
|
- [Onyphe](https://onyphe.io)
|
70
|
+
- [OTX](https://otx.alienvault.com/)
|
59
71
|
- [PassiveTotal](https://community.riskiq.com/)
|
60
72
|
- [SecurityTrails](https://securitytrails.com/)
|
61
73
|
- [Shodan](https://shodan.io)
|
74
|
+
- [Spyse](https://spyse.com)
|
62
75
|
- [urlscan.io](https://urlscan.io)
|
63
76
|
- [VirusTotal](http://virustotal.com)
|
64
77
|
- [ZoomEye](https://zoomeye.org)
|
@@ -78,6 +91,7 @@ Commands:
|
|
78
91
|
mihari http_hash # Cross search with search engines by a hash of an HTTP response (SHA256, MD5 and MurmurHash3)
|
79
92
|
mihari import_from_json # Give a JSON input via STDIN
|
80
93
|
mihari onyphe [QUERY] # Onyphe datascan search by a query
|
94
|
+
mihari otx [IP|DOMAIN] # OTX lookup by an IP or domain
|
81
95
|
mihari passive_dns [IP|DOMAIN] # Cross search with passive DNS services by an ip or domain
|
82
96
|
mihari passive_ssl [SHA1] # Cross search with passive SSL services by an SHA1 certificate fingerprint
|
83
97
|
mihari passivetotal [IP|DOMAIN|EMAIL|SHA1] # PassiveTotal lookup by an ip, domain, email or SHA1 certificate fingerprint
|
@@ -86,6 +100,7 @@ Commands:
|
|
86
100
|
mihari securitytrails [IP|DOMAIN|EMAIL] # SecurityTrails lookup by an ip, domain or email
|
87
101
|
mihari securitytrails_domain_feed [REGEXP] # SecurityTrails new domain feed search by a regexp
|
88
102
|
mihari shodan [QUERY] # Shodan host search by a query
|
103
|
+
mihari spyse [QUERY] # Spyse search by a query
|
89
104
|
mihari ssh_fingerprint [FINGERPRINT] # Cross search with search engines by an SSH fingerprint (e.g. dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0)
|
90
105
|
mihari status # Show the current configuration status
|
91
106
|
mihari urlscan [QUERY] # urlscan search by a given query
|
@@ -105,7 +120,7 @@ You can get aggregated results by using the following commands.
|
|
105
120
|
|
106
121
|
| Command | Desc. |
|
107
122
|
|-----------------|---------------------------------------------------------------------------------------------------------|
|
108
|
-
| passive_dns | Passive DNS lookup with CIRCL passive DNS, PassiveTotal, Pulsedive, SecurityTrails and VirusTotal
|
123
|
+
| passive_dns | Passive DNS lookup with CIRCL passive DNS, OTX, PassiveTotal, Pulsedive, SecurityTrails and VirusTotal |
|
109
124
|
| passive_ssl | Passive SSL lookup with CIRCL passive SSL and PassiveTotal |
|
110
125
|
| reverse_whois | Revese Whois lookup with PassiveTotal and SecurityTrails |
|
111
126
|
| http_hash | HTTP response hash lookup with BinaryEdge(SHA256), Censys(SHA256), Onyphpe(MD5) and Shodan(MurmurHash3) |
|
@@ -189,29 +204,31 @@ The input is a JSON data should have `title`, `description` and `artifacts` key.
|
|
189
204
|
|
190
205
|
Configuration can be done via environment variables or a YAML file.
|
191
206
|
|
192
|
-
| Key | Description
|
193
|
-
|
194
|
-
| DATABASE | A path to the SQLite database
|
195
|
-
| BINARYEDGE_API_KEY | BinaryEdge API key
|
196
|
-
| CENSYS_ID | Censys API ID
|
197
|
-
| CENSYS_SECRET | Censys secret
|
198
|
-
| CIRCL_PASSIVE_PASSWORD | CIRCL passive DNS/SSL password
|
199
|
-
| CIRCL_PASSIVE_USERNAME | CIRCL passive DNS/SSL username
|
200
|
-
| MISP_API_ENDPOINT | MISP URL
|
201
|
-
| MISP_API_KEY | MISP API key
|
202
|
-
| ONYPHE_API_KEY | Onyphe API key
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
207
|
+
| Key | Description | Default |
|
208
|
+
|------------------------|-------------------------------------------------------------------------------------------------|-------------|
|
209
|
+
| DATABASE | A path to the SQLite database or a DB URL (e.g. `postgres://postgres:pass@db.host:5432/somedb`) | `mihari.db` |
|
210
|
+
| BINARYEDGE_API_KEY | BinaryEdge API key | |
|
211
|
+
| CENSYS_ID | Censys API ID | |
|
212
|
+
| CENSYS_SECRET | Censys secret | |
|
213
|
+
| CIRCL_PASSIVE_PASSWORD | CIRCL passive DNS/SSL password | |
|
214
|
+
| CIRCL_PASSIVE_USERNAME | CIRCL passive DNS/SSL username | |
|
215
|
+
| MISP_API_ENDPOINT | MISP URL | |
|
216
|
+
| MISP_API_KEY | MISP API key | |
|
217
|
+
| ONYPHE_API_KEY | Onyphe API key | |
|
218
|
+
| OTX_API_KEY | OTX API key | |
|
219
|
+
| PASSIVETOTAL_API_KEY | PassiveTotal API key | |
|
220
|
+
| PASSIVETOTAL_USERNAME | PassiveTotal username | |
|
221
|
+
| PULSEDIVE_API_KEY | Pulsedive API key | |
|
222
|
+
| SECURITYTRAILS_API_KEY | SecurityTrails API key | |
|
223
|
+
| SHODAN_API_KEY | Shodan API key | |
|
224
|
+
| SLACK_CHANNEL | Slack channel name | `#general` |
|
225
|
+
| SLACK_WEBHOOK_URL | Slack Webhook URL | |
|
226
|
+
| SPYSE_API_KEY | Spyse API key | |
|
227
|
+
| THEHIVE_API_ENDPOINT | TheHive URL | |
|
228
|
+
| THEHIVE_API_KEY | TheHive API key | |
|
229
|
+
| VIRUSTOTAL_API_KEY | VirusTotal API key | |
|
230
|
+
| ZOOMEYE_PASSWORD | ZoomEye password | |
|
231
|
+
| ZOOMEYE_USERNAMME | ZoomEye username | |
|
215
232
|
|
216
233
|
Instead of using environment variables, you can use a YAML file for configuration.
|
217
234
|
|
data/docker/Dockerfile
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
FROM ruby:2.
|
2
|
-
RUN apk --no-cache add git build-base ruby-dev \
|
1
|
+
FROM ruby:2.7-alpine3.10
|
2
|
+
RUN apk --no-cache add git build-base ruby-dev sqlite-dev postgresql-dev \
|
3
3
|
&& cd /tmp/ \
|
4
4
|
&& git clone https://github.com/ninoseki/mihari.git \
|
5
5
|
&& cd mihari \
|
data/lib/mihari.rb
CHANGED
@@ -50,11 +50,13 @@ require "mihari/analyzers/crtsh"
|
|
50
50
|
require "mihari/analyzers/dnpedia"
|
51
51
|
require "mihari/analyzers/dnstwister"
|
52
52
|
require "mihari/analyzers/onyphe"
|
53
|
+
require "mihari/analyzers/otx"
|
53
54
|
require "mihari/analyzers/passivetotal"
|
54
55
|
require "mihari/analyzers/pulsedive"
|
55
56
|
require "mihari/analyzers/securitytrails_domain_feed"
|
56
57
|
require "mihari/analyzers/securitytrails"
|
57
58
|
require "mihari/analyzers/shodan"
|
59
|
+
require "mihari/analyzers/spyse"
|
58
60
|
require "mihari/analyzers/urlscan"
|
59
61
|
require "mihari/analyzers/virustotal"
|
60
62
|
require "mihari/analyzers/zoomeye"
|
@@ -71,9 +73,9 @@ require "mihari/notifiers/slack"
|
|
71
73
|
require "mihari/notifiers/exception_notifier"
|
72
74
|
|
73
75
|
require "mihari/emitters/base"
|
76
|
+
require "mihari/emitters/database"
|
74
77
|
require "mihari/emitters/misp"
|
75
78
|
require "mihari/emitters/slack"
|
76
|
-
require "mihari/emitters/sqlite"
|
77
79
|
require "mihari/emitters/stdout"
|
78
80
|
require "mihari/emitters/the_hive"
|
79
81
|
|
data/lib/mihari/alert_viewer.rb
CHANGED
@@ -2,17 +2,21 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
class AlertViewer
|
5
|
-
|
5
|
+
def list(title: nil, source: nil, tag: nil, limit: 5)
|
6
|
+
limit = limit.to_i
|
7
|
+
raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
relation = Alert.includes(:tags, :artifacts)
|
10
|
+
relation = relation.where(title: title) if title
|
11
|
+
relation = relation.where(source: source) if source
|
12
|
+
relation = relation.where(tags: { name: tag } ) if tag
|
11
13
|
|
12
|
-
|
13
|
-
alerts = Alert.order(id: :desc).limit(limit).includes(:tags, :artifacts)
|
14
|
+
alerts = relation.limit(limit).order(id: :desc)
|
14
15
|
alerts.map do |alert|
|
15
|
-
AlertSerializer.new(alert).as_json
|
16
|
+
json = AlertSerializer.new(alert).as_json
|
17
|
+
json[:artifacts] = (json.dig(:artifacts) || []).map { |artifact_| artifact_.dig(:data) }
|
18
|
+
json[:tags] = (json.dig(:tags) || []).map { |tag_| tag_.dig(:name) }
|
19
|
+
json
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "otx_ruby"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Analyzers
|
7
|
+
class OTX < Base
|
8
|
+
attr_reader :query
|
9
|
+
attr_reader :type
|
10
|
+
|
11
|
+
attr_reader :title
|
12
|
+
attr_reader :description
|
13
|
+
attr_reader :tags
|
14
|
+
|
15
|
+
def initialize(query, title: nil, description: nil, tags: [])
|
16
|
+
super()
|
17
|
+
|
18
|
+
@query = query
|
19
|
+
@type = TypeChecker.type(query)
|
20
|
+
|
21
|
+
@title = title || "OTX lookup"
|
22
|
+
@description = description || "query = #{query}"
|
23
|
+
@tags = tags
|
24
|
+
end
|
25
|
+
|
26
|
+
def artifacts
|
27
|
+
lookup || []
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def config_keys
|
33
|
+
%w(otx_api_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
def domain_client
|
37
|
+
@domain_client ||= ::OTX::Domain.new(Mihari.config.otx_api_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ip_client
|
41
|
+
@ip_client ||= ::OTX::IP.new(Mihari.config.otx_api_key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_type?
|
45
|
+
%w(ip domain).include? type
|
46
|
+
end
|
47
|
+
|
48
|
+
def lookup
|
49
|
+
case type
|
50
|
+
when "domain"
|
51
|
+
domain_lookup
|
52
|
+
when "ip"
|
53
|
+
ip_lookup
|
54
|
+
else
|
55
|
+
raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def domain_lookup
|
60
|
+
records = domain_client.get_passive_dns(query)
|
61
|
+
records.map do |record|
|
62
|
+
record.address if record.record_type == "A"
|
63
|
+
end.compact.uniq
|
64
|
+
end
|
65
|
+
|
66
|
+
def ip_lookup
|
67
|
+
records = ip_client.get_passive_dns(query)
|
68
|
+
records.map do |record|
|
69
|
+
record.hostname if record.record_type == "A"
|
70
|
+
end.compact.uniq
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -14,6 +14,7 @@ module Mihari
|
|
14
14
|
|
15
15
|
ANALYZERS = [
|
16
16
|
Mihari::Analyzers::CIRCL,
|
17
|
+
Mihari::Analyzers::OTX,
|
17
18
|
Mihari::Analyzers::PassiveTotal,
|
18
19
|
Mihari::Analyzers::Pulsedive,
|
19
20
|
Mihari::Analyzers::SecurityTrails,
|
@@ -55,7 +56,7 @@ module Mihari
|
|
55
56
|
analyzer.artifacts
|
56
57
|
rescue ArgumentError, InvalidInputError => _e
|
57
58
|
nil
|
58
|
-
rescue ::PassiveCIRCL::Error, ::PassiveTotal::Error, ::Pulsedive::ResponseError, ::SecurityTrails::Error, ::VirusTotal::Error => _e
|
59
|
+
rescue Faraday::Error, ::PassiveCIRCL::Error, ::PassiveTotal::Error, ::Pulsedive::ResponseError, ::SecurityTrails::Error, ::VirusTotal::Error => _e
|
59
60
|
nil
|
60
61
|
end
|
61
62
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spyse"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Mihari
|
7
|
+
module Analyzers
|
8
|
+
class Spyse < Base
|
9
|
+
attr_reader :query
|
10
|
+
attr_reader :type
|
11
|
+
|
12
|
+
attr_reader :title
|
13
|
+
attr_reader :description
|
14
|
+
attr_reader :tags
|
15
|
+
|
16
|
+
def initialize(query, title: nil, description: nil, tags: [], type: "domain")
|
17
|
+
super()
|
18
|
+
|
19
|
+
@query = query
|
20
|
+
|
21
|
+
@title = title || "Spyse lookup"
|
22
|
+
@description = description || "query = #{query}"
|
23
|
+
@tags = tags
|
24
|
+
@type = type
|
25
|
+
end
|
26
|
+
|
27
|
+
def artifacts
|
28
|
+
lookup || []
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def search_params
|
34
|
+
@search_params ||= JSON.parse(query)
|
35
|
+
end
|
36
|
+
|
37
|
+
def config_keys
|
38
|
+
%w(spyse_api_key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def api
|
42
|
+
@api ||= ::Spyse::API.new(Mihari.config.spyse_api_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_type?
|
46
|
+
%w(ip domain cert).include? type
|
47
|
+
end
|
48
|
+
|
49
|
+
def domain_lookup
|
50
|
+
res = api.domain.search(search_params, limit: 100)
|
51
|
+
items = res.dig("data", "items") || []
|
52
|
+
items.map do |item|
|
53
|
+
item.dig("name")
|
54
|
+
end.uniq.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def ip_lookup
|
58
|
+
res = api.ip.search(search_params, limit: 100)
|
59
|
+
items = res.dig("data", "items") || []
|
60
|
+
items.map do |item|
|
61
|
+
item.dig("ip")
|
62
|
+
end.uniq.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
def lookup
|
66
|
+
case type
|
67
|
+
when "domain"
|
68
|
+
domain_lookup
|
69
|
+
when "ip"
|
70
|
+
ip_lookup
|
71
|
+
else
|
72
|
+
raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/mihari/cli.rb
CHANGED
@@ -164,6 +164,27 @@ module Mihari
|
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
167
|
+
desc "otx [IP|DOMAIN]", "OTX lookup by an IP or domain"
|
168
|
+
method_option :title, type: :string, desc: "title"
|
169
|
+
method_option :description, type: :string, desc: "description"
|
170
|
+
method_option :tags, type: :array, desc: "tags"
|
171
|
+
def otx(domain)
|
172
|
+
with_error_handling do
|
173
|
+
run_analyzer Analyzers::OTX, query: refang(domain), options: options
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
desc "spyse [QUERY]", "Spyse search by a query"
|
178
|
+
method_option :title, type: :string, desc: "title"
|
179
|
+
method_option :description, type: :string, desc: "description"
|
180
|
+
method_option :tags, type: :array, desc: "tags"
|
181
|
+
method_option :type, type: :string, desc: "type to search (ip or domain)", default: "doamin"
|
182
|
+
def spyse(query)
|
183
|
+
with_error_handling do
|
184
|
+
run_analyzer Analyzers::Spyse, query: query, options: options
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
167
188
|
desc "passive_dns [IP|DOMAIN]", "Cross search with passive DNS services by an ip or domain"
|
168
189
|
method_option :title, type: :string, desc: "title"
|
169
190
|
method_option :description, type: :string, desc: "description"
|
@@ -249,10 +270,15 @@ module Mihari
|
|
249
270
|
|
250
271
|
desc "alerts", "Show the alerts on TheHive"
|
251
272
|
method_option :limit, type: :string, default: "5", desc: "Number of alerts to show (or 'all' to show all the alerts)"
|
273
|
+
method_option :title, type: :string, desc: "Title to filter"
|
274
|
+
method_option :source, type: :string, desc: "Source to filter"
|
275
|
+
method_option :tag, type: :string, desc: "Tag to filter"
|
252
276
|
def alerts
|
253
277
|
with_error_handling do
|
254
|
-
|
255
|
-
|
278
|
+
load_configuration
|
279
|
+
|
280
|
+
viewer = AlertViewer.new
|
281
|
+
alerts = viewer.list(limit: options["limit"], title: options["title"], source: options["source"], tag: options[:tag])
|
256
282
|
puts JSON.pretty_generate(alerts)
|
257
283
|
end
|
258
284
|
end
|
@@ -261,6 +287,7 @@ module Mihari
|
|
261
287
|
def status
|
262
288
|
with_error_handling do
|
263
289
|
load_configuration
|
290
|
+
|
264
291
|
puts JSON.pretty_generate(Status.check)
|
265
292
|
end
|
266
293
|
end
|
@@ -289,8 +316,7 @@ module Mihari
|
|
289
316
|
return unless config
|
290
317
|
|
291
318
|
Config.load_from_yaml(config)
|
292
|
-
|
293
|
-
load File.expand_path("./database.rb", __dir__)
|
319
|
+
Database.connect
|
294
320
|
end
|
295
321
|
|
296
322
|
def run_analyzer(analyzer_class, query:, options:)
|
data/lib/mihari/config.rb
CHANGED
@@ -12,6 +12,7 @@ module Mihari
|
|
12
12
|
attr_accessor :misp_api_endpoint
|
13
13
|
attr_accessor :misp_api_key
|
14
14
|
attr_accessor :onyphe_api_key
|
15
|
+
attr_accessor :otx_api_key
|
15
16
|
attr_accessor :passivetotal_api_key
|
16
17
|
attr_accessor :passivetotal_username
|
17
18
|
attr_accessor :pulsedive_api_key
|
@@ -19,6 +20,7 @@ module Mihari
|
|
19
20
|
attr_accessor :shodan_api_key
|
20
21
|
attr_accessor :slack_channel
|
21
22
|
attr_accessor :slack_webhook_url
|
23
|
+
attr_accessor :spyse_api_key
|
22
24
|
attr_accessor :thehive_api_endpoint
|
23
25
|
attr_accessor :thehive_api_key
|
24
26
|
attr_accessor :virustotal_api_key
|
@@ -40,6 +42,7 @@ module Mihari
|
|
40
42
|
@misp_api_endpoint = ENV["MISP_API_ENDPOINT"]
|
41
43
|
@misp_api_key = ENV["MISP_API_KEY"]
|
42
44
|
@onyphe_api_key = ENV["ONYPHE_API_KEY"]
|
45
|
+
@otx_api_key = ENV["OTX_API_KEY"]
|
43
46
|
@passivetotal_api_key = ENV["PASSIVETOTAL_API_KEY"]
|
44
47
|
@passivetotal_username = ENV["PASSIVETOTAL_USERNAME"]
|
45
48
|
@pulsedive_api_key = ENV["PULSEDIVE_API_KEY"]
|
@@ -47,6 +50,7 @@ module Mihari
|
|
47
50
|
@shodan_api_key = ENV["SHODAN_API_KEY"]
|
48
51
|
@slack_channel = ENV["SLACK_CHANNEL"]
|
49
52
|
@slack_webhook_url = ENV["SLACK_WEBHOOK_URL"]
|
53
|
+
@spyse_api_key = ENV["SPYSE_API_KEY"]
|
50
54
|
@thehive_api_endpoint = ENV["THEHIVE_API_ENDPOINT"]
|
51
55
|
@thehive_api_key = ENV["THEHIVE_API_KEY"]
|
52
56
|
@virustotal_api_key = ENV["VIRUSTOTAL_API_KEY"]
|
data/lib/mihari/configurable.rb
CHANGED
data/lib/mihari/database.rb
CHANGED
@@ -2,11 +2,6 @@
|
|
2
2
|
|
3
3
|
require "active_record"
|
4
4
|
|
5
|
-
ActiveRecord::Base.establish_connection(
|
6
|
-
adapter: "sqlite3",
|
7
|
-
database: Mihari.config.database
|
8
|
-
)
|
9
|
-
|
10
5
|
class InitialSchema < ActiveRecord::Migration[6.0]
|
11
6
|
def change
|
12
7
|
create_table :tags, if_not_exists: true do |t|
|
@@ -37,9 +32,37 @@ class InitialSchema < ActiveRecord::Migration[6.0]
|
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# Do nothing
|
35
|
+
def adapter
|
36
|
+
return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
|
37
|
+
|
38
|
+
"sqlite3"
|
45
39
|
end
|
40
|
+
|
41
|
+
module Mihari
|
42
|
+
class Database
|
43
|
+
class << self
|
44
|
+
def connect
|
45
|
+
case adapter
|
46
|
+
when "postgresql"
|
47
|
+
ActiveRecord::Base.establish_connection(Mihari.config.database)
|
48
|
+
else
|
49
|
+
ActiveRecord::Base.establish_connection(
|
50
|
+
adapter: adapter,
|
51
|
+
database: Mihari.config.database
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
ActiveRecord::Migration.verbose = false
|
56
|
+
InitialSchema.migrate(:up)
|
57
|
+
rescue StandardError
|
58
|
+
# Do nothing
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy!
|
62
|
+
InitialSchema.migrate(:down) if ActiveRecord::Base.connected?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Mihari::Database.connect
|
data/lib/mihari/emitters/misp.rb
CHANGED
@@ -4,6 +4,8 @@ require "slack-notifier"
|
|
4
4
|
require "digest/sha2"
|
5
5
|
require "mem"
|
6
6
|
|
7
|
+
require "mihari/slack_monkeypatch"
|
8
|
+
|
7
9
|
module Mihari
|
8
10
|
module Emitters
|
9
11
|
class Attachment
|
@@ -135,7 +137,7 @@ module Mihari
|
|
135
137
|
private
|
136
138
|
|
137
139
|
def config_keys
|
138
|
-
|
140
|
+
%w(slack_webhook_url)
|
139
141
|
end
|
140
142
|
end
|
141
143
|
end
|
data/lib/mihari/retriable.rb
CHANGED
@@ -7,10 +7,10 @@ module Mihari
|
|
7
7
|
begin
|
8
8
|
try += 1
|
9
9
|
yield
|
10
|
-
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError, Timeout::Error =>
|
10
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError, Timeout::Error, ::Shodan::Error => e
|
11
11
|
sleep interval
|
12
12
|
retry if try < times
|
13
|
-
raise
|
13
|
+
raise e
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slack
|
4
|
+
class Notifier
|
5
|
+
module Util
|
6
|
+
class LinkFormatter
|
7
|
+
class << self
|
8
|
+
def format(string, opts = {})
|
9
|
+
# Resolve warning in Ruby 2.7
|
10
|
+
LinkFormatter.new(string, **opts).formatted
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/mihari/version.rb
CHANGED
data/mihari.gemspec
CHANGED
@@ -26,15 +26,15 @@ Gem::Specification.new do |spec|
|
|
26
26
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 2.1"
|
28
28
|
spec.add_development_dependency "coveralls", "~> 0.8"
|
29
|
-
spec.add_development_dependency "execjs", "~> 2.
|
29
|
+
spec.add_development_dependency "execjs", "~> 2.7"
|
30
30
|
spec.add_development_dependency "fakefs", "~> 1.2"
|
31
31
|
spec.add_development_dependency "pre-commit", "~> 0.39"
|
32
32
|
spec.add_development_dependency "rake", "~> 13.0"
|
33
33
|
spec.add_development_dependency "rspec", "~> 3.9"
|
34
|
-
spec.add_development_dependency "rubocop", "~> 0.
|
35
|
-
spec.add_development_dependency "rubocop-performance", "~> 1.
|
34
|
+
spec.add_development_dependency "rubocop", "~> 0.88"
|
35
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.7"
|
36
36
|
spec.add_development_dependency "timecop", "~> 0.9"
|
37
|
-
spec.add_development_dependency "vcr", "~>
|
37
|
+
spec.add_development_dependency "vcr", "~> 6.0"
|
38
38
|
spec.add_development_dependency "webmock", "~> 3.8"
|
39
39
|
|
40
40
|
spec.add_dependency "active_model_serializers", "~> 0.10"
|
@@ -52,14 +52,17 @@ Gem::Specification.new do |spec|
|
|
52
52
|
spec.add_dependency "murmurhash3", "~> 0.1"
|
53
53
|
spec.add_dependency "net-ping", "~> 2.0"
|
54
54
|
spec.add_dependency "onyphe", "~> 2.0"
|
55
|
+
spec.add_dependency "otx_ruby", "~> 0.9"
|
55
56
|
spec.add_dependency "parallel", "~> 1.19"
|
56
57
|
spec.add_dependency "passive_circl", "~> 0.1"
|
57
58
|
spec.add_dependency "passivetotalx", "~> 0.1"
|
59
|
+
spec.add_dependency "pg", "~> 1.2"
|
58
60
|
spec.add_dependency "public_suffix", "~> 4.0"
|
59
61
|
spec.add_dependency "pulsedive", "~> 0.1"
|
60
62
|
spec.add_dependency "securitytrails", "~> 1.0"
|
61
63
|
spec.add_dependency "shodanx", "~> 0.2"
|
62
64
|
spec.add_dependency "slack-notifier", "~> 2.3"
|
65
|
+
spec.add_dependency "spysex", "~> 0.1"
|
63
66
|
spec.add_dependency "sqlite3", "~> 1.4"
|
64
67
|
spec.add_dependency "thor", "~> 1.0"
|
65
68
|
spec.add_dependency "urlscan", "~> 0.5"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mihari
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manabu Niseki
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '2.
|
47
|
+
version: '2.7'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '2.
|
54
|
+
version: '2.7'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: fakefs
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,28 +114,28 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0.
|
117
|
+
version: '0.88'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0.
|
124
|
+
version: '0.88'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: rubocop-performance
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '1.
|
131
|
+
version: '1.7'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '1.
|
138
|
+
version: '1.7'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: timecop
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,14 +156,14 @@ dependencies:
|
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: '
|
159
|
+
version: '6.0'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: '
|
166
|
+
version: '6.0'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
168
|
name: webmock
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -388,6 +388,20 @@ dependencies:
|
|
388
388
|
- - "~>"
|
389
389
|
- !ruby/object:Gem::Version
|
390
390
|
version: '2.0'
|
391
|
+
- !ruby/object:Gem::Dependency
|
392
|
+
name: otx_ruby
|
393
|
+
requirement: !ruby/object:Gem::Requirement
|
394
|
+
requirements:
|
395
|
+
- - "~>"
|
396
|
+
- !ruby/object:Gem::Version
|
397
|
+
version: '0.9'
|
398
|
+
type: :runtime
|
399
|
+
prerelease: false
|
400
|
+
version_requirements: !ruby/object:Gem::Requirement
|
401
|
+
requirements:
|
402
|
+
- - "~>"
|
403
|
+
- !ruby/object:Gem::Version
|
404
|
+
version: '0.9'
|
391
405
|
- !ruby/object:Gem::Dependency
|
392
406
|
name: parallel
|
393
407
|
requirement: !ruby/object:Gem::Requirement
|
@@ -430,6 +444,20 @@ dependencies:
|
|
430
444
|
- - "~>"
|
431
445
|
- !ruby/object:Gem::Version
|
432
446
|
version: '0.1'
|
447
|
+
- !ruby/object:Gem::Dependency
|
448
|
+
name: pg
|
449
|
+
requirement: !ruby/object:Gem::Requirement
|
450
|
+
requirements:
|
451
|
+
- - "~>"
|
452
|
+
- !ruby/object:Gem::Version
|
453
|
+
version: '1.2'
|
454
|
+
type: :runtime
|
455
|
+
prerelease: false
|
456
|
+
version_requirements: !ruby/object:Gem::Requirement
|
457
|
+
requirements:
|
458
|
+
- - "~>"
|
459
|
+
- !ruby/object:Gem::Version
|
460
|
+
version: '1.2'
|
433
461
|
- !ruby/object:Gem::Dependency
|
434
462
|
name: public_suffix
|
435
463
|
requirement: !ruby/object:Gem::Requirement
|
@@ -500,6 +528,20 @@ dependencies:
|
|
500
528
|
- - "~>"
|
501
529
|
- !ruby/object:Gem::Version
|
502
530
|
version: '2.3'
|
531
|
+
- !ruby/object:Gem::Dependency
|
532
|
+
name: spysex
|
533
|
+
requirement: !ruby/object:Gem::Requirement
|
534
|
+
requirements:
|
535
|
+
- - "~>"
|
536
|
+
- !ruby/object:Gem::Version
|
537
|
+
version: '0.1'
|
538
|
+
type: :runtime
|
539
|
+
prerelease: false
|
540
|
+
version_requirements: !ruby/object:Gem::Requirement
|
541
|
+
requirements:
|
542
|
+
- - "~>"
|
543
|
+
- !ruby/object:Gem::Version
|
544
|
+
version: '0.1'
|
503
545
|
- !ruby/object:Gem::Dependency
|
504
546
|
name: sqlite3
|
505
547
|
requirement: !ruby/object:Gem::Requirement
|
@@ -605,6 +647,7 @@ files:
|
|
605
647
|
- lib/mihari/analyzers/free_text.rb
|
606
648
|
- lib/mihari/analyzers/http_hash.rb
|
607
649
|
- lib/mihari/analyzers/onyphe.rb
|
650
|
+
- lib/mihari/analyzers/otx.rb
|
608
651
|
- lib/mihari/analyzers/passive_dns.rb
|
609
652
|
- lib/mihari/analyzers/passive_ssl.rb
|
610
653
|
- lib/mihari/analyzers/passivetotal.rb
|
@@ -613,6 +656,7 @@ files:
|
|
613
656
|
- lib/mihari/analyzers/securitytrails.rb
|
614
657
|
- lib/mihari/analyzers/securitytrails_domain_feed.rb
|
615
658
|
- lib/mihari/analyzers/shodan.rb
|
659
|
+
- lib/mihari/analyzers/spyse.rb
|
616
660
|
- lib/mihari/analyzers/ssh_fingerprint.rb
|
617
661
|
- lib/mihari/analyzers/urlscan.rb
|
618
662
|
- lib/mihari/analyzers/virustotal.rb
|
@@ -622,9 +666,9 @@ files:
|
|
622
666
|
- lib/mihari/configurable.rb
|
623
667
|
- lib/mihari/database.rb
|
624
668
|
- lib/mihari/emitters/base.rb
|
669
|
+
- lib/mihari/emitters/database.rb
|
625
670
|
- lib/mihari/emitters/misp.rb
|
626
671
|
- lib/mihari/emitters/slack.rb
|
627
|
-
- lib/mihari/emitters/sqlite.rb
|
628
672
|
- lib/mihari/emitters/stdout.rb
|
629
673
|
- lib/mihari/emitters/the_hive.rb
|
630
674
|
- lib/mihari/errors.rb
|
@@ -640,10 +684,12 @@ files:
|
|
640
684
|
- lib/mihari/serializers/alert.rb
|
641
685
|
- lib/mihari/serializers/artifact.rb
|
642
686
|
- lib/mihari/serializers/tag.rb
|
687
|
+
- lib/mihari/slack_monkeypatch.rb
|
643
688
|
- lib/mihari/status.rb
|
644
689
|
- lib/mihari/type_checker.rb
|
645
690
|
- lib/mihari/version.rb
|
646
691
|
- mihari.gemspec
|
692
|
+
- renovate.json
|
647
693
|
- screenshots/alert.png
|
648
694
|
- screenshots/eyecatch.png
|
649
695
|
- screenshots/misp.png
|
@@ -652,7 +698,7 @@ homepage: https://github.com/ninoseki/mihari
|
|
652
698
|
licenses:
|
653
699
|
- MIT
|
654
700
|
metadata: {}
|
655
|
-
post_install_message:
|
701
|
+
post_install_message:
|
656
702
|
rdoc_options: []
|
657
703
|
require_paths:
|
658
704
|
- lib
|
@@ -668,7 +714,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
668
714
|
version: '0'
|
669
715
|
requirements: []
|
670
716
|
rubygems_version: 3.1.2
|
671
|
-
signing_key:
|
717
|
+
signing_key:
|
672
718
|
specification_version: 4
|
673
719
|
summary: A framework for continuous malicious hosts monitoring.
|
674
720
|
test_files: []
|