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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a78aa76416da5dc139e1fe11ce2d2475a45deda3a82caa1fa21eecd6e291711
4
- data.tar.gz: 9958ca0873d55c20525c3fa9186b266bbfba68c957997ae14f73a1d136d2032e
3
+ metadata.gz: 6409ca38527e133337f4dbe174615e3c84aff5d4d82effcc40d57e63c0a6f80c
4
+ data.tar.gz: 6814631990a33c3c3363863d7e9c00d8d3d1ca4f48f0862226cc5416e7be1a14
5
5
  SHA512:
6
- metadata.gz: 4d80e8026601ca12ed90b45d21ef0eb88a53cd1312d6f29f910ded76d7fa76e72c3295051e59a91e159d88cc54b3abec627837bbb6e53eda8a75c4acc52edcb2
7
- data.tar.gz: f70660b7da90c4ae9a13cfba050d775283072fbd135d30b58df6d17ae41e6799532038bef16fe76a183e81a07ab188aada365f418a733540ee15436df0b6295f
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(reject_exists_ones: true)
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
- begin
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
- tags = options.dig("tags") || []
12
- censys = Analyzers::Censys.new(query, tags: tags)
13
- run_analyzer censys
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
- tags = options.dig("tags") || []
20
- shodan = Analyzers::Shodan.new(query, tags: tags)
21
- run_analyzer shodan
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
- tags = options.dig("tags") || []
28
- onyphe = Analyzers::Onyphe.new(query, tags: tags)
29
- run_analyzer onyphe
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
- tags = options.dig("tags") || []
36
- urlscan = Analyzers::Urlscan.new(query, tags: tags)
37
- run_analyzer urlscan
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
- tags = options.dig("tags") || []
44
- virustotal = Analyzers::VirusTotal.new(indiactor, tags: tags)
45
- run_analyzer virustotal
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
- json = input || STDIN.gets.chomp
51
- raise ArgumentError, "Input not found: please give an input in a JSON format" unless json
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
- json = parse_as_json(json)
54
- raise ArgumentError, "Invalid input format: an input JSON data should have title, description and artifacts key" unless valid_json?(json)
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
- title = json.dig("title")
57
- description = json.dig("description")
58
- artifacts = json.dig("artifacts")
59
- tags = json.dig("tags") || []
67
+ title = json.dig("title")
68
+ description = json.dig("description")
69
+ artifacts = json.dig("artifacts")
70
+ tags = json.dig("tags") || []
60
71
 
61
- basic = Analyzers::Basic.new(title: title, description: description, artifacts: artifacts, tags: tags)
62
- run_analyzer basic
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
- puts "Warning: #{e}"
82
- puts e.backtrace.join('\n')
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
- SLACK_WEBHOOK_URL_KEY = "SLACK_WEBHOOK_URL"
104
- SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
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
- slack_webhook_url?
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 = ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  end
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.19"
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.0
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-12 00:00:00.000000000 Z
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.19'
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.19'
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