mihari 0.5.0 → 0.5.1
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/lib/mihari/analyzers/base.rb +8 -8
- data/lib/mihari/cli.rb +39 -33
- data/lib/mihari/emitters/slack.rb +5 -16
- data/lib/mihari/notifiers/base.rb +16 -0
- data/lib/mihari/notifiers/exception_notifier.rb +78 -0
- data/lib/mihari/notifiers/slack.rb +31 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari.rb +4 -0
- data/mihari.gemspec +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6409ca38527e133337f4dbe174615e3c84aff5d4d82effcc40d57e63c0a6f80c
|
4
|
+
data.tar.gz: 6814631990a33c3c3363863d7e9c00d8d3d1ca4f48f0862226cc5416e7be1a14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5693f87250cd3f23047932adc24a781074038ebaf34c3a2a4aabc921446053df0f426a259b1cc51d49f0dae5e9f6fdf64a8391dcb45f7c323267051b8b2823fd
|
7
|
+
data.tar.gz: d5337291e107b38a69ef4d70900b3c90a2c8f5eba13cdc96efb30ded29ff90fa6f0eec08595045fb3407d094f37d9572a458292686088b74b29a3f8e96888c69
|
@@ -29,21 +29,21 @@ module Mihari
|
|
29
29
|
[]
|
30
30
|
end
|
31
31
|
|
32
|
-
def run
|
33
|
-
artifacts = reject_exists_ones ? unique_artifacts : normalized_artifacts
|
34
|
-
|
32
|
+
def run
|
35
33
|
Mihari.emitters.each do |emitter_class|
|
36
34
|
emitter = emitter_class.new
|
37
35
|
next unless emitter.valid?
|
38
36
|
|
39
|
-
|
40
|
-
emitter.emit(title: title, description: description, artifacts: artifacts, tags: tags)
|
41
|
-
rescue StandardError => e
|
42
|
-
puts "Sending notification by #{emitter.class} is failed: #{e}"
|
43
|
-
end
|
37
|
+
run_emitter emitter
|
44
38
|
end
|
45
39
|
end
|
46
40
|
|
41
|
+
def run_emitter(emitter)
|
42
|
+
emitter.emit(title: title, description: description, artifacts: unique_artifacts, tags: tags)
|
43
|
+
rescue StandardError => e
|
44
|
+
puts "Emission by #{emitter.class} is failed: #{e}"
|
45
|
+
end
|
46
|
+
|
47
47
|
private
|
48
48
|
|
49
49
|
# @return [Array<Mihari::Artifact>]
|
data/lib/mihari/cli.rb
CHANGED
@@ -8,58 +8,70 @@ module Mihari
|
|
8
8
|
desc "censys [QUERY]", "Censys IPv4 lookup by a given query"
|
9
9
|
method_option :tags, type: :array, desc: "tags"
|
10
10
|
def censys(query)
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
with_error_handling do
|
12
|
+
tags = options.dig("tags") || []
|
13
|
+
censys = Analyzers::Censys.new(query, tags: tags)
|
14
|
+
censys.run
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
desc "shodan [QUERY]", "Shodan host lookup by a given query"
|
17
19
|
method_option :tags, type: :array, desc: "tags"
|
18
20
|
def shodan(query)
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
with_error_handling do
|
22
|
+
tags = options.dig("tags") || []
|
23
|
+
shodan = Analyzers::Shodan.new(query, tags: tags)
|
24
|
+
shodan.run
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
desc "onyphe [QUERY]", "Onyphe datascan lookup by a given query"
|
25
29
|
method_option :tags, type: :array, desc: "tags"
|
26
30
|
def onyphe(query)
|
27
|
-
|
28
|
-
|
29
|
-
|
31
|
+
with_error_handling do
|
32
|
+
tags = options.dig("tags") || []
|
33
|
+
onyphe = Analyzers::Onyphe.new(query, tags: tags)
|
34
|
+
onyphe.run
|
35
|
+
end
|
30
36
|
end
|
31
37
|
|
32
38
|
desc "urlscan [QUERY]", "urlscan lookup by a given query"
|
33
39
|
method_option :tags, type: :array, desc: "tags"
|
34
40
|
def urlscan(query)
|
35
|
-
|
36
|
-
|
37
|
-
|
41
|
+
with_error_handling do
|
42
|
+
tags = options.dig("tags") || []
|
43
|
+
urlscan = Analyzers::Urlscan.new(query, tags: tags)
|
44
|
+
urlscan.run
|
45
|
+
end
|
38
46
|
end
|
39
47
|
|
40
48
|
desc "virustotal [IP|DOMAIN]", "VirusTotal resolutions lookup by a given ip or domain"
|
41
49
|
method_option :tags, type: :array, desc: "tags"
|
42
50
|
def virustotal(indiactor)
|
43
|
-
|
44
|
-
|
45
|
-
|
51
|
+
with_error_handling do
|
52
|
+
tags = options.dig("tags") || []
|
53
|
+
virustotal = Analyzers::VirusTotal.new(indiactor, tags: tags)
|
54
|
+
virustotal.run
|
55
|
+
end
|
46
56
|
end
|
47
57
|
|
48
58
|
desc "import_from_json", "Give a JSON input via STDIN"
|
49
59
|
def import_from_json(input = nil)
|
50
|
-
|
51
|
-
|
60
|
+
with_error_handling do
|
61
|
+
json = input || STDIN.gets.chomp
|
62
|
+
raise ArgumentError, "Input not found: please give an input in a JSON format" unless json
|
52
63
|
|
53
|
-
|
54
|
-
|
64
|
+
json = parse_as_json(json)
|
65
|
+
raise ArgumentError, "Invalid input format: an input JSON data should have title, description and artifacts key" unless valid_json?(json)
|
55
66
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
title = json.dig("title")
|
68
|
+
description = json.dig("description")
|
69
|
+
artifacts = json.dig("artifacts")
|
70
|
+
tags = json.dig("tags") || []
|
60
71
|
|
61
|
-
|
62
|
-
|
72
|
+
basic = Analyzers::Basic.new(title: title, description: description, artifacts: artifacts, tags: tags)
|
73
|
+
basic.run
|
74
|
+
end
|
63
75
|
end
|
64
76
|
|
65
77
|
desc "alerts", "Show the alerts on TheHive"
|
@@ -75,15 +87,9 @@ module Mihari
|
|
75
87
|
no_commands do
|
76
88
|
def with_error_handling
|
77
89
|
yield
|
78
|
-
rescue ArgumentError, Hachi::Error, Censys::ResponseError, Error => e
|
79
|
-
puts "Warning: #{e}"
|
80
90
|
rescue StandardError => e
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
def run_analyzer(analyzer)
|
86
|
-
with_error_handling { analyzer.run }
|
91
|
+
notifier = Notifiers::ExceptionNotifier.new
|
92
|
+
notifier.notify e
|
87
93
|
end
|
88
94
|
|
89
95
|
def parse_as_json(input)
|
@@ -100,23 +100,12 @@ module Mihari
|
|
100
100
|
end
|
101
101
|
|
102
102
|
class Slack < Base
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
def slack_channel
|
107
|
-
ENV.fetch SLACK_CHANNEL_KEY, "#general"
|
108
|
-
end
|
109
|
-
|
110
|
-
def slack_webhook_url
|
111
|
-
ENV.fetch SLACK_WEBHOOK_URL_KEY
|
112
|
-
end
|
113
|
-
|
114
|
-
def slack_webhook_url?
|
115
|
-
ENV.key? SLACK_WEBHOOK_URL_KEY
|
103
|
+
def notifier
|
104
|
+
@notifier ||= Notifiers::Slack.new
|
116
105
|
end
|
117
106
|
|
118
107
|
def valid?
|
119
|
-
|
108
|
+
notifier.valid?
|
120
109
|
end
|
121
110
|
|
122
111
|
def to_attachments(artifacts)
|
@@ -130,9 +119,9 @@ module Mihari
|
|
130
119
|
|
131
120
|
attachments = to_attachments(artifacts)
|
132
121
|
tags = ["N/A"] if tags.empty?
|
122
|
+
text = "#{title} (desc.: #{description} / tags: #{tags.join(', ')})"
|
133
123
|
|
134
|
-
notifier
|
135
|
-
notifier.post(text: "#{title} (desc.: #{description} / tags: #{tags.join(', ')})", attachments: attachments)
|
124
|
+
notifier.notify(text: text, attachments: attachments)
|
136
125
|
end
|
137
126
|
end
|
138
127
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Notifiers
|
5
|
+
class Base
|
6
|
+
# @return [true, false]
|
7
|
+
def valid?
|
8
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def notify
|
12
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Notifiers
|
7
|
+
class ExceptionNotifier
|
8
|
+
def initialize
|
9
|
+
@backtrace_lines = 10
|
10
|
+
@color = 'danger'
|
11
|
+
|
12
|
+
@slack = Notifiers::Slack.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
@slack.valid?
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify(exception)
|
20
|
+
notify_to_stdout exception
|
21
|
+
|
22
|
+
clean_message = exception.message.tr('`', "'")
|
23
|
+
attachments = to_attachments(exception, clean_message)
|
24
|
+
notify_to_slack(text: clean_message, attachments: attachments) if @slack.valid?
|
25
|
+
end
|
26
|
+
|
27
|
+
def notify_to_slack(text:, attachments:)
|
28
|
+
@slack.notify(text: text, attachments: attachments)
|
29
|
+
end
|
30
|
+
|
31
|
+
def notify_to_stdout(exception)
|
32
|
+
text = to_text(exception.class).chomp
|
33
|
+
message = "#{text}: #{exception.message}"
|
34
|
+
puts message
|
35
|
+
puts format_backtrace(exception.backtrace) if exception.backtrace
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_attachments(exception, clean_message)
|
39
|
+
text = to_text(exception.class)
|
40
|
+
backtrace = exception.backtrace
|
41
|
+
fields = to_fields(clean_message, backtrace)
|
42
|
+
|
43
|
+
[color: @color, text: text, fields: fields, mrkdwn_in: %w[text fields]]
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_text(exception_class)
|
47
|
+
measure_word = /^[aeiou]/i.match?(exception_class.to_s) ? 'An' : 'A'
|
48
|
+
exception_name = "*#{measure_word}* `#{exception_class}`"
|
49
|
+
"#{exception_name} *occured in background*\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_fields(clean_message, backtrace)
|
53
|
+
fields = [
|
54
|
+
{ title: 'exception', value: clean_message },
|
55
|
+
{ title: 'Hostname', value: hostname }
|
56
|
+
]
|
57
|
+
|
58
|
+
if backtrace
|
59
|
+
formatted_backtrace = format_backtrace(backtrace)
|
60
|
+
fields << { title: 'Backtrace', value: formatted_backtrace }
|
61
|
+
end
|
62
|
+
fields
|
63
|
+
end
|
64
|
+
|
65
|
+
def hostname
|
66
|
+
Socket.gethostname
|
67
|
+
rescue StandardError => _e
|
68
|
+
"N/A"
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_backtrace(backtrace)
|
72
|
+
return nil unless backtrace
|
73
|
+
|
74
|
+
"```#{backtrace.first(@backtrace_lines).join("\n")}```"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Notifiers
|
5
|
+
class Slack < Base
|
6
|
+
SLACK_WEBHOOK_URL_KEY = "SLACK_WEBHOOK_URL"
|
7
|
+
SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
|
8
|
+
|
9
|
+
def slack_channel
|
10
|
+
ENV.fetch SLACK_CHANNEL_KEY, "#general"
|
11
|
+
end
|
12
|
+
|
13
|
+
def slack_webhook_url
|
14
|
+
ENV.fetch SLACK_WEBHOOK_URL_KEY
|
15
|
+
end
|
16
|
+
|
17
|
+
def slack_webhook_url?
|
18
|
+
ENV.key? SLACK_WEBHOOK_URL_KEY
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
slack_webhook_url?
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify(text:, attachments: [])
|
26
|
+
notifier = ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel)
|
27
|
+
notifier.post(text: text, attachments: attachments)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/mihari/version.rb
CHANGED
data/lib/mihari.rb
CHANGED
@@ -33,6 +33,10 @@ require "mihari/analyzers/shodan"
|
|
33
33
|
require "mihari/analyzers/urlscan"
|
34
34
|
require "mihari/analyzers/virustotal"
|
35
35
|
|
36
|
+
require "mihari/notifiers/base"
|
37
|
+
require "mihari/notifiers/slack"
|
38
|
+
require "mihari/notifiers/exception_notifier"
|
39
|
+
|
36
40
|
require "mihari/emitters/base"
|
37
41
|
require "mihari/emitters/slack"
|
38
42
|
require "mihari/emitters/stdout"
|
data/mihari.gemspec
CHANGED
@@ -41,7 +41,7 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_dependency "public_suffix", "~> 3.1"
|
42
42
|
spec.add_dependency "shodanx", "~> 0.1"
|
43
43
|
spec.add_dependency "slack-notifier", "~> 2.3"
|
44
|
-
spec.add_dependency "thor", "~> 0.
|
44
|
+
spec.add_dependency "thor", "~> 0.20"
|
45
45
|
spec.add_dependency "urlscan", "~> 0.2"
|
46
46
|
spec.add_dependency "virustotalx", "~> 0.1"
|
47
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.5.
|
4
|
+
version: 0.5.1
|
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-08-
|
11
|
+
date: 2019-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -240,14 +240,14 @@ dependencies:
|
|
240
240
|
requirements:
|
241
241
|
- - "~>"
|
242
242
|
- !ruby/object:Gem::Version
|
243
|
-
version: '0.
|
243
|
+
version: '0.20'
|
244
244
|
type: :runtime
|
245
245
|
prerelease: false
|
246
246
|
version_requirements: !ruby/object:Gem::Requirement
|
247
247
|
requirements:
|
248
248
|
- - "~>"
|
249
249
|
- !ruby/object:Gem::Version
|
250
|
-
version: '0.
|
250
|
+
version: '0.20'
|
251
251
|
- !ruby/object:Gem::Dependency
|
252
252
|
name: urlscan
|
253
253
|
requirement: !ruby/object:Gem::Requirement
|
@@ -312,6 +312,9 @@ files:
|
|
312
312
|
- lib/mihari/emitters/stdout.rb
|
313
313
|
- lib/mihari/emitters/the_hive.rb
|
314
314
|
- lib/mihari/errors.rb
|
315
|
+
- lib/mihari/notifiers/base.rb
|
316
|
+
- lib/mihari/notifiers/exception_notifier.rb
|
317
|
+
- lib/mihari/notifiers/slack.rb
|
315
318
|
- lib/mihari/the_hive.rb
|
316
319
|
- lib/mihari/the_hive/alert.rb
|
317
320
|
- lib/mihari/the_hive/artifact.rb
|