mihari 2.1.0 → 2.4.0

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/README.md +8 -0
  4. data/bin/console +1 -0
  5. data/docker/Dockerfile +1 -1
  6. data/examples/ipinfo_hosted_domains.rb +1 -1
  7. data/images/{eyecatch.png → overview.png} +0 -0
  8. data/images/tines.png +0 -0
  9. data/images/web_alerts.png +0 -0
  10. data/images/web_config.png +0 -0
  11. data/lib/mihari.rb +1 -0
  12. data/lib/mihari/analyzers/base.rb +10 -1
  13. data/lib/mihari/analyzers/circl.rb +3 -3
  14. data/lib/mihari/analyzers/onyphe.rb +2 -2
  15. data/lib/mihari/analyzers/urlscan.rb +1 -6
  16. data/lib/mihari/analyzers/zoomeye.rb +2 -2
  17. data/lib/mihari/cli.rb +12 -3
  18. data/lib/mihari/commands/config.rb +7 -1
  19. data/lib/mihari/commands/dnstwister.rb +2 -0
  20. data/lib/mihari/commands/json.rb +6 -0
  21. data/lib/mihari/commands/onyphe.rb +2 -0
  22. data/lib/mihari/commands/passive_dns.rb +2 -0
  23. data/lib/mihari/commands/securitytrails.rb +2 -0
  24. data/lib/mihari/commands/shodan.rb +2 -0
  25. data/lib/mihari/commands/spyse.rb +2 -0
  26. data/lib/mihari/commands/urlscan.rb +2 -2
  27. data/lib/mihari/commands/virustotal.rb +2 -0
  28. data/lib/mihari/commands/web.rb +2 -0
  29. data/lib/mihari/commands/zoomeye.rb +2 -0
  30. data/lib/mihari/config.rb +5 -4
  31. data/lib/mihari/emitters/slack.rb +0 -3
  32. data/lib/mihari/emitters/webhook.rb +60 -0
  33. data/lib/mihari/models/artifact.rb +13 -2
  34. data/lib/mihari/notifiers/slack.rb +0 -1
  35. data/lib/mihari/status.rb +1 -9
  36. data/lib/mihari/version.rb +1 -1
  37. data/lib/mihari/web/app.rb +22 -137
  38. data/lib/mihari/web/controllers/alerts_controller.rb +75 -0
  39. data/lib/mihari/web/controllers/artifacts_controller.rb +24 -0
  40. data/lib/mihari/web/controllers/base_controller.rb +22 -0
  41. data/lib/mihari/web/controllers/command_controller.rb +26 -0
  42. data/lib/mihari/web/controllers/config_controller.rb +13 -0
  43. data/lib/mihari/web/controllers/sources_controller.rb +12 -0
  44. data/lib/mihari/web/controllers/tags_controller.rb +28 -0
  45. data/lib/mihari/web/helpers/json.rb +53 -0
  46. data/lib/mihari/web/public/index.html +2 -2
  47. data/lib/mihari/web/public/redoc-static.html +519 -0
  48. data/lib/mihari/web/public/static/js/{app.280cbdb7.js → app.cccddb2b.js} +3 -3
  49. data/lib/mihari/web/public/static/js/app.cccddb2b.js.map +1 -0
  50. data/mihari.gemspec +10 -6
  51. metadata +96 -30
  52. data/lib/mihari/slack_monkeypatch.rb +0 -16
  53. data/lib/mihari/web/public/static/js/app.280cbdb7.js.map +0 -1
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record"
4
+ require "active_record/filter"
5
+ require "active_support/core_ext/integer/time"
6
+ require "active_support/core_ext/numeric/time"
4
7
 
5
8
  class ArtifactValidator < ActiveModel::Validator
6
9
  def validate(record)
@@ -20,8 +23,16 @@ module Mihari
20
23
  self.data_type = TypeChecker.type(data)
21
24
  end
