mihari 3.7.0 → 3.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6e9001e10ac0891e2e10d90bccbd165afbdfc3bcd2724a3d5adf521b2be843b
4
- data.tar.gz: 98119fae9302eb4251ceda56c1ddb0ab615fbf36d67505fd8af2ffb08f1dcfaf
3
+ metadata.gz: 0a3dcac61a2835aa2f940ab90edf363b10293d0537baf8b1748212686770495f
4
+ data.tar.gz: 1f1bb0515227d8cb842e511b3045298bec2b981706ddacd0750b9a6a11aa2625
5
5
  SHA512:
6
- metadata.gz: 92ae37318ffb97ab4746cb44c266e63fba5c505a6c81c141be362c11ecf4e399850f1921b2907bf511f6b0a4ea5884642282f319962af1628fc8bf9e69503fa8
7
- data.tar.gz: 45f2a2a748bb362a0daa7f9c3c7da5329cb7bab002f1ddcec4a56d111102d311471b3904b2d77f50f406e05f43deeacbd8f235e5ff4ae2dd2a0096a4dd98074d
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
@@ -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
@@ -14,6 +14,7 @@ module Mihari
14
14
  run_analyzer Analyzers::PassiveTotal, query: indicator, options: options
15
15
  end
16
16
  end
17
+ map "pt" => :passivetotal
17
18
  end
18
19
  end
19
20
  end
@@ -14,6 +14,7 @@ module Mihari
14
14
  run_analyzer Analyzers::VirusTotal, query: indiactor, options: options
15
15
  end
16
16
  end
17
+ map "vt" => :virustotal
17
18
  end
18
19
  end
19
20
  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
 
@@ -17,11 +17,9 @@ module Mihari
17
17
  def build_by_ip(ip)
18
18
  res = Enrichers::IPInfo.query(ip)
19
19
 
20
- unless res.nil?
21
- return new(asn: res.asn)
22
- end
20
+ return nil if res.nil? || res.asn.nil?
23
21
 
24
- nil
22
+ new(asn: res.asn)
25
23
  end
26
24
  end
27
25
  end
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
- type = is_analyzer ? "Analyzer" : "Emitter"
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
- org = d.fetch("org")
21
- asn = org.split.first
22
- asn = normalize_asn(asn)
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
- end
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", "censys", "circl", "dnpedia", "dnstwister",
17
- "onyphe", "otx", "passivetotal", "pulsedive", "securitytrails",
18
- "shodan", "virustotal"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "3.7.0"
4
+ VERSION = "3.8.1"
5
5
  end
@@ -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.06d5cf1c.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.06d5cf1c.js"></script></body></html>
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>