mihari 4.4.1 → 4.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/analyzers/censys.rb +6 -1
  3. data/lib/mihari/analyzers/shodan.rb +39 -5
  4. data/lib/mihari/commands/web.rb +1 -1
  5. data/lib/mihari/database.rb +22 -1
  6. data/lib/mihari/enrichers/ipinfo.rb +1 -1
  7. data/lib/mihari/enrichers/shodan.rb +36 -0
  8. data/lib/mihari/entities/artifact.rb +6 -0
  9. data/lib/mihari/entities/cpe.rb +9 -0
  10. data/lib/mihari/entities/port.rb +9 -0
  11. data/lib/mihari/errors.rb +10 -2
  12. data/lib/mihari/http.rb +7 -1
  13. data/lib/mihari/models/alert.rb +3 -1
  14. data/lib/mihari/models/artifact.rb +31 -1
  15. data/lib/mihari/models/cpe.rb +23 -0
  16. data/lib/mihari/models/port.rb +23 -0
  17. data/lib/mihari/models/reverse_dns.rb +4 -4
  18. data/lib/mihari/schemas/analyzer.rb +2 -2
  19. data/lib/mihari/schemas/emitter.rb +1 -1
  20. data/lib/mihari/structs/censys.rb +14 -1
  21. data/lib/mihari/structs/rule.rb +3 -0
  22. data/lib/mihari/structs/shodan.rb +27 -0
  23. data/lib/mihari/types.rb +2 -4
  24. data/lib/mihari/version.rb +1 -1
  25. data/lib/mihari/web/api.rb +0 -2
  26. data/lib/mihari/web/app.rb +7 -1
  27. data/lib/mihari/web/endpoints/artifacts.rb +3 -1
  28. data/lib/mihari/web/endpoints/rules.rb +2 -1
  29. data/lib/mihari/web/public/index.html +1 -1
  30. data/lib/mihari/web/public/redoc-static.html +319 -320
  31. data/lib/mihari/web/public/static/css/{app.de5845d8.css → app.2a5d3d21.css} +1 -1
  32. data/lib/mihari/web/public/static/css/{chunk-vendors.da2a7bfc.css → chunk-vendors.06251949.css} +2 -2
  33. data/lib/mihari/web/public/static/fonts/{fa-brands-400.f7223235.ttf → fa-brands-400.7fa789ab.ttf} +0 -0
  34. data/lib/mihari/web/public/static/fonts/fa-brands-400.859fc388.woff2 +0 -0
  35. data/lib/mihari/web/public/static/fonts/fa-regular-400.2ffd018f.woff2 +0 -0
  36. data/lib/mihari/web/public/static/fonts/{fa-regular-400.a7fde52b.ttf → fa-regular-400.da02cb7e.ttf} +0 -0
  37. data/lib/mihari/web/public/static/fonts/{fa-solid-900.5b03221c.ttf → fa-solid-900.3a463ec3.ttf} +0 -0
  38. data/lib/mihari/web/public/static/fonts/fa-solid-900.40ddefd7.woff2 +0 -0
  39. data/lib/mihari/web/public/static/fonts/{fa-v4compatibility.42932bea.ttf → fa-v4compatibility.924588dc.ttf} +0 -0
  40. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js +2 -0
  41. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js.map +1 -0
  42. data/lib/mihari/web/public/static/js/app.823b5af7.js +2 -0
  43. data/lib/mihari/web/public/static/js/app.823b5af7.js.map +1 -0
  44. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js +25 -0
  45. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js.map +1 -0
  46. data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js +31 -0
  47. data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js.map +1 -0
  48. data/lib/mihari.rb +5 -1
  49. data/mihari.gemspec +7 -9
  50. data/sig/lib/mihari/models/artifact.rbs +2 -0
  51. data/sig/lib/mihari/models/cpe.rbs +7 -0
  52. data/sig/lib/mihari/models/port.rbs +7 -0
  53. data/sig/lib/mihari/structs/censys.rbs +8 -0
  54. data/sig/lib/mihari/structs/shodan.rbs +2 -0
  55. metadata +40 -63
  56. data/lib/mihari/entities/command.rb +0 -14
  57. data/lib/mihari/web/endpoints/command.rb +0 -33
  58. data/lib/mihari/web/public/static/fonts/fa-brands-400.edf40f86.woff2 +0 -0
  59. data/lib/mihari/web/public/static/fonts/fa-regular-400.3665ebc7.woff2 +0 -0
  60. data/lib/mihari/web/public/static/fonts/fa-solid-900.0d2abd43.woff2 +0 -0
  61. data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js +0 -2
  62. data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js.map +0 -1
  63. data/lib/mihari/web/public/static/js/app.40749592.js +0 -2
  64. data/lib/mihari/web/public/static/js/app.40749592.js.map +0 -1
  65. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js +0 -25
  66. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js.map +0 -1
  67. data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js +0 -31
  68. data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.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: 266aaff37fdb3453ed52ba4391f25e2fc9d26c37996a4d00c462d2c9cb9a2771
