mihari 3.7.0 → 3.8.1
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/rule.rb +24 -19
- 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/enrichers/base.rb +18 -0
- data/lib/mihari/enrichers/ipinfo.rb +12 -1
- data/lib/mihari/models/autonomous_system.rb +2 -4
- data/lib/mihari/status.rb +7 -2
- data/lib/mihari/structs/ipinfo.rb +8 -5
- data/lib/mihari/structs/virustotal_intelligence.rb +75 -0
- data/lib/mihari/types.rb +17 -3
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/static/js/app.a862ebca.js +50 -0
- data/lib/mihari/web/public/static/js/app.a862ebca.js.map +1 -0
- data/lib/mihari.rb +37 -27
- data/mihari.gemspec +5 -4
- data/sig/lib/mihari/analyzers/rule.rbs +2 -2
- 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 +2 -0
- data/sig/lib/mihari/structs/ipinfo.rbs +1 -1
- data/sig/lib/mihari/structs/virustotal_intelligence.rbs +33 -0
- data/sig/lib/mihari.rbs +2 -0
- metadata +33 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a3dcac61a2835aa2f940ab90edf363b10293d0537baf8b1748212686770495f
|
4
|
+
data.tar.gz: 1f1bb0515227d8cb842e511b3045298bec2b981706ddacd0750b9a6a11aa2625
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce4ad498c64026a3b349c927dcd5f98bb59415a232acaaf279cde6f45d8f4af18bb62776568c092e261c936e5574f487a02f1fc4089f8af05a367578f06864b5
|
7
|
+
data.tar.gz: a5223e8a9f2060374df5cb21f6dbcfd475ce66408a04356131471fbb0321ac4622fec0df7ebf354ae2b46c537460a8738beb3dda5ac35c3a11588a4a97642712
|
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
|
@@ -4,6 +4,30 @@ require "uuidtools"
|
|
4
4
|
|
5
5
|
module Mihari
|
6
6
|
module Analyzers
|
7
|
+
ANALYZER_TO_CLASS = {
|
8
|
+
"binaryedge" => BinaryEdge,
|
9
|
+
"censys" => Censys,
|
10
|
+
"circl" => CIRCL,
|
11
|
+
"crtsh" => Crtsh,
|
12
|
+
"dnpedia" => DNPedia,
|
13
|
+
"dnstwister" => DNSTwister,
|
14
|
+
"onyphe" => Onyphe,
|
15
|
+
"otx" => OTX,
|
16
|
+
"passivetotal" => PassiveTotal,
|
17
|
+
"pt" => PassiveTotal,
|
18
|
+
"pulsedive" => Pulsedive,
|
19
|
+
"securitytrails" => SecurityTrails,
|
20
|
+
"shodan" => Shodan,
|
21
|
+
"spyse" => Spyse,
|
22
|
+
"st" => SecurityTrails,
|
23
|
+
"urlscan" => Urlscan,
|
24
|
+
"virustotal_intelligence" => VirusTotalIntelligence,
|
25
|
+
"virustotal" => VirusTotal,
|
26
|
+
"vt_intel" => VirusTotalIntelligence,
|
27
|
+
"vt" => VirusTotal,
|
28
|
+
"zoomeye" => ZoomEye
|
29
|
+
}.freeze
|
30
|
+
|
7
31
|
class Rule < Base
|
8
32
|
include Mihari::Mixins::DisallowedDataValue
|
9
33
|
|
@@ -26,25 +50,6 @@ module Mihari
|
|
26
50
|
validate_analyzer_configurations
|
27
51
|
end
|
28
52
|
|
29
|
-
ANALYZER_TO_CLASS = {
|
30
|
-
"binaryedge" => BinaryEdge,
|
31
|
-
"censys" => Censys,
|
32
|
-
"circl" => CIRCL,
|
33
|
-
"crtsh" => Crtsh,
|
34
|
-
"dnpedia" => DNPedia,
|
35
|
-
"dnstwister" => DNSTwister,
|
36
|
-
"onyphe" => Onyphe,
|
37
|
-
"otx" => OTX,
|
38
|
-
"passivetotal" => PassiveTotal,
|
39
|
-
"pulsedive" => Pulsedive,
|
40
|
-
"securitytrails" => SecurityTrails,
|
41
|
-
"shodan" => Shodan,
|
42
|
-
"spyse" => Spyse,
|
43
|
-
"urlscan" => Urlscan,
|
44
|
-
"virustotal" => VirusTotal,
|
45
|
-
"zoomeye" => ZoomEye
|
46
|
-
}.freeze
|
47
|
-
|
48
53
|
#
|
49
54
|
# Returns a list of artifacts matched with queries
|
50
55
|
#
|
@@ -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
|
@@ -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
|
@@ -4,7 +4,18 @@ require "memist"
|
|
4
4
|
|
5
5
|
module Mihari
|
6
6
|
module Enrichers
|
7
|
-
class IPInfo
|
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
|
+
|
8
19
|
class << self
|
9
20
|
include Memist::Memoizable
|
10
21
|
|
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
|
@@ -9,7 +9,7 @@ module Mihari
|
|
9
9
|
attribute :hostname, Types::String.optional
|
10
10
|
attribute :loc, Types::String
|
11
11
|
attribute :country_code, Types::String
|
12
|
-
attribute :asn, Types::Integer
|
12
|
+
attribute :asn, Types::Integer.optional
|
13
13
|
|
14
14
|
class << self
|
15
15
|
include Mixins::AutonomousSystem
|
@@ -17,9 +17,12 @@ module Mihari
|
|
17
17
|
def from_dynamic!(d)
|
18
18
|
d = Types::Hash[d]
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
asn = nil
|
21
|
+
org = d["org"]
|
22
|
+
unless org.nil?
|
23
|
+
asn = org.split.first
|
24
|
+
asn = normalize_asn(asn)
|
25
|
+
end
|
23
26
|
|
24
27
|
new(
|
25
28
|
ip: d.fetch("ip"),
|
@@ -29,7 +32,7 @@ module Mihari
|
|
29
32
|
asn: asn
|
30
33
|
)
|
31
34
|
end
|
32
|
-
|
35
|
+
end
|
33
36
|
end
|
34
37
|
end
|
35
38
|
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,23 @@ 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
|
+
"pt",
|
25
|
+
"pulsedive",
|
26
|
+
"securitytrails",
|
27
|
+
"shodan",
|
28
|
+
"st",
|
29
|
+
"virustotal_intelligence",
|
30
|
+
"virustotal",
|
31
|
+
"vt_intel",
|
32
|
+
"vt"
|
19
33
|
)
|
20
34
|
end
|
21
35
|
end
|
data/lib/mihari/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Mihari</title><link href="/static/js/app.
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Mihari</title><link href="/static/js/app.a862ebca.js" rel="preload" as="script"></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/app.a862ebca.js"></script></body></html>
|