22
25
 
23
- def unique?
24
- self.class.find_by(data: data).nil?
26
+ def unique?(ignore_old_artifacts: false, ignore_threshold: 0)
27
+ artifact = self.class.where(data: data).order(created_at: :desc).first
28
+ return true if artifact.nil?
29
+
30
+ return false unless ignore_old_artifacts
31
+
32
+ days_before = (-ignore_threshold).days.from_now
33
+ # if an artifact is created before {ignore_threshold} days, ignore it
34
+ # within {ignore_threshold} days, do not ignore it
35
+ artifact.created_at < days_before
25
36
  end
26
37
  end
27
38
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "slack-notifier"
4
- require "mihari/slack_monkeypatch"
5
4
 
6
5
  module Mihari
7
6
  module Notifiers
data/lib/mihari/status.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Mihari
4
4
  class Status
5
5
  def check
6
- statuses.transform_values { |value| convert(**value) }
6
+ statuses
7
7
  end
8
8
 
9
9
  def self.check
@@ -12,14 +12,6 @@ module Mihari
12
12
 
13
13
  private
14
14
 
15
- def convert(is_configured:, values:, type:)
16
- {
17
- is_configured: is_configured,
18
- values: values,
19
- type: type
20
- }
21
- end
22
-
23
15
  def statuses
24
16
  (Mihari.analyzers + Mihari.emitters).map do |klass|
25
17
  name = klass.to_s.split("::").last.to_s
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "2.1.0"
4
+ VERSION = "2.4.0"
5
5
  end
@@ -1,16 +1,23 @@
1
- require "awrence"
2
- require "colorize"
1
+ # frozen_string_literal: true
2
+
3
3
  require "launchy"
4
4
  require "rack"
5
- require "safe_shell"
5
+ require "rack/handler/puma"
6
6
  require "sinatra"
7
- require "sinatra/json"
8
- require "sinatra/reloader"
7
+
8
+ require "mihari/web/helpers/json"
9
+
10
+ require "mihari/web/controllers/base_controller"
11
+
12
+ require "mihari/web/controllers/alerts_controller"
13
+ require "mihari/web/controllers/artifacts_controller"
14
+ require "mihari/web/controllers/command_controller"
15
+ require "mihari/web/controllers/config_controller"
16
+ require "mihari/web/controllers/sources_controller"
17
+ require "mihari/web/controllers/tags_controller"
9
18
 
10
19
  module Mihari
11
20
  class App < Sinatra::Base
12
- register Sinatra::Reloader
13
-
14
21
  set :root, File.dirname(__FILE__)
15
22
  set :public_folder, File.join(root, "public")
16
23
 
@@ -18,140 +25,18 @@ module Mihari
18
25
  send_file File.join(settings.public_folder, "index.html")
19
26
  end
20
27
 
