mihari 1.4.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  4. data/.github/workflows/test.yml +68 -0
  5. data/.rubocop.yml +6 -0
  6. data/.standard.yml +4 -0
  7. data/README.md +24 -270
  8. data/Rakefile +1 -0
  9. data/bin/console +1 -0
  10. data/build_frontend.sh +14 -0
  11. data/docker/Dockerfile +5 -3
  12. data/examples/ipinfo_hosted_domains.rb +1 -1
  13. data/{screenshots → images}/alert.png +0 -0
  14. data/images/logo.png +0 -0
  15. data/{screenshots → images}/misp.png +0 -0
  16. data/{screenshots/eyecatch.png → images/overview.png} +0 -0
  17. data/{screenshots → images}/slack.png +0 -0
  18. data/images/web_alerts.png +0 -0
  19. data/images/web_config.png +0 -0
  20. data/lib/mihari.rb +2 -2
  21. data/lib/mihari/analyzers/base.rb +10 -1
  22. data/lib/mihari/analyzers/basic.rb +3 -4
  23. data/lib/mihari/analyzers/binaryedge.rb +4 -7
  24. data/lib/mihari/analyzers/censys.rb +3 -7
  25. data/lib/mihari/analyzers/circl.rb +6 -8
  26. data/lib/mihari/analyzers/crtsh.rb +2 -6
  27. data/lib/mihari/analyzers/dnpedia.rb +3 -6
  28. data/lib/mihari/analyzers/dnstwister.rb +4 -9
  29. data/lib/mihari/analyzers/free_text.rb +2 -6
  30. data/lib/mihari/analyzers/http_hash.rb +3 -11
  31. data/lib/mihari/analyzers/onyphe.rb +5 -8
  32. data/lib/mihari/analyzers/otx.rb +4 -9
  33. data/lib/mihari/analyzers/passive_dns.rb +4 -9
  34. data/lib/mihari/analyzers/passive_ssl.rb +4 -9
  35. data/lib/mihari/analyzers/passivetotal.rb +9 -14
  36. data/lib/mihari/analyzers/pulsedive.rb +7 -12
  37. data/lib/mihari/analyzers/reverse_whois.rb +4 -9
  38. data/lib/mihari/analyzers/securitytrails.rb +12 -17
  39. data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
  40. data/lib/mihari/analyzers/shodan.rb +9 -8
  41. data/lib/mihari/analyzers/spyse.rb +6 -11
  42. data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
  43. data/lib/mihari/analyzers/urlscan.rb +4 -12
  44. data/lib/mihari/analyzers/virustotal.rb +6 -11
  45. data/lib/mihari/analyzers/zoomeye.rb +7 -11
  46. data/lib/mihari/cli.rb +70 -300
  47. data/lib/mihari/commands/binaryedge.rb +21 -0
  48. data/lib/mihari/commands/censys.rb +22 -0
  49. data/lib/mihari/commands/circl.rb +21 -0
  50. data/lib/mihari/commands/config.rb +27 -0
  51. data/lib/mihari/commands/crtsh.rb +22 -0
  52. data/lib/mihari/commands/dnpedia.rb +21 -0
  53. data/lib/mihari/commands/dnstwister.rb +21 -0
  54. data/lib/mihari/commands/free_text.rb +21 -0
  55. data/lib/mihari/commands/http_hash.rb +25 -0
  56. data/lib/mihari/commands/json.rb +42 -0
  57. data/lib/mihari/commands/onyphe.rb +21 -0
  58. data/lib/mihari/commands/otx.rb +21 -0
  59. data/lib/mihari/commands/passive_dns.rb +21 -0
  60. data/lib/mihari/commands/passive_ssl.rb +21 -0
  61. data/lib/mihari/commands/passivetotal.rb +21 -0
  62. data/lib/mihari/commands/pulsedive.rb +21 -0
  63. data/lib/mihari/commands/reverse_whois.rb +21 -0
  64. data/lib/mihari/commands/securitytrails.rb +22 -0
  65. data/lib/mihari/commands/securitytrails_domain_feed.rb +23 -0
  66. data/lib/mihari/commands/shodan.rb +21 -0
  67. data/lib/mihari/commands/spyse.rb +22 -0
  68. data/lib/mihari/commands/ssh_fingerprint.rb +21 -0
  69. data/lib/mihari/commands/urlscan.rb +25 -0
  70. data/lib/mihari/commands/virustotal.rb +21 -0
  71. data/lib/mihari/commands/web.rb +22 -0
  72. data/lib/mihari/commands/zoomeye.rb +22 -0
  73. data/lib/mihari/config.rb +13 -25
  74. data/lib/mihari/configurable.rb +4 -5
  75. data/lib/mihari/database.rb +7 -1
  76. data/lib/mihari/emitters/misp.rb +4 -2
  77. data/lib/mihari/emitters/slack.rb +18 -7
  78. data/lib/mihari/emitters/the_hive.rb +1 -1
  79. data/lib/mihari/errors.rb +2 -0
  80. data/lib/mihari/models/alert.rb +51 -0
  81. data/lib/mihari/models/artifact.rb +14 -3
  82. data/lib/mihari/notifiers/exception_notifier.rb +1 -1
  83. data/lib/mihari/serializers/alert.rb +1 -1
  84. data/lib/mihari/serializers/artifact.rb +1 -1
  85. data/lib/mihari/serializers/tag.rb +1 -1
  86. data/lib/mihari/status.rb +6 -14
  87. data/lib/mihari/type_checker.rb +4 -4
  88. data/lib/mihari/version.rb +1 -1
  89. data/lib/mihari/web/app.rb +49 -0
  90. data/lib/mihari/web/controllers/alerts_controller.rb +66 -0
  91. data/lib/mihari/web/controllers/artifacts_controller.rb +26 -0
  92. data/lib/mihari/web/controllers/command_controller.rb +27 -0
  93. data/lib/mihari/web/controllers/config_controller.rb +15 -0
  94. data/lib/mihari/web/controllers/sources_controller.rb +14 -0
  95. data/lib/mihari/web/controllers/tags_controller.rb +30 -0
  96. data/lib/mihari/web/helpers/json.rb +51 -0
  97. data/lib/mihari/web/public/index.html +21 -0
  98. data/lib/mihari/web/public/redoc-static.html +519 -0
  99. data/lib/mihari/web/public/static/favicon.ico +0 -0
  100. data/lib/mihari/web/public/static/fonts/fa-brands-400.099a9556.woff +0 -0
  101. data/lib/mihari/web/public/static/fonts/fa-brands-400.30cc681d.eot +0 -0
  102. data/lib/mihari/web/public/static/fonts/fa-brands-400.3b89dd10.ttf +0 -0
  103. data/lib/mihari/web/public/static/fonts/fa-brands-400.f7307680.woff2 +0 -0
  104. data/lib/mihari/web/public/static/fonts/fa-regular-400.1f77739c.ttf +0 -0
  105. data/lib/mihari/web/public/static/fonts/fa-regular-400.7124eb50.woff +0 -0
  106. data/lib/mihari/web/public/static/fonts/fa-regular-400.7630483d.eot +0 -0
  107. data/lib/mihari/web/public/static/fonts/fa-regular-400.f0f82301.woff2 +0 -0
  108. data/lib/mihari/web/public/static/fonts/fa-solid-900.1042e8ca.eot +0 -0
  109. data/lib/mihari/web/public/static/fonts/fa-solid-900.605ed792.ttf +0 -0
  110. data/lib/mihari/web/public/static/fonts/fa-solid-900.9fe5a17c.woff +0 -0
  111. data/lib/mihari/web/public/static/fonts/fa-solid-900.e8a427e1.woff2 +0 -0
  112. data/lib/mihari/web/public/static/img/fa-brands-400.ba7ed552.svg +3717 -0
  113. data/lib/mihari/web/public/static/img/fa-regular-400.0bb42845.svg +801 -0
  114. data/lib/mihari/web/public/static/img/fa-solid-900.376c1f97.svg +5034 -0
  115. data/lib/mihari/web/public/static/js/app.bcc595df.js +12 -0
  116. data/lib/mihari/web/public/static/js/app.bcc595df.js.map +1 -0
  117. data/mihari.gemspec +28 -21
  118. metadata +217 -45
  119. data/.travis.yml +0 -13
  120. data/lib/mihari/alert_viewer.rb +0 -23
