mihari 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/mihari.svg)](https://badge.fury.io/rb/mihari)
|
3
4
|
[![Build Status](https://travis-ci.org/ninoseki/mihari.svg?branch=master)](https://travis-ci.org/ninoseki/mihari)
|
4
5
|
[![Coverage Status](https://coveralls.io/repos/github/ninoseki/mihari/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/mihari?branch=master)
|
5
6
|
[![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/mihari/badge)](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
|