mihari 4.4.1 → 4.5.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/commands/web.rb +1 -1
  3. data/lib/mihari/database.rb +22 -1
  4. data/lib/mihari/enrichers/shodan.rb +36 -0
  5. data/lib/mihari/entities/artifact.rb +6 -0
  6. data/lib/mihari/entities/cpe.rb +9 -0
  7. data/lib/mihari/entities/port.rb +9 -0
  8. data/lib/mihari/models/alert.rb +3 -1
  9. data/lib/mihari/models/artifact.rb +31 -1
  10. data/lib/mihari/models/cpe.rb +23 -0
  11. data/lib/mihari/models/port.rb +23 -0
  12. data/lib/mihari/models/reverse_dns.rb +4 -4
  13. data/lib/mihari/structs/shodan.rb +25 -0
  14. data/lib/mihari/version.rb +1 -1
  15. data/lib/mihari/web/api.rb +0 -2
  16. data/lib/mihari/web/endpoints/artifacts.rb +3 -1
  17. data/lib/mihari/web/public/index.html +1 -1
  18. data/lib/mihari/web/public/redoc-static.html +18 -22
  19. data/lib/mihari/web/public/static/css/{app.de5845d8.css → app.2a5d3d21.css} +1 -1
  20. data/lib/mihari/web/public/static/js/app-legacy.c3595dce.js +2 -0
  21. data/lib/mihari/web/public/static/js/app-legacy.c3595dce.js.map +1 -0
  22. data/lib/mihari/web/public/static/js/app.afd5025f.js +2 -0
  23. data/lib/mihari/web/public/static/js/app.afd5025f.js.map +1 -0
  24. data/lib/mihari.rb +5 -1
  25. data/mihari.gemspec +0 -1
  26. data/sig/lib/mihari/models/artifact.rbs +2 -0
  27. data/sig/lib/mihari/models/cpe.rbs +7 -0
  28. data/sig/lib/mihari/models/port.rbs +7 -0
  29. metadata +14 -23
  30. data/lib/mihari/entities/command.rb +0 -14
  31. data/lib/mihari/web/endpoints/command.rb +0 -33
  32. data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js +0 -2
  33. data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js.map +0 -1
  34. data/lib/mihari/web/public/static/js/app.40749592.js +0 -2
  35. data/lib/mihari/web/public/static/js/app.40749592.js.map +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15a224f944350480b79957d50a30742b9c4a0597c6dc8765e80269104175cab5
4
- data.tar.gz: d873df7d4c4d30f39f62674a34bd1a28a4737d2183f92acc0f4243de2dd9eacf
3
+ metadata.gz: dfd4bafdc33911546dce3477184426c581b9e3a40a762f32d1944870589ffcc9
4
+ data.tar.gz: 8765422118bcc6fe914439b416e6362551c9de0492397ce90c2c5bf7487d982a
5
5
  SHA512:
6
- metadata.gz: 9f538f70afd329f84c3962c8ec6c7693d8656fc105dcf0f9f30145ec5e74337c9414fdd5804255a7105105a14246e223bc5d8660980af2407db1c0595b4b40aa
7
- data.tar.gz: ce2bec572a183d29c184b188f9314f714446a67c7599ba4855de566120351b2041c6b9f5a98b7b972aa4aa960a6ea233ac7c6509b1fdbbb081a5a08e22ab51c2
6
+ metadata.gz: a0788f87ab0ef4838243e25ca71496408187add826900d0428af6f4a8e7f81093e81944d809425b2c716c778c35b725f757b656cec046adc2294b4a07d764b4f
7
+ data.tar.gz: 3c0c001e91aaec0090daeb117a7595a84172a355d527af0dab7f81dfee557b271697a0fbcf75c8a44d5e33b6fb1ff1f8b82e0c057dff91e85b4fb4a0a30d69ca
@@ -8,7 +8,7 @@ module Mihari
8
8
  desc "web", "Launch the web app"
9
9
  method_option :port, type: :numeric, default: 9292, desc: "Hostname to listen on"
10
10
  method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
11
- method_option :threads, type: :string, default: "0:16", desc: "min:max threads to use"
11
+ method_option :threads, type: :string, default: "1:1", desc: "min:max threads to use"
12
12
  method_option :verbose, type: :boolean, default: true, desc: "Report each request"
13
13
  def web
14
14
  port = options["port"]
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Make possible to use upper case acronyms in class names
4
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
5
+ inflect.acronym "CPE"
6
+ end
7
+
3
8
  def env
4
9
  ENV["APP_ENV"] || ENV["RACK_ENV"]
5
10
  end
@@ -118,6 +123,20 @@ class AddYAMLToRulesSchema < ActiveRecord::Migration[7.0]
118
123
  end
119
124
  end
120
125
 
126
+ class EnrichmentsV45Schema < ActiveRecord::Migration[7.0]
127
+ def change
128
+ create_table :cpes, if_not_exists: true do |t|
129
+ t.string :cpe, null: false
130
+ t.belongs_to :artifact, foreign_key: true
131
+ end
132
+
133
+ create_table :ports, if_not_exists: true do |t|
134
+ t.integer :port, null: false
135
+ t.belongs_to :artifact, foreign_key: true
136
+ end
137
+ end
138
+ end
139
+
121
140
  def adapter