@@ -1,12 +1,15 @@
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)
7
10
  return if record.data_type
8
11
 
9
- record.errors[:data] << "#{record.data} is not supported"
12
+ record.errors.add :data, "#{record.data} is not supported"
10
13
  end
11
14
  end
12
15
 
@@ -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
@@ -19,7 +19,7 @@ module Mihari
19
19
  def notify(exception)
20
20
  notify_to_stdout exception
21
21
 
22
- clean_message = exception.message.tr('`', "'")
22
+ clean_message = exception.message.tr("`", "'")
23
23
  attachments = to_attachments(exception, clean_message)
24
24
  notify_to_slack(text: clean_message, attachments: attachments) if @slack.valid?
25
25
  end
@@ -4,7 +4,7 @@ require "active_model_serializers"
4
4
 
5
5
  module Mihari
6
6
  class AlertSerializer < ActiveModel::Serializer
7
- attributes :title, :description, :source, :created_at
7
+ attributes :id, :title, :description, :source, :created_at
8
8
 
9
9
  has_many :artifacts
10
10
  has_many :tags, through: :taggings
@@ -4,6 +4,6 @@ require "active_model_serializers"
4
4
 
5
5
  module Mihari
6
6
  class ArtifactSerializer < ActiveModel::Serializer
7
- attributes :data, :data_type
7
+ attributes :id, :data, :data_type
8
8
  end
