mihari 5.1.1 → 5.1.2

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/.rubocop.yml +6 -0
  4. data/README.md +0 -1
  5. data/lib/mihari/analyzers/base.rb +32 -27
  6. data/lib/mihari/analyzers/binaryedge.rb +8 -2
  7. data/lib/mihari/analyzers/censys.rb +7 -49
  8. data/lib/mihari/analyzers/circl.rb +5 -2
  9. data/lib/mihari/analyzers/crtsh.rb +6 -0
  10. data/lib/mihari/analyzers/dnstwister.rb +4 -2
  11. data/lib/mihari/analyzers/feed.rb +21 -0
  12. data/lib/mihari/analyzers/greynoise.rb +5 -28
  13. data/lib/mihari/analyzers/onyphe.rb +8 -33
  14. data/lib/mihari/analyzers/otx.rb +3 -0
  15. data/lib/mihari/analyzers/passivetotal.rb +3 -0
  16. data/lib/mihari/analyzers/pulsedive.rb +3 -0
  17. data/lib/mihari/analyzers/rule.rb +0 -1
  18. data/lib/mihari/analyzers/securitytrails.rb +8 -10
  19. data/lib/mihari/analyzers/shodan.rb +13 -81
  20. data/lib/mihari/analyzers/urlscan.rb +9 -0
  21. data/lib/mihari/analyzers/virustotal.rb +4 -0
  22. data/lib/mihari/analyzers/virustotal_intelligence.rb +8 -2
  23. data/lib/mihari/analyzers/zoomeye.rb +9 -0
  24. data/lib/mihari/clients/binaryedge.rb +5 -0
  25. data/lib/mihari/clients/censys.rb +4 -4
  26. data/lib/mihari/clients/circl.rb +3 -3
  27. data/lib/mihari/clients/greynoise.rb +6 -1
  28. data/lib/mihari/clients/misp.rb +6 -1
  29. data/lib/mihari/clients/onyphe.rb +13 -1
  30. data/lib/mihari/clients/otx.rb +20 -0
  31. data/lib/mihari/clients/passivetotal.rb +6 -2
  32. data/lib/mihari/clients/publsedive.rb +18 -1
  33. data/lib/mihari/clients/securitytrails.rb +94 -0
  34. data/lib/mihari/clients/shodan.rb +14 -3
  35. data/lib/mihari/clients/the_hive.rb +6 -1
  36. data/lib/mihari/clients/urlscan.rb +3 -1
  37. data/lib/mihari/clients/virustotal.rb +9 -3
  38. data/lib/mihari/clients/zoomeye.rb +7 -1
  39. data/lib/mihari/commands/database.rb +1 -6
  40. data/lib/mihari/commands/searcher.rb +1 -2
  41. data/lib/mihari/database.rb +9 -0
  42. data/lib/mihari/structs/censys.rb +62 -0
  43. data/lib/mihari/structs/greynoise.rb +43 -0
  44. data/lib/mihari/structs/onyphe.rb +45 -0
  45. data/lib/mihari/structs/shodan.rb +83 -0
  46. data/lib/mihari/version.rb +1 -1
  47. data/lib/mihari/web/middleware/connection_adapter.rb +1 -3
  48. data/lib/mihari/web/public/assets/{index-63900d73.js → index-7d0fb8c4.js} +2 -2
  49. data/lib/mihari/web/public/index.html +1 -1
  50. data/lib/mihari/web/public/redoc-static.html +2 -2
  51. data/lib/mihari.rb +1 -3
  52. data/mihari.gemspec +2 -3
  53. metadata +9 -25
  54. data/lib/mihari/analyzers/dnpedia.rb +0 -33
  55. data/lib/mihari/clients/dnpedia.rb +0 -64
  56. data/lib/mihari/mixins/database.rb +0 -16
@@ -5,8 +5,7 @@ module Mihari
5
5
  class VirusTotal < Base
6
6
  #
7
7
  # @param [String] base_url