122
141
  return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
123
142
  return "mysql2" if Mihari.config.database.start_with?("mysql2://")
@@ -147,7 +166,9 @@ module Mihari
147
166
  RuleSchema,
148
167
  AddeMetadataToArtifactSchema,
149
168
  # v4.4
150
- AddYAMLToRulesSchema
169
+ AddYAMLToRulesSchema,
170
+ # v4.5
171
+ EnrichmentsV45Schema
151
172
  ].each { |schema| schema.migrate direction }
152
173
  end
153
174
  memoize :migrate unless test_env?
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/https"
4
+
5
+ module Mihari
6
+ module Enrichers
7
+ class Shodan < Base
8
+ # @return [Boolean]
9
+ def valid?
10
+ true
11
+ end
12
+
13
+ class << self
14
+ include Memist::Memoizable
15
+
16
+ #
17
+ # Query Shodan Internet DB
18
+ #
19
+ # @param [String] ip
20
+ #
21
+ # @return [Mihari::Structs::Shodan::InternetDBResponse, nil]
22
+ #
23
+ def query(ip)
24
+ url = "https://internetdb.shodan.io/#{ip}"
25
+ res = HTTP.get(url)
26
+ data = JSON.parse(res.body.to_s)
27
+
28
+ Structs::Shodan::InternetDBResponse.from_dynamic! data
29
+ rescue HttpError
30
+ nil
31
+ end
32
+ memoize :query
33
+ end
34
+ end
35
+ end
36
+ end
@@ -21,6 +21,12 @@ module Mihari
21
21
  expose :dns_records, using: Entities::DnsRecord, documentation: { type: Entities::DnsRecord, is_array: true, required: false }, as: :dnsRecords do |status, _options|
22
22
  status.dns_records.empty? ? nil : status.dns_records
23
23
  end
24
+ expose :ceps, using: Entities::CPE, documentation: { type: Entities::CPE, is_array: true, required: false }, as: :cpes do |status, _options|
25
+ status.cpes.empty? ? nil : status.cpes
26
+ end
27
+ expose :ports, using: Entities::Port, documentation: { type: Entities::Port, is_array: true, required: false }, as: :ports do |status, _options|
28
+ status.ports.empty? ? nil : status.ports
29
+ end
24
30
  end
25
31
  end
26
32
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Entities
5
+ class CPE < Grape::Entity
6
+ expose :cpe, documentation: { type: String, required: true }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Entities
5
+ class Port < Grape::Entity
6
+ expose :port, documentation: { type: Integer, required: true }
7
+ end
8
+ end
9
+ end
@@ -35,7 +35,9 @@ module Mihari
35
35
  :geolocation,
36
36
  :whois_record,
37
37
  :dns_records,
38
- :reverse_dns_names
38
+ :reverse_dns_names,
39
+ :cpes,
40
+ :ports
39
41
  ]
40
42
  },
41
43
  :tags
@@ -16,7 +16,9 @@ module Mihari
16
16
  has_one :geolocation, dependent: :destroy
17
17
  has_one :whois_record, dependent: :destroy
18
18
 
19
+ has_many :cpes, dependent: :destroy
19
20
  has_many :dns_records, dependent: :destroy
21
+ has_many :ports, dependent: :destroy
20
22
  has_many :reverse_dns_names, dependent: :destroy
21
23
 
22
24
  include ActiveModel::Validations
@@ -94,7 +96,7 @@ module Mihari
94
96
  end
95
97
 
96
98
  #
97
- # Enrich(add) geolocation
99
+ # Enrich AS
98
100
  #
99
101
  def enrich_autonomous_system
100
102
  return unless can_enrich_autonomous_system?
@@ -102,6 +104,24 @@ module Mihari
102
104
  self.autonomous_system = AutonomousSystem.build_by_ip(data)
103
105
  end
104
106
 
107
+ #
108
+ # Enrich ports
109
+ #
110
+ def enrich_ports
111
+ return unless can_enrich_ports?
112
+
113
+ self.ports = Port.build_by_ip(data)
114
+ end
115
+
116
+ #
117
+ # Enrich CPEs
118
+ #
119
+ def enrich_cpes
120
+ return unless can_enrich_cpes?
121
+
122
+ self.cpes = CPE.build_by_ip(data)
123
+ end
124
+
105
125
  #
106
126
  # Enrich all the enrichable relationships of the artifact
107
127
  #
@@ -111,6 +131,8 @@ module Mihari
111
131
  enrich_geolocation
112
132
  enrich_reverse_dns
113
133
  enrich_whois
134
+ enrich_ports
135
+ enrich_cpes
114
136
  end
115
137
 
116
138
  private
@@ -140,5 +162,13 @@ module Mihari
140
162
  def can_enrich_autonomous_system?
141
163
  data_type == "ip" && autonomous_system.nil?