9
9
  end
@@ -4,6 +4,6 @@ require "active_model_serializers"
4
4
 
5
5
  module Mihari
6
6
  class TagSerializer < ActiveModel::Serializer
7
- attributes :name
7
+ attributes :id, :name
8
8
  end
9
9
  end
data/lib/mihari/status.rb CHANGED
@@ -3,9 +3,7 @@
3
3
  module Mihari
4
4
  class Status
5
5
  def check
6
- statuses.map do |key, value|
7
- [key, convert(**value)]
8
- end.to_h
6
+ statuses
9
7
  end
10
8
 
11
9
  def self.check
@@ -14,16 +12,9 @@ module Mihari
14
12
 
15
13
  private
16
14
 
17
- def convert(status:, message:)
18
- {
19
- status: status ? "OK" : "Bad",
20
- message: message
21
- }
22
- end
23
-
24
15
  def statuses
25
16
  (Mihari.analyzers + Mihari.emitters).map do |klass|
26
- name = klass.to_s.downcase.split("::").last.to_s
17
+ name = klass.to_s.split("::").last.to_s
27
18
 
28
19
  [name, build_status(klass)]
29
20
  end.to_h.compact
@@ -33,10 +24,11 @@ module Mihari
33
24
  is_analyzer = klass.ancestors.include?(Mihari::Analyzers::Base)
34
25
 
35
26
  instance = is_analyzer ? klass.new("dummy") : klass.new
36
- status = instance.configured?
37
- message = instance.configuration_status
27
+ is_configured = instance.configured?
28
+ values = instance.configuration_values
29
+ type = is_analyzer ? "Analyzer" : "Emitter"
38
30
 
39
- message ? { status: status, message: message } : nil
31
+ values ? { is_configured: is_configured, values: values, type: type } : nil
40
32
  rescue ArgumentError => _e
41
33
  nil
42
34
  end
@@ -80,22 +80,22 @@ module Mihari
80
80
 
81
81
  # @return [true, false]
82
82
  def md5?
83
- data.match? /^[A-Fa-f0-9]{32}$/
83
+ data.match?(/^[A-Fa-f0-9]{32}$/)
84
84
  end