4
+ data.tar.gz: 5d3397886e6c63199f49233668772a38ac508b9e32f3cd64f8925c3bf8d391f7
5
5
  SHA512:
6
- metadata.gz: 9f538f70afd329f84c3962c8ec6c7693d8656fc105dcf0f9f30145ec5e74337c9414fdd5804255a7105105a14246e223bc5d8660980af2407db1c0595b4b40aa
7
- data.tar.gz: ce2bec572a183d29c184b188f9314f714446a67c7599ba4855de566120351b2041c6b9f5a98b7b972aa4aa960a6ea233ac7c6509b1fdbbb081a5a08e22ab51c2
6
+ metadata.gz: 6df709c192e0a533d40604ee4fd076eb9d5f776d13165e60fe1687cf2fc2d008decbfd80c48cc809ab6667946e86b7a74490109e2bff9bf6c15d7c5767f490dc
7
+ data.tar.gz: 3350eeaa0d82da0322d463c30d69cfb2d09378449d37e681c34ec082d1d6505bb8bf1838c201779c3f3e618d716e029c25bbf6e0ac1329ad6c2291eef7c38a3a
@@ -88,12 +88,17 @@ module Mihari
88
88
  )
89
89
  end
90
90
 
91
+ ports = hit.services.map(&:port).map do |port|
92
+ Port.new(port: port)
93
+ end
94
+
91
95
  Artifact.new(
92
96
  data: hit.ip,
93
97
  source: source,
94
98
  metadata: hit.metadata,
95
99
  autonomous_system: as,
96
- geolocation: geolocation
100
+ geolocation: geolocation,
101
+ ports: ports
97
102
  )
98
103
  end
99
104
 
@@ -23,10 +23,10 @@ module Mihari
23
23
  return [] unless results || results.empty?
24
24
 
25
25
  results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
26
- results.map do |result|
27
- matches = result.matches || []
28
- matches.map { |match| build_artifact(match, matches) }
29
- end.flatten.uniq(&:data)
26
+ matches = results.map { |result| result.matches || [] }.flatten
27
+
28
+ uniq_matches = matches.uniq(&:ip_str)
29
+ uniq_matches.map { |match| build_artifact(match, matches) }
30
30
  end
31
31
 
32
32
  private
@@ -94,6 +94,30 @@ module Mihari
94
94
  matches.select { |match| match.ip_str == ip }.map(&:metadata)
95
95
  end
96
96
 
97
+ #
98
+ # Collect ports from matches
99
+ #
100
+ # @param [Array<Structs::Shodan::Match>] matches
101
+ # @param [String] ip
102
+ #
103
+ # @return [Array<String>]
104
+ #
105
+ def collect_ports_by_ip(matches, ip)
106
+ matches.select { |match| match.ip_str == ip }.map(&:port)
107
+ end
108
+
109
+ #
110
+ # Collect hostnames from matches
111
+ #
112
+ # @param [Array<Structs::Shodan::Match>] matches
113
+ # @param [String] ip
114
+ #
115
+ # @return [Array<String>]
116
+ #
117
+ def collect_hostnames_by_ip(matches, ip)
118
+ matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
119
+ end
120
+
97
121
  #
98
122
  # Build an artifact from a Shodan search API response
99
123
  #
@@ -116,12 +140,22 @@ module Mihari
116
140
 
117
141
  metadata = collect_metadata_by_ip(matches, match.ip_str)
118
142
 
143
+ ports = collect_ports_by_ip(matches, match.ip_str).map do |port|
144
+ Port.new(port: port)
145
+ end
146
+
147
+ reverse_dns_names = collect_hostnames_by_ip(matches, match.ip_str).map do |name|
148
+ ReverseDnsName.new(name: name)
149
+ end
150
+
119
151
  Artifact.new(
120
152
  data: match.ip_str,
121
153
  source: source,
122
154
  metadata: metadata,
123
155
  autonomous_system: as,
124
- geolocation: geolocation
156
+ geolocation: geolocation,
157
+ ports: ports,
158
+ reverse_dns_names: reverse_dns_names
125
159
  )
126
160
  end
127
161
  end