8
- # @param [String] id
9
- # @param [String] secret
8
+ # @param [String, nil] api_key
10
9
  # @param [Hash] headers
11
10
  #
12
11
  def initialize(base_url = "https://www.virustotal.com", api_key:, headers: {})
@@ -20,6 +19,8 @@ module Mihari
20
19
  #
21
20
  # @param [String] query
22
21
  #
22
+ # @return [Hash]
23
+ #
23
24
  def domain_search(query)
24
25
  _get("/api/v3/domains/#{query}/resolutions")
25
26
  end
@@ -27,6 +28,8 @@ module Mihari
27
28
  #
28
29
  # @param [String] query
29
30
  #
31
+ # @return [Hash]
32
+ #
30
33
  def ip_search(query)
31
34
  _get("/api/v3/ip_addresses/#{query}/resolutions")
32
35
  end
@@ -35,6 +38,8 @@ module Mihari
35
38
  # @param [String] query
36
39
  # @param [String, nil] cursor
37
40
  #
41
+ # @return [Hash]
42
+ #
38
43
  def intel_search(query, cursor: nil)
39
44
  params = { query: query, cursor: cursor }.compact
40
45
  _get("/api/v3/intelligence/search", params: params)
@@ -42,11 +47,12 @@ module Mihari
42
47
 
43
48
  private
44
49
 
45
- #
46
50
  #
47
51
  # @param [String] path
48
52
  # @param [Hash] params
49
53
  #
54
+ # @return [Hash]
55
+ #
50
56
  def _get(path, params: {})
51
57
  res = get(path, params: params)
52
58
  JSON.parse(res.body.to_s)
@@ -5,6 +5,11 @@ module Mihari
5
5
  class ZoomEye < Base
6
6
  attr_reader :api_key
7
7
 
8
+ #
9
+ # @param [String] base_url
10
+ # @param [String, nil] api_key
11
+ # @param [Hash] headers
12
+ #
8
13
  def initialize(base_url = "https://api.zoomeye.org", api_key:, headers: {})
9
14
  raise(ArgumentError, "'api_key' argument is required") unless api_key
10
15
 
@@ -52,11 +57,12 @@ module Mihari
52
57
 
53
58
  private
54
59
 
55
- #
56
60
  #
57
61
  # @param [String] path
58
62
  # @param [Hash] params
59
63
  #
64
+ # @return [Hash, nil]
65
+ #
60
66
  def _get(path, params: {})
61
67
  res = get(path, params: params)
62
68
  JSON.parse(res.body.to_s)
@@ -3,8 +3,6 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Database
6
- include Mixins::Database
7
-
8
6
  def self.included(thor)
9
7
  thor.class_eval do
10
8
  desc "migrate", "Migrate DB schemas"
@@ -12,14 +10,11 @@ module Mihari
12
10
  #
13
11
  # @param [String] direction
14
12
  #
15
- #
16
13
  def migrate(direction = "up")
17
14
  verbose = options["verbose"]
18
15
  ActiveRecord::Migration.verbose = verbose
19
16
 
20
- with_db_connection do
21
- Mihari::Database.migrate(direction.to_sym)
22
- end
17
+ Mihari::Database.with_db_connection { Mihari::Database.migrate(direction.to_sym) }
23
18
  end
24
19
  end
25
20
  end
@@ -3,7 +3,6 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Searcher
6
- include Mixins::Database
7
6
  include Mixins::ErrorNotification
8
7
 
9
8
  def self.included(thor)
@@ -16,7 +15,7 @@ module Mihari
16
15
  # @param [String] path_or_id
17
16
  #
18
17
  def search(path_or_id)
19
- with_db_connection do
18
+ Mihari::Database.with_db_connection do
20
19
  rule = Structs::Rule.from_path_or_id path_or_id
21
20
 
22
21
  # validate
@@ -164,6 +164,15 @@ module Mihari
164
164
 
165
165
  ActiveRecord::Base.clear_active_connections!
