mihari 0.3.0 → 0.4.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/README.md +11 -32
- data/lib/mihari/analyzers/base.rb +13 -8
- data/lib/mihari/analyzers/urlscan.rb +1 -1
- data/lib/mihari/analyzers/virustotal.rb +72 -0
- data/lib/mihari/cli.rb +8 -0
- data/lib/mihari/{notifiers → emitters}/base.rb +3 -3
- data/lib/mihari/{notifiers → emitters}/slack.rb +5 -5
- data/lib/mihari/emitters/stdout.rb +23 -0
- data/lib/mihari/{notifiers → emitters}/the_hive.rb +3 -5
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari.rb +7 -5
- data/mihari.gemspec +3 -2
- metadata +28 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afc816e7fe81735a8e8b007fe32884b1188f09b9fa905fe1603584b4a6d6fbfb
|
4
|
+
data.tar.gz: c7f689b44a4964e982744d67861c6f93f567e4c01ae0d6c43c6419dba063aa73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dfe252ff2f2572328a2101fd50a97cb4ff236430d60d5985f861b885f78daef780f93f67975e78be5debe6ee6fab88777bacc2f7e338681becf2251e755513f
|
7
|
+
data.tar.gz: 8ec54176b1f19bcb1b7542e376d7a8a87aef0fb076270408bdea60a08bbb6efe787b9824436c39129a61d0e99a953154d31277a9ebb67dd644fcbcaaf91650be
|
data/README.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# mihari
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/mihari)
|
3
4
|
[](https://travis-ci.org/ninoseki/mihari)
|
4
5
|
[](https://coveralls.io/github/ninoseki/mihari?branch=master)
|
5
6
|
[](https://www.codefactor.io/repository/github/ninoseki/mihari)
|
6
7
|
|
7
|
-
mihari(`見張り`) is a
|
8
|
+
mihari(`見張り`) is a sidekick tool for [TheHive](https://github.com/TheHive-Project/TheHive) to monitor malicious hosts (C2 / landing page / phishing, etc.) continuously.
|
8
9
|
|
9
10
|
## How it works
|
10
11
|
|
@@ -33,41 +34,18 @@ gem install mihari
|
|
33
34
|
|
34
35
|
## Basic usage
|
35
36
|
|
36
|
-
mihari supports Censys, Shodan and
|
37
|
+
mihari supports Censys, Shodan, Onyphe, urlscan and VirusTotal by default.
|
37
38
|
|
38
39
|
```bash
|
39
40
|
$ mihari
|
40
41
|
Commands:
|
41
|
-
mihari censys [QUERY]
|
42
|
-
mihari help [COMMAND]
|
43
|
-
mihari import_from_json
|
44
|
-
mihari onyphe [QUERY]
|
45
|
-
mihari shodan [QUERY]
|
46
|
-
mihari urlscan [QUERY]
|
47
|
-
|
48
|
-
|
49
|
-
### Censys
|
50
|
-
|
51
|
-
```bash
|
52
|
-
mihari censys "YOUR_QUERY"
|
53
|
-
```
|
54
|
-
|
55
|
-
### Shodan
|
56
|
-
|
57
|
-
```bash
|
58
|
-
mihari shodan "YOUR QUERY"
|
59
|
-
```
|
60
|
-
|
61
|
-
### Onyphe
|
62
|
-
|
63
|
-
```bash
|
64
|
-
mihari onyphe "YOUR QUERY"
|
65
|
-
```
|
66
|
-
|
67
|
-
### urlscan.io
|
68
|
-
|
69
|
-
```bash
|
70
|
-
mihari urlscan "YOUR QUERY"
|
42
|
+
mihari censys [QUERY] # Censys IPv4 lookup by a given query
|
43
|
+
mihari help [COMMAND] # Describe available commands or one specific command
|
44
|
+
mihari import_from_json # Give a JSON input via STDIN
|
45
|
+
mihari onyphe [QUERY] # Onyphe datascan lookup by a given query
|
46
|
+
mihari shodan [QUERY] # Shodan host lookup by a given query
|
47
|
+
mihari urlscan [QUERY] # urlscan lookup by a given query
|
48
|
+
mihari virustotal [IP|DOMAIN] # VirusTotal resolutions lookup by a given ip or domain
|
71
49
|
```
|
72
50
|
|
73
51
|
### Import from JSON
|
@@ -108,6 +86,7 @@ All configuration is done via ENV variables.
|
|
108
86
|
| CENSYS_SECRET | Censys secret | Optional |
|
109
87
|
| SHODAN_API_KEY | Shodan API key | Optional |
|
110
88
|
| ONYPHE_API_KEY | Onyphe API key | Optional |
|
89
|
+
| VIRUSTOTAL_API_KEY | VirusTotal API key | Optional |
|
111
90
|
|
112
91
|
## How to create a custom analyzer
|
113
92
|
|
@@ -30,18 +30,16 @@ module Mihari
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def run(reject_exists_ones: true)
|
33
|
-
|
34
|
-
reject_exists_ones & the_hive.valid? && the_hive.exists?(data: artifact.data, data_type: artifact.data_type)
|
35
|
-
end
|
33
|
+
artifacts = reject_exists_ones ? unique_artifacts : normalized_artifacts
|
36
34
|
|
37
|
-
Mihari.
|
38
|
-
|
39
|
-
next unless
|
35
|
+
Mihari.emitters.each do |emitter_class|
|
36
|
+
emitter = emitter_class.new
|
37
|
+
next unless emitter.valid?
|
40
38
|
|
41
39
|
begin
|
42
|
-
|
40
|
+
emitter.emit(title: title, description: description, artifacts: artifacts, tags: tags)
|
43
41
|
rescue StandardError => e
|
44
|
-
puts "Sending notification by #{
|
42
|
+
puts "Sending notification by #{emitter.class} is failed: #{e}"
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
@@ -54,6 +52,13 @@ module Mihari
|
|
54
52
|
artifact.is_a?(Artifact) ? artifact : Artifact.new(artifact)
|
55
53
|
end.select(&:valid?)
|
56
54
|
end
|
55
|
+
|
56
|
+
# @return [Array<Mihari::Artifact>]
|
57
|
+
def unique_artifacts
|
58
|
+
normalized_artifacts.reject do |artifact|
|
59
|
+
the_hive.valid? && the_hive.exists?(data: artifact.data, data_type: artifact.data_type)
|
60
|
+
end
|
61
|
+
end
|
57
62
|
end
|
58
63
|
end
|
59
64
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "virustotal"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Analyzers
|
7
|
+
class VirusTotal < Base
|
8
|
+
attr_reader :api
|
9
|
+
attr_reader :indicator
|
10
|
+
attr_reader :type
|
11
|
+
|
12
|
+
attr_reader :title
|
13
|
+
attr_reader :description
|
14
|
+
attr_reader :tags
|
15
|
+
|
16
|
+
def initialize(indicator, tags: [])
|
17
|
+
super()
|
18
|
+
|
19
|
+
@api = ::VirusTotal::API.new
|
20
|
+
@indicator = indicator
|
21
|
+
@type = TypeChecker.type(indicator)
|
22
|
+
|
23
|
+
@title = "VirusTotal lookup"
|
24
|
+
@description = "indicator = #{indicator}"
|
25
|
+
@tags = tags
|
26
|
+
end
|
27
|
+
|
28
|
+
def artifacts
|
29
|
+
lookup || []
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def valid_type?
|
35
|
+
%w(ip domain).include? type
|
36
|
+
end
|
37
|
+
|
38
|
+
def lookup
|
39
|
+
case type
|
40
|
+
when "domain"
|
41
|
+
domain_lookup
|
42
|
+
when "ip"
|
43
|
+
ip_lookup
|
44
|
+
else
|
45
|
+
raise ArgumentError, "#{indicator}(type: #{type || 'unknown'}) is not supported." unless valid_type?
|
46
|
+
end
|
47
|
+
rescue ::VirusTotal::Error => _e
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def domain_lookup
|
52
|
+
report = api.domain.report(indicator)
|
53
|
+
return nil unless report
|
54
|
+
|
55
|
+
resolutions = report.dig("resolutions") || []
|
56
|
+
resolutions.map do |resolution|
|
57
|
+
resolution.dig("ip_address")
|
58
|
+
end.compact.uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
def ip_lookup
|
62
|
+
report = api.ip_address.report(indicator)
|
63
|
+
return nil unless report
|
64
|
+
|
65
|
+
resolutions = report.dig("resolutions") || []
|
66
|
+
resolutions.map do |resolution|
|
67
|
+
resolution.dig("hostname")
|
68
|
+
end.compact.uniq
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/mihari/cli.rb
CHANGED
@@ -37,6 +37,14 @@ module Mihari
|
|
37
37
|
run_analyzer urlscan
|
38
38
|
end
|
39
39
|
|
40
|
+
desc "virustotal [IP|DOMAIN]", "VirusTotal resolutions lookup by a given ip or domain"
|
41
|
+
method_option :tags, type: :array, desc: "tags"
|
42
|
+
def virustotal(indiactor)
|
43
|
+
tags = options.dig("tags") || []
|
44
|
+
virustotal = Analyzers::VirusTotal.new(indiactor, tags: tags)
|
45
|
+
run_analyzer virustotal
|
46
|
+
end
|
47
|
+
|
40
48
|
desc "import_from_json", "Give a JSON input via STDIN"
|
41
49
|
def import_from_json(input = nil)
|
42
50
|
json = input || STDIN.gets.chomp
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Mihari
|
4
|
-
module
|
4
|
+
module Emitters
|
5
5
|
class Base
|
6
6
|
def self.inherited(child)
|
7
|
-
Mihari.
|
7
|
+
Mihari.emitters << child
|
8
8
|
end
|
9
9
|
|
10
10
|
# @return [true, false]
|
@@ -12,7 +12,7 @@ module Mihari
|
|
12
12
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def emit(title:, description:, artifacts:)
|
16
16
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
17
17
|
end
|
18
18
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "slack
|
3
|
+
require "slack-notifier"
|
4
4
|
require "digest/sha2"
|
5
5
|
require "mem"
|
6
6
|
|
7
7
|
module Mihari
|
8
|
-
module
|
8
|
+
module Emitters
|
9
9
|
class Attachment
|
10
10
|
include Mem
|
11
11
|
|
@@ -125,14 +125,14 @@ module Mihari
|
|
125
125
|
end.flatten
|
126
126
|
end
|
127
127
|
|
128
|
-
def
|
128
|
+
def emit(title:, description:, artifacts:, tags:)
|
129
129
|
return if artifacts.empty?
|
130
130
|
|
131
131
|
attachments = to_attachments(artifacts)
|
132
132
|
tags << ["N/A"] if tags.empty?
|
133
133
|
|
134
|
-
|
135
|
-
|
134
|
+
notifier = ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel)
|
135
|
+
notifier.post(text: "#{title} (desc.: #{description} / tags: #{tags.join(', ')})", attachments: attachments)
|
136
136
|
end
|
137
137
|
end
|
138
138
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Emitters
|
7
|
+
class StandardOutput < Base
|
8
|
+
def valid?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def emit(title:, description:, artifacts:, tags:)
|
13
|
+
h = {
|
14
|
+
title: title,
|
15
|
+
description: description,
|
16
|
+
artifacts: artifacts.map(&:data),
|
17
|
+
tags: tags
|
18
|
+
}
|
19
|
+
puts JSON.pretty_generate(h)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Mihari
|
4
|
-
module
|
4
|
+
module Emitters
|
5
5
|
class TheHive < Base
|
6
6
|
attr_reader :api
|
7
7
|
|
@@ -14,17 +14,15 @@ module Mihari
|
|
14
14
|
api.valid?
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def emit(title:, description:, artifacts:, tags: [])
|
18
18
|
return if artifacts.empty?
|
19
19
|
|
20
|
-
|
20
|
+
api.create_alert(
|
21
21
|
title: title,
|
22
22
|
description: description,
|
23
23
|
artifacts: artifacts.map(&:to_h),
|
24
24
|
tags: tags
|
25
25
|
)
|
26
|
-
id = res.dig("id")
|
27
|
-
puts "A new alret is created. (id: #{id})"
|
28
26
|
end
|
29
27
|
end
|
30
28
|
end
|
data/lib/mihari/version.rb
CHANGED
data/lib/mihari.rb
CHANGED
@@ -6,10 +6,10 @@ module Mihari
|
|
6
6
|
class << self
|
7
7
|
include Mem
|
8
8
|
|
9
|
-
def
|
9
|
+
def emitters
|
10
10
|
[]
|
11
11
|
end
|
12
|
-
memoize :
|
12
|
+
memoize :emitters
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -28,9 +28,11 @@ require "mihari/analyzers/censys"
|
|
28
28
|
require "mihari/analyzers/onyphe"
|
29
29
|
require "mihari/analyzers/shodan"
|
30
30
|
require "mihari/analyzers/urlscan"
|
31
|
+
require "mihari/analyzers/virustotal"
|
31
32
|
|
32
|
-
require "mihari/
|
33
|
-
require "mihari/
|
34
|
-
require "mihari/
|
33
|
+
require "mihari/emitters/base"
|
34
|
+
require "mihari/emitters/slack"
|
35
|
+
require "mihari/emitters/stdout"
|
36
|
+
require "mihari/emitters/the_hive"
|
35
37
|
|
36
38
|
require "mihari/cli"
|
data/mihari.gemspec
CHANGED
@@ -36,11 +36,12 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency "email_address", "~> 0.1"
|
37
37
|
spec.add_dependency "hachi", "~> 0.1"
|
38
38
|
spec.add_dependency "mem", "~> 0.1"
|
39
|
-
spec.add_dependency "net-ping"
|
39
|
+
spec.add_dependency "net-ping", "~> 2.0"
|
40
40
|
spec.add_dependency "onyphe", "~> 0.2"
|
41
41
|
spec.add_dependency "public_suffix", "~> 3.1"
|
42
42
|
spec.add_dependency "shodanx", "~> 0.1"
|
43
|
-
spec.add_dependency "slack-
|
43
|
+
spec.add_dependency "slack-notifier", "~> 2.3"
|
44
44
|
spec.add_dependency "thor", "~> 0.19"
|
45
45
|
spec.add_dependency "urlscan", "~> 0.2"
|
46
|
+
spec.add_dependency "virustotalx", "~> 0.1"
|
46
47
|
end
|
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: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manabu Niseki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-07
|
11
|
+
date: 2019-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -168,16 +168,16 @@ dependencies:
|
|
168
168
|
name: net-ping
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
|
-
- - "
|
171
|
+
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
173
|
+
version: '2.0'
|
174
174
|
type: :runtime
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
|
-
- - "
|
178
|
+
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
180
|
+
version: '2.0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: onyphe
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -221,19 +221,19 @@ dependencies:
|
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0.1'
|
223
223
|
- !ruby/object:Gem::Dependency
|
224
|
-
name: slack-
|
224
|
+
name: slack-notifier
|
225
225
|
requirement: !ruby/object:Gem::Requirement
|
226
226
|
requirements:
|
227
227
|
- - "~>"
|
228
228
|
- !ruby/object:Gem::Version
|
229
|
-
version: '
|
229
|
+
version: '2.3'
|
230
230
|
type: :runtime
|
231
231
|
prerelease: false
|
232
232
|
version_requirements: !ruby/object:Gem::Requirement
|
233
233
|
requirements:
|
234
234
|
- - "~>"
|
235
235
|
- !ruby/object:Gem::Version
|
236
|
-
version: '
|
236
|
+
version: '2.3'
|
237
237
|
- !ruby/object:Gem::Dependency
|
238
238
|
name: thor
|
239
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -262,6 +262,20 @@ dependencies:
|
|
262
262
|
- - "~>"
|
263
263
|
- !ruby/object:Gem::Version
|
264
264
|
version: '0.2'
|
265
|
+
- !ruby/object:Gem::Dependency
|
266
|
+
name: virustotalx
|
267
|
+
requirement: !ruby/object:Gem::Requirement
|
268
|
+
requirements:
|
269
|
+
- - "~>"
|
270
|
+
- !ruby/object:Gem::Version
|
271
|
+
version: '0.1'
|
272
|
+
type: :runtime
|
273
|
+
prerelease: false
|
274
|
+
version_requirements: !ruby/object:Gem::Requirement
|
275
|
+
requirements:
|
276
|
+
- - "~>"
|
277
|
+
- !ruby/object:Gem::Version
|
278
|
+
version: '0.1'
|
265
279
|
description: A framework for continuous malicious hosts monitoring.
|
266
280
|
email:
|
267
281
|
- manabu.niseki@gmail.com
|
@@ -289,12 +303,14 @@ files:
|
|
289
303
|
- lib/mihari/analyzers/onyphe.rb
|
290
304
|
- lib/mihari/analyzers/shodan.rb
|
291
305
|
- lib/mihari/analyzers/urlscan.rb
|
306
|
+
- lib/mihari/analyzers/virustotal.rb
|
292
307
|
- lib/mihari/artifact.rb
|
293
308
|
- lib/mihari/cli.rb
|
309
|
+
- lib/mihari/emitters/base.rb
|
310
|
+
- lib/mihari/emitters/slack.rb
|
311
|
+
- lib/mihari/emitters/stdout.rb
|
312
|
+
- lib/mihari/emitters/the_hive.rb
|
294
313
|
- lib/mihari/errors.rb
|
295
|
-
- lib/mihari/notifiers/base.rb
|
296
|
-
- lib/mihari/notifiers/slack.rb
|
297
|
-
- lib/mihari/notifiers/the_hive.rb
|
298
314
|
- lib/mihari/the_hive.rb
|
299
315
|
- lib/mihari/type_checker.rb
|
300
316
|
- lib/mihari/version.rb
|