@@ -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?
@@ -39,7 +39,7 @@ module Mihari
39
39
  data = JSON.parse(res.body.to_s)
40
40
 
41
41
  Structs::IPInfo::Response.from_dynamic! data
42
- rescue HttpError
42
+ rescue HTTPError
43
43
  nil
44
44
  end
45
45
  end
@@ -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
data/lib/mihari/errors.rb CHANGED
@@ -11,11 +11,19 @@ module Mihari
11
11
 
12
12
  class FileNotFoundError < Error; end
13
13
 
14
- class HttpError < Error; end
15
-
16
14
  class FeedParseError < Error; end
17
15
 
18
16
  class RuleValidationError < Error; end
19
17
 
20
18
  class ConfigurationError < Error; end
19
+
20
+ class HTTPError < Error; end
21
+
22
+ class UnsuccessfulStatusCodeError < HTTPError; end
23
+
24
+ class NetworkError < HTTPError; end
25
+
26
+ class TimeoutError < HTTPError; end
27
+
28
+ class SSLError < HTTPError; end
21
29
  end
data/lib/mihari/http.rb CHANGED
@@ -90,11 +90,17 @@ module Mihari
90
90
 
91
91
  unless res.is_a?(Net::HTTPSuccess)
92
92
  code = res.code.to_i
93
- raise HttpError, "Unsuccessful response code returned: #{code}"
93
+ raise UnsuccessfulStatusCodeError, "Unsuccessful response code returned: #{code}"
94
94
  end
95
95
 
96
96
  res
97
97
  end
98
+ rescue Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, EOFError, SocketError, Net::ProtocolError => e
99
+ raise NetworkError, e
100
+ rescue Timeout::Error => e
101
+ raise TimeoutError, e
102
+ rescue OpenSSL::SSL::SSLError => e
103
+ raise SSLError, e
98
104
  end
99
105
  end
100
106
  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
@@ -89,10 +89,10 @@ module Mihari
89
89
  required(:analyzer).value(Types::String.enum("feed"))
90
90
  required(:query).value(:string)
91
91
  required(:selector).value(:string)
92
- optional(:http_request_method).value(Types::HttpRequestMethods).default("GET")
92
+ optional(:http_request_method).value(Types::HTTPRequestMethods).default("GET")
93
93
  optional(:http_request_headers).value(:hash).default({})
94
94
  optional(:http_request_payload).value(:hash).default({})
95
- optional(:http_request_payload_type).value(Types::HttpRequestPayloadTypes)
95
+ optional(:http_request_payload_type).value(Types::HTTPRequestPayloadTypes)
96
96
  optional(:options).hash(AnalyzerOptions)
97
97
  end
98
98
  end
@@ -27,7 +27,7 @@ module Mihari
27
27
  HTTP = Dry::Schema.Params do
28
28
  required(:emitter).value(Types::String.enum("http"))
29
29
  required(:url).value(:string)
30
- optional(:http_request_method).value(Types::HttpRequestMethods).default("POST")
30
+ optional(:http_request_method).value(Types::HTTPRequestMethods).default("POST")
31
31
  optional(:http_request_headers).value(:hash).default({})
32
32
  optional(:template).value(:string)
33
33
  end
@@ -27,11 +27,23 @@ module Mihari
27
27
  end
28
28
  end
29
29
 
30
+ class Service < Dry::Struct
31
+ attribute :port, Types::Integer
32
+
33
+ def self.from_dynamic!(d)
34
+ d = Types::Hash[d]
35
+ new(
36
+ port: d.fetch("port")
37
+ )
38
+ end
39
+ end
40
+
30
41
  class Hit < Dry::Struct
31
42
  attribute :ip, Types::String
32
43
  attribute :location, Location
33
44
  attribute :autonomous_system, AutonomousSystem
34
45
  attribute :metadata, Types::Hash
46
+ attribute :services, Types.Array(Service)
35
47
 
36
48
  def self.from_dynamic!(d)
37
49
  d = Types::Hash[d]
@@ -39,7 +51,8 @@ module Mihari
39
51
  ip: d.fetch("ip"),
40
52
  location: Location.from_dynamic!(d.fetch("location")),
41
53
  autonomous_system: AutonomousSystem.from_dynamic!(d.fetch("autonomous_system")),
42
- metadata: d
54
+ metadata: d,
55
+ services: d.fetch("services", []).map { |x| Service.from_dynamic!(x) }
43
56
  )
44
57
  end
45
58
  end
@@ -36,6 +36,9 @@ module Mihari
36
36
  # @return [Array, nil]
37
37
  attr_reader :errors
38
38
 
39
+ # @return [String]
40
+ attr_writer :id
41
+
39
42
  def initialize(data, yaml)
