mihari 3.6.1 → 3.8.0
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/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