142
164
  end
165
+
166
+ def can_enrich_ports?
167
+ data_type == "ip" && ports.empty?
168
+ end
169
+
170
+ def can_enrich_cpes?
171
+ data_type == "ip" && cpes.empty?
172
+ end
143
173
  end
144
174
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ class CPE < ActiveRecord::Base
5
+ belongs_to :artifact
6
+
7
+ class << self
8
+ #
9
+ # Build CPEs
10
+ #
11
+ # @param [String] ip
12
+ #
13
+ # @return [Array<Mihari::CPE>]
14
+ #
15
+ def build_by_ip(ip)
16
+ res = Enrichers::Shodan.query(ip)
17
+ return if res.nil?
18
+
19
+ res.cpes.map { |cpe| new(cpe: cpe) }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ class Port < ActiveRecord::Base
5
+ belongs_to :artifact
6
+
7
+ class << self
8
+ #
9
+ # Build ports
10
+ #
11
+ # @param [String] ip
12
+ #
13
+ # @return [Array<Mihari::Port>]
14
+ #
15
+ def build_by_ip(ip)
16
+ res = Enrichers::Shodan.query(ip)
17
+ return if res.nil?
18
+
19
+ res.ports.map { |port| new(port: port) }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -13,10 +13,10 @@ module Mihari
13
13
  # @return [Array<Mihari::ReverseDnsName>]
14
14
  #
15
15
  def build_by_ip(ip)
16
- names = Resolv.getnames(ip)
17
- names.map { |name| new(name: name) }
18
- rescue Resolv::ResolvError
19
- []
16
+ res = Enrichers::Shodan.query(ip)
17
+ return if res.nil?
18
+
19
+ res.hostnames.map { |name| new(name: name) }
20
20
  end
21
21
  end
22
22
  end
@@ -57,6 +57,31 @@ module Mihari
57
57
  )
58
58
  end
59
59
  end
60
+
61
+ class InternetDBResponse < Dry::Struct
62
+ attribute :ip, Types::String
63
+ attribute :ports, Types.Array(Types::Int)
64
+ attribute :cpes, Types.Array(Types::String)
65
+ attribute :hostnames, Types.Array(Types::String)
66
+ attribute :tags, Types.Array(Types::String)
67
+ attribute :vulns, Types.Array(Types::String)
68
+
69
+ def self.from_dynamic!(d)
70
+ d = Types::Hash[d]
71
+ new(
72
+ ip: d.fetch("ip"),
73
+ ports: d.fetch("ports"),
74
+ cpes: d.fetch("cpes"),
75
+ hostnames: d.fetch("hostnames"),
76
+ tags: d.fetch("tags"),
77
+ vulns: d.fetch("vulns")
78
+ )
79
+ end
80
+
81
+ def self.from_json!(json)
82
+ from_dynamic!(JSON.parse(json))
83
+ end
84
+ end
60
85
  end
61
86
  end
62
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.4.1"
4
+ VERSION = "4.5.0"
5
5
  end
@@ -3,7 +3,6 @@
3
3
  # Endpoints
4
4
  require "mihari/web/endpoints/alerts"
5
5
  require "mihari/web/endpoints/artifacts"
6
- require "mihari/web/endpoints/command"
7
6
  require "mihari/web/endpoints/configs"
8
7
  require "mihari/web/endpoints/ip_addresses"
9
8
  require "mihari/web/endpoints/rules"
@@ -17,7 +16,6 @@ module Mihari
17
16
 
18
17
  mount Endpoints::Alerts
19
18
  mount Endpoints::Artifacts
20
- mount Endpoints::Command
21
19
  mount Endpoints::Configs
22
20
  mount Endpoints::IPAddresses
23
21
  mount Endpoints::Rules
@@ -54,7 +54,9 @@ module Mihari
54
54
  :geolocation,
55
55
  :whois_record,
56
56
  :dns_records,
57
- :reverse_dns_names
57
+ :reverse_dns_names,
58
+ :cpes,
59
+ :ports
58
60
  ).find(id)
59
61
  rescue ActiveRecord::RecordNotFound
60
62
  error!({ message: "ID:#{id} is not found" }, 404)
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="/static/favicon.ico"/><title>Mihari</title><script defer="defer" type="module" src="/static/js/chunk-vendors.3bdbaffb.js"></script><script defer="defer" type="module" src="/static/js/app.40749592.js"></script><link href="/static/css/chunk-vendors.da2a7bfc.css" rel="stylesheet"><link href="/static/css/app.de5845d8.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.d6b76c57.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.f550d6ae.js" nomodule></script></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="/static/favicon.ico"/><title>Mihari</title><script defer="defer" type="module" src="/static/js/chunk-vendors.3bdbaffb.js"></script><script defer="defer" type="module" src="/static/js/app.afd5025f.js"></script><link href="/static/css/chunk-vendors.da2a7bfc.css" rel="stylesheet"><link href="/static/css/app.2a5d3d21.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.d6b76c57.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.c3595dce.js" nomodule></script></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>