40
43
  @data = data.deep_symbolize_keys
41
44
  @yaml = yaml
@@ -22,6 +22,7 @@ module Mihari
22
22
  attribute :location, Location
23
23
  attribute :domains, Types.Array(Types::String)
24
24
  attribute :ip_str, Types::String
25
+ attribute :port, Types::Integer
25
26
  attribute :metadata, Types::Hash
26
27
 
27
28
  def self.from_dynamic!(d)
@@ -40,6 +41,7 @@ module Mihari
40
41
  location: Location.from_dynamic!(d.fetch("location")),
41
42
  domains: d.fetch("domains"),
42
43
  ip_str: d.fetch("ip_str"),
44
+ port: d.fetch("port"),
43
45
  metadata: d
44
46
  )
45
47
  end
@@ -57,6 +59,31 @@ module Mihari
57
59
  )
58
60
  end
59
61
  end
62
+
63
+ class InternetDBResponse < Dry::Struct
64
+ attribute :ip, Types::String
65
+ attribute :ports, Types.Array(Types::Int)
66
+ attribute :cpes, Types.Array(Types::String)
67
+ attribute :hostnames, Types.Array(Types::String)
68
+ attribute :tags, Types.Array(Types::String)
69
+ attribute :vulns, Types.Array(Types::String)
70
+
71
+ def self.from_dynamic!(d)
72
+ d = Types::Hash[d]
73
+ new(
74
+ ip: d.fetch("ip"),
75
+ ports: d.fetch("ports"),
76
+ cpes: d.fetch("cpes"),
77
+ hostnames: d.fetch("hostnames"),
78
+ tags: d.fetch("tags"),
79
+ vulns: d.fetch("vulns")
80
+ )
81
+ end
82
+
83
+ def self.from_json!(json)
84
+ from_dynamic!(JSON.parse(json))
85
+ end
86
+ end
60
87
  end
61
88
  end
62
89
  end
data/lib/mihari/types.rb CHANGED
@@ -14,10 +14,8 @@ module Mihari
14
14
 
15
15
  DataTypes = Types::String.enum(*ALLOWED_DATA_TYPES)
16
16
 
17
- UrlscanDataTypes = Types::String.enum("ip", "domain", "url")
18
-
19
- HttpRequestMethods = Types::String.enum("GET", "POST")
20
- HttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
17
+ HTTPRequestMethods = Types::String.enum("GET", "POST")
18
+ HTTPRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
21
19
 
22
20
  EmitterTypes = Types::String.enum(
23
21
  "database",
@@ -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.2"
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
@@ -42,9 +42,15 @@ module Mihari
42
42
  end.to_app
43
43
  end
44
44
 
45
- def run!(port: 9292, host: "localhost", threads: "0:5", verbose: false)
45
+ def run!(port: 9292, host: "localhost", threads: "1:1", verbose: false)
46
46
  url = "http://#{host}:#{port}"
47
47
 
48
+ # set maximum number of threads to use as PARALLEL_PROCESSOR_COUNT (if it is not set)
49
+ # ref. https://github.com/grosser/parallel#tips
50
+ # TODO: is this the best way?
51
+ _min_thread, max_thread = threads.split(":")
52
+ ENV["PARALLEL_PROCESSOR_COUNT"] = max_thread if ENV["PARALLEL_PROCESSOR_COUNT"].nil?
53
+
48
54
  Rack::Handler::Puma.run(instance, Port: port, Host: host, Threads: threads, Verbose: verbose) do |_launcher|
49
55
  Launchy.open(url) if ENV["RACK_ENV"] != "development"
50
56
  end
@@ -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)
@@ -145,6 +145,7 @@ module Mihari
145
145
  end
146
146
 
147
147
  rule = Structs::Rule::Rule.from_yaml(yaml)
148
+ rule.id = id
148
149
 
149
150
  begin
150
151
  rule.validate!
@@ -159,7 +160,7 @@ module Mihari
159
160
  model = rule.to_model
160
161
  model.save
161
162
  rescue ActiveRecord::RecordNotUnique
162
- error!({ message: "ID:#{rule.id} is already registered" }, 400)
163
+ error!({ message: "ID:#{model.id} is already registered" }, 400)
163
164
  end
164
165
 
165
166
  status 201
@@ -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.dde2116c.js"></script><script defer="defer" type="module" src="/static/js/app.823b5af7.js"></script><link href="/static/css/chunk-vendors.06251949.css" rel="stylesheet"><link href="/static/css/app.2a5d3d21.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.b110c129.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.9d5c9c3d.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>