mihari 1.0.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44cc5eea0746aa47918f5102cf4834fcd436b67f371b37f8a1e4fe6999902c82
4
- data.tar.gz: e0dd701f1d5b705239d73125ca8865fa15c949d4b62cb59a2c9beef8e01e363d
3
+ metadata.gz: 6d6b5d42dc4fbe937cc101f7dbdfa1491104f2b6f70fdf690b2ad51db02304c5
4
+ data.tar.gz: 32b5df5f6970d6aaf6f9d5c57ca9bc86d8e43f0298f22c6c9e811cf60a6d1089
5
5
  SHA512:
6
- metadata.gz: 36722428cc688eaefd3f293f1de99db20b8671945082b1d5f1d08d61c9b977491e46f45122311010650d80b1f0077dd7ec9323e304c02fdcf9c4fc1b22c66672
7
- data.tar.gz: ad9e0603040bc4069194df7f21c0963e096beaa3e6e2ac13ea0d66803217619b885f3afef4624c0c45ae4282c64f8bda905bf61d8c564f9aef8c7a5b33bb2dca
6
+ metadata.gz: 16cdfd458c0d464eb618fc5aae8386c1cb37fd554fd6aba275323cb539a08916f3cd855e4e5683085e03f206debe879af6f2e3c7a42b1cd0b2a6395d86e3f55a
7
+ data.tar.gz: 93f9e9e2165e79ef6602d1628b5689e0993ae4d88605db4f6aa4ff79dc6a21d25437997a1402f76ce3a5e2e9155ad68a441a8cc8351b54c5f4d1c3a72eb82df4
@@ -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
  ![img](https://github.com/ninoseki/mihari/raw/master/screenshots/misp.png)
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 | Default |
193
- |------------------------|--------------------------------|-------------|
194
- | DATABASE | A path to the SQLite database | `mihari.db` |
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
- | PASSIVETOTAL_API_KEY | PassiveTotal API key | |
204
- | PASSIVETOTAL_USERNAME | PassiveTotal username | |
205
- | PULSEDIVE_API_KEY | Pulsedive API key | |
206
- | SECURITYTRAILS_API_KEY | SecurityTrails API key | |
207
- | SHODAN_API_KEY | Shodan API key | |
208
- | SLACK_CHANNEL | Slack channel name | `#general` |
209
- | SLACK_WEBHOOK_URL | Slack Webhook URL | |
210
- | THEHIVE_API_ENDPOINT | TheHive URL | |
211
- | THEHIVE_API_KEY | TheHive API key | |
212
- | VIRUSTOTAL_API_KEY | VirusTotal API key | |
213
- | ZOOMEYE_PASSWORD | ZoomEye password | |
214
- | ZOOMEYE_USERNAMME | ZoomEye username | |
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
 
@@ -1,5 +1,5 @@
1
- FROM ruby:2.6-alpine3.10
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 \
@@ -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
 
@@ -2,17 +2,21 @@
2
2
 
3
3
  module Mihari
4
4
  class AlertViewer
5
- attr_reader :limit
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
- def initialize(limit: 5)
8
- @limit = limit.to_i
9
- raise ArgumentError, "limit should be bigger than zero" unless @limit.positive?
10
- end
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
- def list
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
@@ -52,7 +52,7 @@ module Mihari
52
52
  end
53
53
 
54
54
  def config_keys
55
- [Mihari.config.binaryedge_api_key]
55
+ %w(binaryedge_api_key)
56
56
  end
57
57
 
58
58
  def api
@@ -86,7 +86,7 @@ module Mihari
86
86
  end
87
87
 
88
88
  def config_keys
89
- [Mihari.config.censys_id, Mihari.config.censys_secret]
89
+ %w(censys_id censys_secret)
90
90
  end
91
91
 
92
92
  def api
@@ -27,7 +27,7 @@ module Mihari
27
27
  private
28
28
 
29
29
  def config_keys
30
- [Mihari.config.circl_passive_password, Mihari.config.circl_passive_username]
30
+ %w(circl_passive_password circl_passive_username)
31
31
  end
32
32
 
33
33
  def api
@@ -35,7 +35,7 @@ module Mihari
35
35
  PAGE_SIZE = 10
36
36
 
37
37
  def config_keys
38
- [Mihari.config.onyphe_api_key]
38
+ %w(onyphe_api_key)
39
39
  end
40
40
 
41
41
  def api
@@ -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
@@ -30,7 +30,7 @@ module Mihari
30
30
  private
31
31
 
32
32
  def config_keys
33
- [Mihari.config.passivetotal_username, Mihari.config.passivetotal_api_key]
33
+ %w(passivetotal_username passivetotal_api_key)
34
34
  end
35
35
 
36
36
  def api
@@ -30,7 +30,7 @@ module Mihari
30
30
  private
31
31
 
32
32
  def config_keys
33
- [Mihari.config.pulsedive_api_key]
33
+ %w(pulsedive_api_key)
34
34
  end
35
35
 
36
36
  def api
@@ -30,7 +30,7 @@ module Mihari
30
30
  private
31
31
 
32
32
  def config_keys
33
- [Mihari.config.securitytrails_api_key]
33
+ %w(securitytrails_api_key)
34
34
  end
35
35
 
36
36
  def api
@@ -32,7 +32,7 @@ module Mihari
32
32
  private
33
33
 
34
34
  def config_keys
35
- [Mihari.config.securitytrails_api_key]
35
+ %w(securitytrails_api_key)
36
36
  end
37
37
 
38
38
  def api
@@ -36,7 +36,7 @@ module Mihari
36
36
  PAGE_SIZE = 100
37
37
 
38
38
  def config_keys
39
- [Mihari.config.shodan_api_key]
39
+ %w(shodan_api_key)
40
40
  end
41
41
 
42
42
  def api
@@ -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
@@ -30,7 +30,7 @@ module Mihari
30
30
  private
31
31
 
32
32
  def config_keys
33
- [Mihari.config.virustotal_api_key]
33
+ %w(virustotal_api_key)
34
34
  end
35
35
 
36
36
  def api
@@ -41,7 +41,7 @@ module Mihari
41
41
  end
42
42
 
43
43
  def config_keys
44
- [Mihari.config.zoomeye_password, Mihari.config.zoomeye_username]
44
+ %w(zoomeye_password zoomeye_username)
45
45
  end
46
46
 
47
47
  def api
@@ -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
- viewer = AlertViewer.new(limit: options["limit"])
255
- alerts = viewer.list
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
- # reload database settings
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:)
@@ -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"]
@@ -3,7 +3,7 @@
3
3
  module Mihari