166
166
  end
167
+
168
+ def with_db_connection
169
+ Mihari::Database.connect
170
+ yield
171
+ rescue ActiveRecord::StatementInvalid
172
+ Mihari.logger.error("You haven't finished the DB migration! Please run 'mihari db migrate'.")
173
+ ensure
174
+ Mihari::Database.close
175
+ end
167
176
  end
168
177
  end
169
178
  end
@@ -4,8 +4,17 @@ module Mihari
4
4
  module Structs
5
5
  module Censys
6
6
  class AutonomousSystem < Dry::Struct
7
+ include Mixins::AutonomousSystem
8
+
7
9
  attribute :asn, Types::Int
8
10
 
11
+ #
12
+ # @return [Mihari::AutonomousSystem]
13
+ #
14
+ def to_as
15
+ Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
16
+ end
17
+
9
18
  def self.from_dynamic!(d)
10
19
  d = Types::Hash[d]
11
20
  new(
@@ -18,6 +27,20 @@ module Mihari
18
27
  attribute :country, Types::String.optional
19
28
  attribute :country_code, Types::String.optional
20
29
 
30
+ #
31
+ # @return [Mihari::Geolocation] <description>
32
+ #
33
+ def to_geolocation
34
+ # sometimes Censys overlooks country
35
+ # then set geolocation as nil
36
+ return nil if country.nil?
37
+
38
+ Mihari::Geolocation.new(
39
+ country: country,
40
+ country_code: country_code
41
+ )
42
+ end
43
+
21
44
  def self.from_dynamic!(d)
22
45
  d = Types::Hash[d]
23
46
  new(
@@ -30,6 +53,13 @@ module Mihari
30
53
  class Service < Dry::Struct
31
54
  attribute :port, Types::Integer
32
55
 
56
+ #
57
+ # @return [Mihari::Port]
58
+ #
59
+ def to_port
60
+ Port.new(port: port)
61
+ end
62
+
33
63
  def self.from_dynamic!(d)
34
64
  d = Types::Hash[d]
35
65
  new(
@@ -45,6 +75,29 @@ module Mihari
45
75
  attribute :metadata, Types::Hash
46
76
  attribute :services, Types.Array(Service)
47
77
 
78
+ #
79
+ # @return [Array<Mihari::Port>]
80
+ #
81
+ def to_ports
82
+ services.map(&:to_port)
83
+ end
84
+
85
+ #
86
+ # @param [String] source
87
+ #
88
+ # @return [Mihari::Artifact]
89
+ #
90
+ def to_artifact(source = "Censys")
91
+ Artifact.new(
92
+ data: ip,
93
+ source: source,
94
+ metadata: metadata,
95
+ autonomous_system: autonomous_system.to_as,
96
+ geolocation: location.to_geolocation,
97
+ ports: to_ports
98
+ )
99
+ end
100
+
48
101
  def self.from_dynamic!(d)
49
102
  d = Types::Hash[d]
50
103
  new(
@@ -76,6 +129,15 @@ module Mihari
76
129
  attribute :hits, Types.Array(Hit)
77
130
  attribute :links, Links
78
131
 
132
+ #
133
+ # @param [String] source
134
+ #
135
+ # @return [Array<Mihari::Artifact>]
136
+ #
137
+ def to_artifacts(source = "Censys")
138
+ hits.map { |hit| hit.to_artifact(source) }
139
+ end
140
+
79
141
  def self.from_dynamic!(d)
80
142
  d = Types::Hash[d]
81
143
  new(
@@ -4,10 +4,29 @@ module Mihari
4
4
  module Structs
5
5
  module GreyNoise
6
6
  class Metadata < Dry::Struct
7
+ include Mixins::AutonomousSystem
8
+
7
9
  attribute :country, Types::String
8
10
  attribute :country_code, Types::String
9
11
  attribute :asn, Types::String
10
12
 
13
+ #
14
+ # @return [Mihari::AutonomousSystem]
15
+ #
16
+ def to_as
17
+ Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
18
+ end
19
+
20
+ #
21
+ # @return [Mihari::Geolocation]
22
+ #
23
+ def to_geolocation
24
+ Mihari::Geolocation.new(
25
+ country: country,
26
+ country_code: country_code
27
+ )
28
+ end
29
+
11
30
  def self.from_dynamic!(d)
12
31
  d = Types::Hash[d]
13
32
  new(
@@ -23,6 +42,21 @@ module Mihari
23
42
  attribute :metadata, Metadata
24
43
  attribute :metadata_, Types::Hash
25
44
 
45
+ #
46
+ # @param [String] source
47
+ #
48
+ # @return [Mihari::Artifact]
49
+ #
50
+ def to_artifact(source = "GreyNoise")
51
+ Mihari::Artifact.new(
52
+ data: ip,
53
+ source: source,
54
+ metadata: metadata_,
55
+ autonomous_system: metadata.to_as,
56
+ geolocation: metadata.to_geolocation
57
+ )
58
+ end
59
+
26
60
  def self.from_dynamic!(d)
27
61
  d = Types::Hash[d]
28
62
  new(
@@ -40,6 +74,15 @@ module Mihari
40
74
  attribute :message, Types::String
41
75
  attribute :query, Types::String
42
76
 
77
+ #
78
+ # @param [String] source
79
+ #
80
+ # @return [Array<Mihari::Artifact>]
81
+ #
82
+ def to_artifacts(source = "GreyNoise")
83
+ data.map { |datum| datum.to_artifact(source) }
84
+ end
85
+
43
86
  def self.from_dynamic!(d)
44
87
  d = Types::Hash[d]
45
88
  new(
@@ -4,11 +4,47 @@ module Mihari
4
4
  module Structs
5
5
  module Onyphe
6
6
  class Result < Dry::Struct
7
+ include Mixins::AutonomousSystem
8
+
7
9
  attribute :asn, Types::String
8
10
  attribute :country_code, Types::String.optional
9
11
  attribute :ip, Types::String
10
12
  attribute :metadata, Types::Hash
11
13
 
14
+ #
15
+ # @param [String] source
16
+ #
17
+ # @return [Mihari::Artifact]
18
+ #
19
+ def to_artifact(source = "Onyphe")
20
+ Mihari::Artifact.new(
21
+ data: ip,
22
+ source: source,
23
+ metadata: metadata,
24
+ autonomous_system: to_as,
25
+ geolocation: to_geolocation
26
+ )
27
+ end
28
+
29
+ #
30
+ # @return [Mihari::Geolocation, nil]
31
+ #
32
+ def to_geolocation
33
+ return nil if country_code.nil?
34
+
35
+ Mihari::Geolocation.new(
36
+ country: NormalizeCountry(country_code, to: :short),
37
+ country_code: country_code
38
+ )
39
+ end
40
+
41
+ #
42
+ # @return [Mihari::AutonomousSystem]
43
+ #
44
+ def to_as
45
+ Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
46
+ end
47
+
12
48
  def self.from_dynamic!(d)
13
49
  d = Types::Hash[d]
14
50
  new(
@@ -30,6 +66,15 @@ module Mihari
30
66
  attribute :status, Types::String
31
67
  attribute :total, Types::Int
32
68
 
69
+ #
70
+ # @param [String] source
71
+ #
72
+ # @return [Array<Mihari::Artifact>]
73
+ #
74
+ def to_artifacts(source = "Onyphe")
75
+ results.map { |result| result.to_artifact(source) }
76
+ end
77
+
33
78
  def self.from_dynamic!(d)
34
79
  d = Types::Hash[d]
35
80
  new(
@@ -7,6 +7,18 @@ module Mihari
7
7
  attribute :country_code, Types::String.optional
8
8
  attribute :country_name, Types::String.optional
9
9
 
10
+ #
11
+ # @return [Mihari::Geolocation, nil]
12
+ #
13
+ def to_geolocation
14
+ return nil if country_name.nil? && country_code.nil?
15
+
16
+ Mihari::Geolocation.new(
17
+ country: country_name,
18
+ country_code: country_code
19
+ )
20
+ end
21
+
10
22
  def self.from_dynamic!(d)
11
23
  d = Types::Hash[d]
12
24
  new(
@@ -17,6 +29,8 @@ module Mihari
17
29
  end
18
30
 
19
31
  class Match < Dry::Struct
32
+ include Mixins::AutonomousSystem
33
+
20
34
  attribute :asn, Types::String.optional
21
35
  attribute :hostnames, Types.Array(Types::String)
22
36
  attribute :location, Location
@@ -25,6 +39,15 @@ module Mihari
25
39
  attribute :port, Types::Integer
26
40
  attribute :metadata, Types::Hash
27
41
 
42
+ #
43
+ # @return [Mihari::AutonomousSystem, nil]
44
+ #
45
+ def to_asn
46
+ return nil if asn.nil?
47
+
48
+ Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
49
+ end
50
+
28
51
  def self.from_dynamic!(d)
29
52
  d = Types::Hash[d]
30
53
 
@@ -51,6 +74,66 @@ module Mihari
51
74
  attribute :matches, Types.Array(Match)
52
75
  attribute :total, Types::Int
53
76
 
77
+ #
78
+ # Collect metadata from matches
79
+ #
80
+ # @param [String] ip
81
+ #
82
+ # @return [Array<Hash>]
83
+ #
84
+ def collect_metadata_by_ip(ip)
85
+ matches.select { |match| match.ip_str == ip }.map(&:metadata)
86
+ end
87
+
88
+ #
89
+ # Collect ports from matches
90
+ #
91
+ # @param [String] ip
92
+ #
93
+ # @return [Array<String>]
94
+ #
95
+ def collect_ports_by_ip(ip)
96
+ matches.select { |match| match.ip_str == ip }.map(&:port)
97
+ end
98
+
99
+ #
100
+ # Collect hostnames from matches
101
+ #
102
+ # @param [String] ip
103
+ #
104
+ # @return [Array<String>]
105
+ #
106
+ def collect_hostnames_by_ip(ip)
107
+ matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
108
+ end
109
+
110
+ #
111
+ # @param [Source] source
112
+ #
113
+ # @return [Array<Mihari::Artifact>]
114
+ #
115
+ def to_artifacts(source = "Shodan")
116
+ matches.map do |match|
117
+ metadata = collect_metadata_by_ip(match.ip_str)
118
+ ports = collect_ports_by_ip(match.ip_str).map do |port|
119
+ Mihari::Port.new(port: port)
120
+ end
121
+ reverse_dns_names = collect_hostnames_by_ip(match.ip_str).map do |name|
122
+ Mihari::ReverseDnsName.new(name: name)
123
+ end
124
+
125
+ Mihari::Artifact.new(
126
+ data: match.ip_str,
127
+ source: source,
128
+ metadata: metadata,
129
+ autonomous_system: match.to_asn,
130
+ geolocation: match.location.to_geolocation,
131
+ ports: ports,
132
+ reverse_dns_names: reverse_dns_names
133
+ )
134
+ end
135
+ end
136
+
54
137
  def self.from_dynamic!(d)
55
138
  d = Types::Hash[d]
56
139
  new(
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "5.1.1"
4
+ VERSION = "5.1.2"
5
5
  end
@@ -1,14 +1,12 @@
1
1
  module Mihari
2
2
  module Middleware
3
3
  class ConnectionAdapter
4
- include Mixins::Database
5
-
6
4
  def initialize(app)
7
5
  @app = app
8
6
  end
9
7
 
10
8
  def call(env)
11
- with_db_connection do
9
+ Mihari::Database.with_db_connection do
12
10
  status, headers, body = @app.call(env)
13
11
 
14
12
  [status, headers, body]