mihari 4.4.1 → 4.5.0

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