4
4
  module Configurable
5
5
  def configured?
6
- config_keys.all? { |key| !key.nil? }
6
+ config_keys.all? { |key| Mihari.config.send(key) }
7
7
  end
8
8
 
9
9
  def configuration_status
@@ -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
- begin
41
- ActiveRecord::Migration.verbose = false
42
- InitialSchema.migrate(:up)
43
- rescue StandardError
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Mihari
4
4
  module Emitters
5
- class SQLite < Base
5
+ class Database < Base
6
6
  def valid?
7
7
  true
8
8
  end
@@ -35,7 +35,7 @@ module Mihari
35
35
  private
36
36
 
37
37
  def config_keys
38
- [Mihari.config.misp_api_endpoint, Mihari.config.misp_api_key]
38
+ %w(misp_api_endpoint misp_api_key)
39
39
  end
40
40
 
41
41
  def build_attribute(artifact)
@@ -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
- [Mihari.config.slack_webhook_url]
140
+ %w(slack_webhook_url)
139
141
  end
140
142
  end
141
143
  end
@@ -27,7 +27,7 @@ module Mihari
27
27
  private
28
28
 
29
29
  def config_keys
30
- [Mihari.config.thehive_api_endpoint, Mihari.config.thehive_api_key]
30
+ %w(thehive_api_endpoint thehive_api_key)
31
31
  end
32
32
 
33
33
  def api
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "slack-notifier"
4
+ require "mihari/slack_monkeypatch"
5
+
3
6
  module Mihari
4
7
  module Notifiers
5
8
  class Slack < Base
@@ -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 => _e
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "1.0.1"
4
+ VERSION = "1.3.0"
5
5
  end
@@ -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.0"
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.82"
35
- spec.add_development_dependency "rubocop-performance", "~> 1.5"
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", "~> 5.1"
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"
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "config:base"
4
+ ]
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.1
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-05-03 00:00:00.000000000 Z
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.0'
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.0'
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.82'
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.82'
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.5'
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.5'
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: '5.1'
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: '5.1'
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: []