21
- get "/api/alerts" do
22
- page = params["page"] || 1
23
- page = page.to_i
24
- limit = 10
25
-
26
- artifact_data = params["artifact"]
27
- description = params["description"]
28
- source = params["source"]
29
- tag_name = params["tag"]
30
- title = params["title"]
31
-
32
- from_at = params["from_at"] || params["fromAt"]
33
- from_at = DateTime.parse(from_at) if from_at
34
- to_at = params["to_at"] || params["toAt"]
35
- to_at = DateTime.parse(to_at) if to_at
36
-
37
- alerts = Mihari::Alert.search(
38
- artifact_data: artifact_data,
39
- description: description,
40
- from_at: from_at,
41
- limit: limit,
42
- page: page,
43
- source: source,
44
- tag_name: tag_name,
45
- title: title,
46
- to_at: to_at
47
- )
48
- total = Mihari::Alert.count(
49
- artifact_data: artifact_data,
50
- description: description,
51
- from_at: from_at,
52
- source: source,
53
- tag_name: tag_name,
54
- title: title,
55
- to_at: to_at
56
- )
57
-
58
- json = { alerts: alerts, total: total, current_page: page, page_size: limit }
59
- json json.to_camelback_keys
60
- end
61
-
62
- delete "/api/alerts/:id" do
63
- id = params["id"]
64
- id = id.to_i
65
-
66
- begin
67
- alert = Mihari::Alert.find(id)
68
- alert.destroy
69
-
70
- status 204
71
- body ""
72
- rescue ActiveRecord::RecordNotFound
73
- status 404
74
-
75
- message = { message: "ID:#{id} is not found" }
76
- json message
77
- end
78
- end
79
-
80
- delete "/api/artifacts/:id" do
81
- id = params["id"]
82
- id = id.to_i
83
-
84
- begin
85
- alert = Mihari::Artifact.find(id)
86
- alert.delete
87
-
88
- status 204
89
- body ""
90
- rescue ActiveRecord::RecordNotFound
91
- status 404
92
-
93
- message = { message: "ID:#{id} is not found" }
94
- json message
95
- end
96
- end
97
-
98
- delete "/api/tags/:name" do
99
- name = params["name"]
100
-
101
- begin
102
- Mihari::Tag.where(name: name).destroy_all
103
-
104
- status 204
105
- body ""
106
- rescue ActiveRecord::RecordNotFound
107
- status 404
108
-
109
- message = { message: "Name:#{name} is not found" }
110
- json message
111
- end
112
- end
113
-
114
- get "/api/sources" do
115
- tags = Mihari::Alert.distinct.pluck(:source)
116
- json tags
117
- end
118
-
119
- get "/api/tags" do
120
- tags = Mihari::Tag.distinct.pluck(:name)
121
- json tags
122
- end
123
-
124
- get "/api/config" do
125
- report = Status.check
126
-
127
- json report.to_camelback_keys
128
- end
129
-
130
- post "/api/command" do
131
- payload = JSON.parse(request.body.read)
132
-
133
- command = payload["command"]
134
- if command.nil?
135
- status 400
136
- return json( { message: "command is required" })
137
- end
138
-
139
- command = command.split
140
-
141
- output = SafeShell.execute("mihari", *command)
142
- success = $?.success?
143
-
144
- json({ output: output, success: success })
145
- end
28
+ use Mihari::Controllers::AlertsController
29
+ use Mihari::Controllers::ArtifactsController
30
+ use Mihari::Controllers::CommandController
31
+ use Mihari::Controllers::ConfigController
32
+ use Mihari::Controllers::SourcesController
33
+ use Mihari::Controllers::TagsController
146
34
 
147
35
  class << self
148
36
  def run!(port: 9292, host: "localhost")
149
- url = "http://#{host}:#{port}"
150
-
151
- puts "The app will be available at #{url}.".colorize(:blue)
152
- puts "(Press Ctrl+C to quit)".colorize(:blue)
37
+ url = "http://#{host}:#{port}"
153
38
 
154
- Rack::Handler::WEBrick.run self, Port: port, Host: host do |server|
39
+ Rack::Handler::Puma.run self, Port: port, Host: host do |server|
155
40
  Launchy.open url
156
41
 
