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 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