mihari 3.6.1 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -3
- data/lib/mihari/analyzers/base.rb +2 -17
- data/lib/mihari/analyzers/rule.rb +1 -0
- data/lib/mihari/analyzers/virustotal_intelligence.rb +63 -0
- data/lib/mihari/cli/analyzer.rb +2 -0
- data/lib/mihari/commands/passivetotal.rb +1 -0
- data/lib/mihari/commands/virustotal.rb +1 -0
- data/lib/mihari/commands/virustotal_intelligence.rb +22 -0
- data/lib/mihari/database.rb +13 -0
- data/lib/mihari/enrichers/base.rb +18 -0
- data/lib/mihari/enrichers/ipinfo.rb +49 -0
- data/lib/mihari/mixins/autonomous_system.rb +19 -0
- data/lib/mihari/models/artifact.rb +42 -3
- data/lib/mihari/models/autonomous_system.rb +18 -1
- data/lib/mihari/models/dns.rb +2 -0
- data/lib/mihari/models/geolocation.rb +21 -1
- data/lib/mihari/models/reverse_dns.rb +2 -0
- data/lib/mihari/models/whois.rb +1 -1
- data/lib/mihari/status.rb +7 -2
- data/lib/mihari/structs/ipinfo.rb +39 -0
- data/lib/mihari/structs/virustotal_intelligence.rb +75 -0
- data/lib/mihari/types.rb +13 -3
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/controllers/artifacts_controller.rb +27 -1
- data/lib/mihari/web/controllers/ip_address_controller.rb +4 -19
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +7 -6
- data/lib/mihari/web/public/static/js/app.06d5cf1c.js +36 -0
- data/lib/mihari/web/public/static/js/app.06d5cf1c.js.map +1 -0
- data/lib/mihari.rb +42 -27
- data/mihari.gemspec +8 -6
- data/sig/lib/mihari/analyzers/base.rbs +1 -10
- data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +32 -0
- data/sig/lib/mihari/enrichers/base.rbs +12 -0
- data/sig/lib/mihari/enrichers/ipinfo.rbs +16 -0
- data/sig/lib/mihari/mixins/autonomous_system.rbs +14 -0
- data/sig/lib/mihari/models/artifact.rbs +11 -0
- data/sig/lib/mihari/models/autonomous_system.rbs +9 -0
- data/sig/lib/mihari/models/geolocation.rbs +9 -0
- data/sig/lib/mihari/structs/ipinfo.rbs +17 -0
- data/sig/lib/mihari/structs/virustotal_intelligence.rbs +33 -0
- data/sig/lib/mihari.rbs +2 -0
- metadata +57 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aac91d43689cb53dc0570bfed3cec57a07cbe88de0716530f2ea8bfac8f8d39d
|
4
|
+
data.tar.gz: 0f59bdc53cfa75e56884dd3497fa0492d3a41a3b7540cbdab1345ec5b301c69c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30aef30fb14c7c1a50e75162141d1266b1ec5b847f6329935a221a90f59d37a2bed97c6a8aa4371962ab85b18c5ff23cf0417f08f9dc3320c737721ac1a07602
|
7
|
+
data.tar.gz: 1029878ec85cbdbe0a2c800b6b68cce99d45510818dde6b1d84a826022379cbe21fc57f490e3a77521136dbc22e8112b804d891892ea0a351e0d1db935b28b4b
|
data/README.md
CHANGED
@@ -46,7 +46,7 @@ Mihari supports the following services by default.
|
|
46
46
|
- [Shodan](https://shodan.io)
|
47
47
|
- [Spyse](https://spyse.com)
|
48
48
|
- [urlscan.io](https://urlscan.io)
|
49
|
-
- [VirusTotal](http://virustotal.com)
|
49
|
+
- [VirusTotal](http://virustotal.com) & [VirusTotal Intelligence](https://www.virustotal.com/gui/intelligence-overview)
|
50
50
|
- [ZoomEye](https://zoomeye.org)
|
51
51
|
|
52
52
|
## Docs
|
@@ -64,5 +64,3 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
64
64
|
## Acknowledgement
|
65
65
|
|
66
66
|
Mihari is proudly supported by [Tines.io](https://tines.io?utm_source=github&utm_medium=sponsorship&utm_campaign=ninoseki), The SOAR Platform for Enterprise Security Teams.
|
67
|
-
|
68
|
-
$ bundle exec rbs -rpathname --repo=gem_rbs/gems -ractivesupport -ractionpack -ractivejob -ractivemodel -ractionview -ractiverecord -rrailties -I sig validate
|
@@ -8,6 +8,7 @@ module Mihari
|
|
8
8
|
class Base
|
9
9
|
extend Dry::Initializer
|
10
10
|
|
11
|
+
include Mixins::AutonomousSystem
|
11
12
|
include Mixins::Configurable
|
12
13
|
include Mixins::Retriable
|
13
14
|
|
@@ -111,9 +112,7 @@ module Mihari
|
|
111
112
|
#
|
112
113
|
def enriched_artifacts
|
113
114
|
@enriched_artifacts ||= unique_artifacts.map do |artifact|
|
114
|
-
artifact.
|
115
|
-
artifact.enrich_dns
|
116
|
-
artifact.enrich_reverse_dns
|
115
|
+
artifact.enrich_all
|
117
116
|
artifact
|
118
117
|
end
|
119
118
|
end
|
@@ -141,20 +140,6 @@ module Mihari
|
|
141
140
|
emitter.valid? ? emitter : nil
|
142
141
|
end.compact
|
143
142
|
end
|
144
|
-
|
145
|
-
#
|
146
|
-
# Normalize ASN value
|
147
|
-
#
|
148
|
-
# @param [String, Integer] asn
|
149
|
-
#
|
150
|
-
# @return [Integer]
|
151
|
-
#
|
152
|
-
def normalize_asn(asn)
|
153
|
-
return asn if asn.is_a?(Integer)
|
154
|
-
return asn.to_i unless asn.start_with?("AS")
|
155
|
-
|
156
|
-
asn.delete_prefix("AS").to_i
|
157
|
-
end
|
158
143
|
end
|
159
144
|
end
|
160
145
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "virustotal"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Analyzers
|
7
|
+
class VirusTotalIntelligence < Base
|
8
|
+
param :query
|
9
|
+
option :title, default: proc { "VirusTotal Intelligence search" }
|
10
|
+
option :description, default: proc { "query = #{query}" }
|
11
|
+
option :tags, default: proc { [] }
|
12
|
+
|
13
|
+
def initialize(*args, **kwargs)
|
14
|
+
super
|
15
|
+
|
16
|
+
@query = query
|
17
|
+
end
|
18
|
+
|
19
|
+
def artifacts
|
20
|
+
responses = search_witgh_cursor
|
21
|
+
responses.map do |response|
|
22
|
+
response.data.map(&:value)
|
23
|
+
end.flatten.compact.uniq
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def configuration_keys
|
29
|
+
%w[virustotal_api_key]
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# VT API
|
34
|
+
#
|
35
|
+
# @return [::VirusTotal::API]
|
36
|
+
#
|
37
|
+
def api
|
38
|
+
@api = ::VirusTotal::API.new(key: Mihari.config.virustotal_api_key)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Search with cursor
|
43
|
+
#
|
44
|
+
# @return [Array<Structs::VirusTotalIntelligence::Response>]
|
45
|
+
#
|
46
|
+
def search_witgh_cursor
|
47
|
+
cursor = nil
|
48
|
+
responses = []
|
49
|
+
|
50
|
+
loop do
|
51
|
+
response = Structs::VirusTotalIntelligence::Response.from_dynamic!(api.intelligence.search(query, cursor: cursor))
|
52
|
+
responses << response
|
53
|
+
|
54
|
+
break if response.meta.cursor.nil?
|
55
|
+
|
56
|
+
cursor = response.meta.cursor
|
57
|
+
end
|
58
|
+
|
59
|
+
responses
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/mihari/cli/analyzer.rb
CHANGED
@@ -14,6 +14,7 @@ require "mihari/commands/securitytrails"
|
|
14
14
|
require "mihari/commands/shodan"
|
15
15
|
require "mihari/commands/spyse"
|
16
16
|
require "mihari/commands/urlscan"
|
17
|
+
require "mihari/commands/virustotal_intelligence"
|
17
18
|
require "mihari/commands/virustotal"
|
18
19
|
require "mihari/commands/zoomeye"
|
19
20
|
|
@@ -42,6 +43,7 @@ module Mihari
|
|
42
43
|
include Mihari::Commands::Spyse
|
43
44
|
include Mihari::Commands::Urlscan
|
44
45
|
include Mihari::Commands::VirusTotal
|
46
|
+
include Mihari::Commands::VirusTotalIntelligence
|
45
47
|
include Mihari::Commands::ZoomEye
|
46
48
|
end
|
47
49
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Commands
|
5
|
+
module VirusTotalIntelligence
|
6
|
+
def self.included(thor)
|
7
|
+
thor.class_eval do
|
8
|
+
desc "virustotal_intelligence [QUERY]", "VirusTotal Intelligence search"
|
9
|
+
method_option :title, type: :string, desc: "title"
|
10
|
+
method_option :description, type: :string, desc: "description"
|
11
|
+
method_option :tags, type: :array, desc: "tags"
|
12
|
+
def virustotal_intelligence(query)
|
13
|
+
with_error_handling do
|
14
|
+
run_analyzer Analyzers::VirusTotalIntelligence, query: query, options: options
|
15
|
+
end
|
16
|
+
end
|
17
|
+
map "vt_intel" => :virustotal_intelligence
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/mihari/database.rb
CHANGED
@@ -74,6 +74,17 @@ class EnrichmentsSchema < ActiveRecord::Migration[6.1]
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
+
class EnrichmentCreatedAtSchema < ActiveRecord::Migration[6.1]
|
78
|
+
def change
|
79
|
+
# Add created_at column because now it is able to enrich an atrifact after the creation
|
80
|
+
add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
|
81
|
+
add_column :geolocations, :created_at, :datetime, if_not_exists: true
|
82
|
+
add_column :whois_records, :created_at, :datetime, if_not_exists: true
|
83
|
+
add_column :dns_records, :created_at, :datetime, if_not_exists: true
|
84
|
+
add_column :reverse_dns_names, :created_at, :datetime, if_not_exists: true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
77
88
|
def adapter
|
78
89
|
return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
|
79
90
|
return "mysql2" if Mihari.config.database.start_with?("mysql2://")
|
@@ -101,6 +112,7 @@ module Mihari
|
|
101
112
|
InitialSchema.migrate(:up)
|
102
113
|
AddeSourceToArtifactSchema.migrate(:up)
|
103
114
|
EnrichmentsSchema.migrate(:up)
|
115
|
+
EnrichmentCreatedAtSchema.migrate(:up)
|
104
116
|
rescue StandardError
|
105
117
|
# Do nothing
|
106
118
|
end
|
@@ -116,6 +128,7 @@ module Mihari
|
|
116
128
|
InitialSchema.migrate(:down)
|
117
129
|
AddeSourceToArtifactSchema.migrate(:down)
|
118
130
|
EnrichmentsSchema.migrate(:down)
|
131
|
+
EnrichmentCreatedAtSchema.migrate(:down)
|
119
132
|
end
|
120
133
|
end
|
121
134
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Enrichers
|
5
|
+
class Base
|
6
|
+
include Mixins::Configurable
|
7
|
+
|
8
|
+
def self.inherited(child)
|
9
|
+
Mihari.enrichers << child
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Boolean]
|
13
|
+
def valid?
|
14
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "http"
|
2
|
+
require "json"
|
3
|
+
require "memist"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Enrichers
|
7
|
+
class IPInfo < Base
|
8
|
+
# @return [Boolean]
|
9
|
+
def valid?
|
10
|
+
Mihari.config.ipinfo_api_key.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def configuration_keys
|
16
|
+
%w[ipinfo_api_key]
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
include Memist::Memoizable
|
21
|
+
|
22
|
+
#
|
23
|
+
# Query IPInfo
|
24
|
+
#
|
25
|
+
# @param [String] ip
|
26
|
+
#
|
27
|
+
# @return [Mihari::Structs::IPInfo::Response, nil]
|
28
|
+
#
|
29
|
+
def query(ip)
|
30
|
+
headers = {}
|
31
|
+
token = Mihari.config.ipinfo_api_key
|
32
|
+
unless token.nil?
|
33
|
+
headers[:authorization] = "Bearer #{token}"
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
res = HTTP.headers(headers).get("https://ipinfo.io/#{ip}/json")
|
38
|
+
data = JSON.parse(res.body.to_s)
|
39
|
+
|
40
|
+
Structs::IPInfo::Response.from_dynamic! data
|
41
|
+
rescue HTTP::Error
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
memoize :query
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mihari
|
2
|
+
module Mixins
|
3
|
+
module AutonomousSystem
|
4
|
+
#
|
5
|
+
# Normalize ASN value
|
6
|
+
#
|
7
|
+
# @param [String, Integer] asn
|
8
|
+
#
|
9
|
+
# @return [Integer]
|
10
|
+
#
|
11
|
+
def normalize_asn(asn)
|
12
|
+
return asn if asn.is_a?(Integer)
|
13
|
+
return asn.to_i unless asn.start_with?("AS")
|
14
|
+
|
15
|
+
asn.delete_prefix("AS").to_i
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -16,6 +16,8 @@ end
|
|
16
16
|
|
17
17
|
module Mihari
|
18
18
|
class Artifact < ActiveRecord::Base
|
19
|
+
belongs_to :alert
|
20
|
+
|
19
21
|
has_one :autonomous_system, dependent: :destroy
|
20
22
|
has_one :geolocation, dependent: :destroy
|
21
23
|
has_one :whois_record, dependent: :destroy
|
@@ -80,6 +82,35 @@ module Mihari
|
|
80
82
|
self.reverse_dns_names = ReverseDnsName.build_by_ip(data)
|
81
83
|
end
|
82
84
|
|
85
|
+
#
|
86
|
+
# Enrich(add) geolocation
|
87
|
+
#
|
88
|
+
def enrich_geolocation
|
89
|
+
return unless can_enrich_geolocation?
|
90
|
+
|
91
|
+
self.geolocation = Geolocation.build_by_ip(data)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Enrich(add) geolocation
|
96
|
+
#
|
97
|
+
def enrich_autonomous_system
|
98
|
+
return unless can_enrich_autonomous_system?
|
99
|
+
|
100
|
+
self.autonomous_system = AutonomousSystem.build_by_ip(data)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Enrich all the enrichable relationships of the artifact
|
105
|
+
#
|
106
|
+
def enrich_all
|
107
|
+
enrich_autonomous_system
|
108
|
+
enrich_dns
|
109
|
+
enrich_geolocation
|
110
|
+
enrich_reverse_dns
|
111
|
+
enrich_whois
|
112
|
+
end
|
113
|
+
|
83
114
|
private
|
84
115
|
|
85
116
|
def normalize_as_domain(url_or_domain)
|
@@ -89,15 +120,23 @@ module Mihari
|
|
89
120
|
end
|
90
121
|
|
91
122
|
def can_enrich_whois?
|
92
|
-
%w[domain url].include?
|
123
|
+
%w[domain url].include?(data_type) && whois_record.nil?
|
93
124
|
end
|
94
125
|
|
95
126
|
def can_enrich_dns?
|
96
|
-
%w[domain url].include?
|
127
|
+
%w[domain url].include?(data_type) && dns_records.empty?
|
97
128
|
end
|
98
129
|
|
99
130
|
def can_enrich_revese_dns?
|
100
|
-
data_type == "ip"
|
131
|
+
data_type == "ip" && reverse_dns_names.empty?
|
132
|
+
end
|
133
|
+
|
134
|
+
def can_enrich_geolocation?
|
135
|
+
data_type == "ip" && geolocation.nil?
|
136
|
+
end
|
137
|
+
|
138
|
+
def can_enrich_autonomous_system?
|
139
|
+
data_type == "ip" && autonomous_system.nil?
|
101
140
|
end
|
102
141
|
end
|
103
142
|
end
|
@@ -4,6 +4,23 @@ require "active_record"
|
|
4
4
|
|
5
5
|
module Mihari
|
6
6
|
class AutonomousSystem < ActiveRecord::Base
|
7
|
-
|
7
|
+
belongs_to :artifact
|
8
|
+
|
9
|
+
class << self
|
10
|
+
#
|
11
|
+
# Build AS
|
12
|
+
#
|
13
|
+
# @param [String] ip
|
14
|
+
#
|
15
|
+
# @return [Mihari::AutonomousSystem, nil]
|
16
|
+
#
|
17
|
+
def build_by_ip(ip)
|
18
|
+
res = Enrichers::IPInfo.query(ip)
|
19
|
+
|
20
|
+
return nil if res.nil? || res.asn.nil?
|
21
|
+
|
22
|
+
new(asn: res.asn)
|
23
|
+
end
|
24
|
+
end
|
8
25
|
end
|
9
26
|
end
|
data/lib/mihari/models/dns.rb
CHANGED
@@ -1,9 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record"
|
4
|
+
require "normalize_country"
|
4
5
|
|
5
6
|
module Mihari
|
6
7
|
class Geolocation < ActiveRecord::Base
|
7
|
-
|
8
|
+
belongs_to :artifact
|
9
|
+
|
10
|
+
class << self
|
11
|
+
#
|
12
|
+
# Build Geolocation
|
13
|
+
#
|
14
|
+
# @param [String] ip
|
15
|
+
#
|
16
|
+
# @return [Mihari::Geolocation, nil]
|
17
|
+
#
|
18
|
+
def build_by_ip(ip)
|
19
|
+
res = Enrichers::IPInfo.query(ip)
|
20
|
+
|
21
|
+
unless res.nil?
|
22
|
+
return new(country: NormalizeCountry(res.country_code, to: :short), country_code: res.country_code)
|
23
|
+
end
|
24
|
+
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
8
28
|
end
|
9
29
|
end
|
data/lib/mihari/models/whois.rb
CHANGED
data/lib/mihari/status.rb
CHANGED
@@ -18,7 +18,7 @@ module Mihari
|
|
18
18
|
# @return [Array<Hash>]
|
19
19
|
#
|
20
20
|
def statuses
|
21
|
-
(Mihari.analyzers + Mihari.emitters).map do |klass|
|
21
|
+
(Mihari.analyzers + Mihari.emitters + Mihari.enrichers).map do |klass|
|
22
22
|
name = klass.to_s.split("::").last.to_s
|
23
23
|
|
24
24
|
[name, build_status(klass)]
|
@@ -36,11 +36,16 @@ module Mihari
|
|
36
36
|
return nil if klass == Mihari::Analyzers::Rule
|
37
37
|
|
38
38
|
is_analyzer = klass.ancestors.include?(Mihari::Analyzers::Base)
|
39
|
+
is_emitter = klass.ancestors.include?(Mihari::Emitters::Base)
|
40
|
+
is_enricher = klass.ancestors.include?(Mihari::Enrichers::Base)
|
39
41
|
|
40
42
|
instance = is_analyzer ? klass.new("dummy") : klass.new
|
41
43
|
is_configured = instance.configured?
|
42
44
|
values = instance.configuration_values
|
43
|
-
|
45
|
+
|
46
|
+
type = "Analyzer"
|
47
|
+
type = "Emitter" if is_emitter
|
48
|
+
type = "Enricher" if is_enricher
|
44
49
|
|
45
50
|
values ? { is_configured: is_configured, values: values, type: type } : nil
|
46
51
|
rescue ArgumentError => _e
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "json"
|
2
|
+
require "dry/struct"
|
3
|
+
|
4
|
+
module Mihari
|
5
|
+
module Structs
|
6
|
+
module IPInfo
|
7
|
+
class Response < Dry::Struct
|
8
|
+
attribute :ip, Types::String
|
9
|
+
attribute :hostname, Types::String.optional
|
10
|
+
attribute :loc, Types::String
|
11
|
+
attribute :country_code, Types::String
|
12
|
+
attribute :asn, Types::Integer.optional
|
13
|
+
|
14
|
+
class << self
|
15
|
+
include Mixins::AutonomousSystem
|
16
|
+
|
17
|
+
def from_dynamic!(d)
|
18
|
+
d = Types::Hash[d]
|
19
|
+
|
20
|
+
asn = nil
|
21
|
+
org = d["org"]
|
22
|
+
unless org.nil?
|
23
|
+
asn = org.split.first
|
24
|
+
asn = normalize_asn(asn)
|
25
|
+
end
|
26
|
+
|
27
|
+
new(
|
28
|
+
ip: d.fetch("ip"),
|
29
|
+
loc: d.fetch("loc"),
|
30
|
+
hostname: d["hostname"],
|
31
|
+
country_code: d.fetch("country"),
|
32
|
+
asn: asn
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "json"
|
2
|
+
require "dry/struct"
|
3
|
+
|
4
|
+
module Mihari
|
5
|
+
module Structs
|
6
|
+
module VirusTotalIntelligence
|
7
|
+
class ContextAttributes < Dry::Struct
|
8
|
+
attribute :url, Types.Array(Types::String).optional
|
9
|
+
|
10
|
+
def self.from_dynamic!(d)
|
11
|
+
d = Types::Hash[d]
|
12
|
+
new(
|
13
|
+
url: d["url"]
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Datum < Dry::Struct
|
19
|
+
attribute :type, Types::String
|
20
|
+
attribute :id, Types::String
|
21
|
+
attribute :context_attributes, ContextAttributes.optional
|
22
|
+
|
23
|
+
def value
|
24
|
+
case type
|
25
|
+
when "file"
|
26
|
+
id
|
27
|
+
when "url"
|
28
|
+
(context_attributes.url || []).first
|
29
|
+
when "domain"
|
30
|
+
id
|
31
|
+
when "ip_address"
|
32
|
+
id
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_dynamic!(d)
|
37
|
+
d = Types::Hash[d]
|
38
|
+
|
39
|
+
context_attributes = nil
|
40
|
+
context_attributes = ContextAttributes.from_dynamic!(d.fetch("context_attributes")) if d.key?("context_attributes")
|
41
|
+
|
42
|
+
new(
|
43
|
+
type: d.fetch("type"),
|
44
|
+
id: d.fetch("id"),
|
45
|
+
context_attributes: context_attributes
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Meta < Dry::Struct
|
51
|
+
attribute :cursor, Types::String.optional
|
52
|
+
|
53
|
+
def self.from_dynamic!(d)
|
54
|
+
d = Types::Hash[d]
|
55
|
+
new(
|
56
|
+
cursor: d["cursor"]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Response < Dry::Struct
|
62
|
+
attribute :meta, Meta
|
63
|
+
attribute :data, Types.Array(Datum)
|
64
|
+
|
65
|
+
def self.from_dynamic!(d)
|
66
|
+
d = Types::Hash[d]
|
67
|
+
new(
|
68
|
+
meta: Meta.from_dynamic!(d.fetch("meta")),
|
69
|
+
data: d.fetch("data").map { |x| Datum.from_dynamic!(x) }
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/mihari/types.rb
CHANGED
@@ -13,9 +13,19 @@ module Mihari
|
|
13
13
|
DataTypes = Types::String.enum(*ALLOWED_DATA_TYPES)
|
14
14
|
|
15
15
|
AnalyzerTypes = Types::String.enum(
|
16
|
-
"binaryedge",
|
17
|
-
"
|
18
|
-
"
|
16
|
+
"binaryedge",
|
17
|
+
"censys",
|
18
|
+
"circl",
|
19
|
+
"dnpedia",
|
20
|
+
"dnstwister",
|
21
|
+
"onyphe",
|
22
|
+
"otx",
|
23
|
+
"passivetotal",
|
24
|
+
"pulsedive",
|
25
|
+
"securitytrails",
|
26
|
+
"shodan",
|
27
|
+
"virustotal_intelligence",
|
28
|
+
"virustotal"
|
19
29
|
)
|
20
30
|
end
|
21
31
|
end
|
data/lib/mihari/version.rb
CHANGED