mihari 2.1.0 → 2.4.0

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