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.
- checksums.yaml +4 -4
- data/.gitmodules +0 -3
- data/.rubocop.yml +6 -0
- data/README.md +0 -1
- data/lib/mihari/analyzers/base.rb +32 -27
- data/lib/mihari/analyzers/binaryedge.rb +8 -2
- data/lib/mihari/analyzers/censys.rb +7 -49
- data/lib/mihari/analyzers/circl.rb +5 -2
- data/lib/mihari/analyzers/crtsh.rb +6 -0
- data/lib/mihari/analyzers/dnstwister.rb +4 -2
- data/lib/mihari/analyzers/feed.rb +21 -0
- data/lib/mihari/analyzers/greynoise.rb +5 -28
- data/lib/mihari/analyzers/onyphe.rb +8 -33
- data/lib/mihari/analyzers/otx.rb +3 -0
- data/lib/mihari/analyzers/passivetotal.rb +3 -0
- data/lib/mihari/analyzers/pulsedive.rb +3 -0
- data/lib/mihari/analyzers/rule.rb +0 -1
- data/lib/mihari/analyzers/securitytrails.rb +8 -10
- data/lib/mihari/analyzers/shodan.rb +13 -81
- data/lib/mihari/analyzers/urlscan.rb +9 -0
- data/lib/mihari/analyzers/virustotal.rb +4 -0
- data/lib/mihari/analyzers/virustotal_intelligence.rb +8 -2
- data/lib/mihari/analyzers/zoomeye.rb +9 -0
- data/lib/mihari/clients/binaryedge.rb +5 -0
- data/lib/mihari/clients/censys.rb +4 -4
- data/lib/mihari/clients/circl.rb +3 -3
- data/lib/mihari/clients/greynoise.rb +6 -1
- data/lib/mihari/clients/misp.rb +6 -1
- data/lib/mihari/clients/onyphe.rb +13 -1
- data/lib/mihari/clients/otx.rb +20 -0
- data/lib/mihari/clients/passivetotal.rb +6 -2
- data/lib/mihari/clients/publsedive.rb +18 -1
- data/lib/mihari/clients/securitytrails.rb +94 -0
- data/lib/mihari/clients/shodan.rb +14 -3
- data/lib/mihari/clients/the_hive.rb +6 -1
- data/lib/mihari/clients/urlscan.rb +3 -1
- data/lib/mihari/clients/virustotal.rb +9 -3
- data/lib/mihari/clients/zoomeye.rb +7 -1
- data/lib/mihari/commands/database.rb +1 -6
- data/lib/mihari/commands/searcher.rb +1 -2
- data/lib/mihari/database.rb +9 -0
- data/lib/mihari/structs/censys.rb +62 -0
- data/lib/mihari/structs/greynoise.rb +43 -0
- data/lib/mihari/structs/onyphe.rb +45 -0
- data/lib/mihari/structs/shodan.rb +83 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/middleware/connection_adapter.rb +1 -3
- data/lib/mihari/web/public/assets/{index-63900d73.js → index-7d0fb8c4.js} +2 -2
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +2 -2
- data/lib/mihari.rb +1 -3
- data/mihari.gemspec +2 -3
- metadata +9 -25
- data/lib/mihari/analyzers/dnpedia.rb +0 -33
- data/lib/mihari/clients/dnpedia.rb +0 -64
- 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]
|
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
|
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
|
data/lib/mihari/database.rb
CHANGED
@@ -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(
|
data/lib/mihari/version.rb
CHANGED
@@ -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]
|