mihari 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|