mihari 5.1.1 → 5.1.2

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