157
42
  [:INT, :TERM].each do |sig|
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Controllers
5
+ class AlertsController < BaseController
6
+ get "/api/alerts" do
7
+ param :page, Integer
8
+ param :artifact, String
9
+ param :description, String
10
+ param :source, String
11
+ param :tag, String
12
+
13
+ param :from_at, DateTime
14
+ param :fromAt, DateTime
15
+ param :to_at, DateTime
16
+ param :toAt, DateTime
17
+
18
+ page = params["page"] || 1
19
+ page = page.to_i
20
+ limit = 10
21
+
22
+ artifact_data = params["artifact"]
23
+ description = params["description"]
24
+ source = params["source"]
25
+ tag_name = params["tag"]
26
+ title = params["title"]
27
+
28
+ from_at = params["from_at"] || params["fromAt"]
29
+ from_at = DateTime.parse(from_at) if from_at
30
+ to_at = params["to_at"] || params["toAt"]
31
+ to_at = DateTime.parse(to_at) if to_at
32
+
33
+ alerts = Mihari::Alert.search(
34
+ artifact_data: artifact_data,
35
+ description: description,
36
+ from_at: from_at,
37
+ limit: limit,
38
+ page: page,
39
+ source: source,
40
+ tag_name: tag_name,
41
+ title: title,
42
+ to_at: to_at
43
+ )
44
+ total = Mihari::Alert.count(
45
+ artifact_data: artifact_data,
46
+ description: description,
47
+ from_at: from_at,
48
+ source: source,
49
+ tag_name: tag_name,
50
+ title: title,
51
+ to_at: to_at
52
+ )
53
+
54
+ json({ alerts: alerts, total: total, current_page: page, page_size: limit })
55
+ end
56
+
57
+ delete "/api/alerts/:id" do
58
+ id = params["id"]
59
+ id = id.to_i
60
+
61
+ begin
62
+ alert = Mihari::Alert.find(id)
63
+ alert.destroy
64
+
65
+ status 204
66
+ body ""
67
+ rescue ActiveRecord::RecordNotFound
68
+ status 404
69
+
70
+ json({ message: "ID:#{id} is not found" })
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Controllers
5
+ class ArtifactsController < BaseController
6
+ delete "/api/artifacts/:id" do
7
+ id = params["id"]
8
+ id = id.to_i
9
+
10
+ begin
11
+ alert = Mihari::Artifact.find(id)
12
+ alert.delete
13
+
14
+ status 204
15
+ body ""
16
+ rescue ActiveRecord::RecordNotFound
17
+ status 404
18
+
19
+ json({ message: "ID:#{id} is not found" })
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/contrib/json_body_parser"
4
+ require "sinatra"
5
+ require "sinatra/param"
6
+
7
+ module Mihari
8
+ module Controllers
9
+ class BaseController < Sinatra::Base
10
+ helpers Sinatra::Param
11
+
12
+ use Rack::JSONBodyParser
13
+
14
+ set :show_exceptions, false
15
+ set :raise_sinatra_param_exceptions, true
16
+
17
+ error Sinatra::Param::InvalidParameterError do
18
+ json({ error: "#{env['sinatra.error'].param} is invalid" })
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "safe_shell"
4
+
5
+ module Mihari
6
+ module Controllers
7
+ class CommandController < BaseController
8
+ post "/api/command" do
9
+ param :command, String, required: true
10
+
11
+ command = params["command"]
12
+ if command.nil?
13
+ status 400
14
+ return json({ message: "command is required" })
15
+ end
16
+
17
+ command = command.split
18
+
19
+ output = SafeShell.execute("mihari", *command)
20
+ success = $?.success?
21
+
22
+ json({ output: output, success: success })
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Controllers
5
+ class ConfigController < BaseController
6
+ get "/api/config" do
7
+ report = Status.check
8
+
9
+ json report.to_camelback_keys
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Controllers
5
+ class SourcesController < BaseController
6
+ get "/api/sources" do
7
+ tags = Mihari::Alert.distinct.pluck(:source)
8
+ json tags
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Controllers
5
+ class TagsController < BaseController
6
+ get "/api/tags" do
7
+ tags = Mihari::Tag.distinct.pluck(:name)
8
+ json tags
9
+ end
10
+
11
+ delete "/api/tags/:name" do
12
+ name = params["name"]
13
+
14
+ begin
15
+ Mihari::Tag.where(name: name).destroy_all
16
+
17
+ status 204
18
+ body ""
19
+ rescue ActiveRecord::RecordNotFound
20
+ status 404
21
+
22
+ message = { message: "Name:#{name} is not found" }
23
+ json message
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end