85
85
 
86
86
  # @return [true, false]
87
87
  def sha1?
88
- data.match? /^[A-Fa-f0-9]{40}$/
88
+ data.match?(/^[A-Fa-f0-9]{40}$/)
89
89
  end
90
90
 
91
91
  # @return [true, false]
92
92
  def sha256?
93
- data.match? /^[A-Fa-f0-9]{64}$/
93
+ data.match?(/^[A-Fa-f0-9]{64}$/)
94
94
  end
95
95
 
96
96
  # @return [true, false]
97
97
  def sha512?
98
- data.match? /^[A-Fa-f0-9]{128}$/
98
+ data.match?(/^[A-Fa-f0-9]{128}$/)
99
99
  end
100
100
  end
101
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "1.4.1"
4
+ VERSION = "2.2.0"
5
5
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "launchy"
4
+ require "rack"
5
+ require "rack/handler/puma"
6
+ require "sinatra"
7
+
8
+ require "mihari/web/helpers/json"
9
+
10
+ require "mihari/web/controllers/alerts_controller"
11
+ require "mihari/web/controllers/artifacts_controller"
12
+ require "mihari/web/controllers/command_controller"
13
+ require "mihari/web/controllers/config_controller"
14
+ require "mihari/web/controllers/sources_controller"
15
+ require "mihari/web/controllers/tags_controller"
16
+
17
+ module Mihari
18
+ class App < Sinatra::Base
19
+ set :root, File.dirname(__FILE__)
20
+ set :public_folder, File.join(root, "public")
21
+
22
+ get "/" do
23
+ send_file File.join(settings.public_folder, "index.html")
24
+ end
25
+
26
+ use Mihari::Controllers::AlertsController
27
+ use Mihari::Controllers::ArtifactsController
28
+ use Mihari::Controllers::CommandController
29
+ use Mihari::Controllers::ConfigController
30
+ use Mihari::Controllers::SourcesController
31
+ use Mihari::Controllers::TagsController
32
+
33
+ class << self
34
+ def run!(port: 9292, host: "localhost")
35
+ url = "http://#{host}:#{port}"
36
+
37
+ Rack::Handler::Puma.run self, Port: port, Host: host do |server|
38
+ Launchy.open url
39
+
40
+ [:INT, :TERM].each do |sig|
41
+ trap(sig) do
42
+ server.shutdown
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra"
4
+
5
+ module Mihari
6
+ module Controllers
7
+ class AlertsController < Sinatra::Base
8
+ get "/api/alerts" do
9
+ page = params["page"] || 1
10
+ page = page.to_i
11
+ limit = 10
12
+
13
+ artifact_data = params["artifact"]
14
+ description = params["description"]
15
+ source = params["source"]
16
+ tag_name = params["tag"]
17
+ title = params["title"]
18
+
19
+ from_at = params["from_at"] || params["fromAt"]
20
+ from_at = DateTime.parse(from_at) if from_at
21
+ to_at = params["to_at"] || params["toAt"]
22
+ to_at = DateTime.parse(to_at) if to_at
23
+
24
+ alerts = Mihari::Alert.search(
25
+ artifact_data: artifact_data,
26
+ description: description,
27
+ from_at: from_at,
28
+ limit: limit,
29
+ page: page,
30
+ source: source,
31
+ tag_name: tag_name,
32
+ title: title,
33
+ to_at: to_at
34
+ )
35
+ total = Mihari::Alert.count(
36
+ artifact_data: artifact_data,
37
+ description: description,
38
+ from_at: from_at,
39
+ source: source,
40
+ tag_name: tag_name,
41
+ title: title,
42
+ to_at: to_at
43
+ )
44
+
45
+ json({ alerts: alerts, total: total, current_page: page, page_size: limit })
46
+ end
47
+
48
+ delete "/api/alerts/:id" do
49
+ id = params["id"]
50
+ id = id.to_i
51
+
52
+ begin
53
+ alert = Mihari::Alert.find(id)
54
+ alert.destroy
55
+
56
+ status 204
57
+ body ""
58
+ rescue ActiveRecord::RecordNotFound
59
+ status 404
60
+
61
+ json({ message: "ID:#{id} is not found" })
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra"
4
+
5
+ module Mihari
6
+ module Controllers
7
+ class ArtifactsController < Sinatra::Base
8
+ delete "/api/artifacts/:id" do
9
+ id = params["id"]
10
+ id = id.to_i
11
+
12
+ begin
13
+ alert = Mihari::Artifact.find(id)
14
+ alert.delete
15
+
16
+ status 204
17
+ body ""
18
+ rescue ActiveRecord::RecordNotFound
19
+ status 404
20
+
21
+ json({ message: "ID:#{id} is not found" })
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "safe_shell"
4
+ require "sinatra"
5
+
6
+ module Mihari
7
+ module Controllers
8
+ class CommandController < Sinatra::Base
9
+ post "/api/command" do
10
+ payload = JSON.parse(request.body.read)
11
+
12
+ command = payload["command"]
13
+ if command.nil?
14
+ status 400
15
+ return json({ message: "command is required" })
16
+ end
17
+
18
+ command = command.split
19
+
20
+ output = SafeShell.execute("mihari", *command)
21
+ success = $?.success?
22
+
23
+ json({ output: output, success: success })
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra"
4
+
5
+ module Mihari
6
+ module Controllers
7
+ class ConfigController < Sinatra::Base
8
+ get "/api/config" do
9
+ report = Status.check
10
+
11
+ json report.to_camelback_keys
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra"
4
+
5
+ module Mihari
6
+ module Controllers
7
+ class SourcesController < Sinatra::Base
8
+ get "/api/sources" do
9
+ tags = Mihari::Alert.distinct.pluck(:source)
10
+ json tags
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra"
4
+
5
+ module Mihari
6
+ module Controllers
7
+ class TagsController < Sinatra::Base
8
+ get "/api/tags" do
9
+ tags = Mihari::Tag.distinct.pluck(:name)
10
+ json tags
11
+ end
12
+
13
+ delete "/api/tags/:name" do
14
+ name = params["name"]
15
+
16
+ begin
17
+ Mihari::Tag.where(name: name).destroy_all
18
+
19
+ status 204
20
+ body ""
21
+ rescue ActiveRecord::RecordNotFound
22
+ status 404
23
+
24
+ message = { message: "Name:#{name} is not found" }
25
+ json message
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ require "awrence"
2
+ require "multi_json"
3
+ require "sinatra/base"
4
+
5
+ module Sinatra
6
+ module JSON
7
+ class << self
8
+ def encode(object)
9
+ ::MultiJson.dump(object)
10
+ end
11
+ end
12
+
13
+ def json(object, options = {})
14
+ object = object.to_camelback_keys
15
+
16
+ content_type resolve_content_type(options)
17
+ resolve_encoder_action object, resolve_encoder(options)
18
+ end
19
+
20
+ private
21
+
22
+ def resolve_content_type(options = {})
23
+ options[:content_type] || settings.json_content_type
24
+ end
25
+
26
+ def resolve_encoder(options = {})
27
+ options[:json_encoder] || settings.json_encoder
28
+ end
29
+
30
+ def resolve_encoder_action(object, encoder)
31
+ [:encode, :generate].each do |method|
32
+ return encoder.send(method, object) if encoder.respond_to? method
33
+ end
34
+
35
+ if encoder.is_a? Symbol
36
+ object.__send__(encoder)
37
+ else
38
+ fail "#{encoder} does not respond to #generate nor #encode"
39
+ end
40
+ end
41
+ end
42
+
43
+ Base.set :json_encoder do
44
+ ::MultiJson
45
+ end
46
+
47
+ Base.set :json_content_type, :json
48
+
49
+ # Load the JSON helpers in modular style automatically
50
+ Base.helpers JSON
51
+ end