mihari 7.1.1 → 7.1.3

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: 8ea0adf73bba53f264c22f8885d44e24de5657a2a77c2b7ea3533bb5acf6e78b
4
- data.tar.gz: 77ab077d9322a22b0e399c81c057485aec1c4cdb1a14f15cbd81f1a3650f37a6
3
+ metadata.gz: d7463506c12a2476cfea5dda939e88e01150fb6539c232e102c15fca4c57ab99
4
+ data.tar.gz: fdb700f461140badc1d90b3199999a97e483e57ae9c9bfed2d0157a94f35529d
5
5
  SHA512:
6
- metadata.gz: 3b68702c146819189140c0c5626c27f7b53f94021dd21138f0ac2758366486cebb7c450e0168f76cfc3d9940ee86eb2b279eb1725a3922614f51930dcb0e6b71
7
- data.tar.gz: e30c01360a6d382e73a8e88a40a5cd3f1dba6bac9ebbbe9b87b9b51cff4a6dc59676d19eec4fe757a3a40d78a560886e434802b74fdf125e663b73ae4c7abd8a
6
+ metadata.gz: 18d9dd1f63056cd2731caa823904fd9df80ca21e63a540eab89d06e7951fa10cde498344049f3ba7de8740da2e8cfb9da9fe660f77c3a1abca5e308e36a6973c
7
+ data.tar.gz: 5a8ce97455cb0e68538af1ca2c8af5b89ea96f817d4f11cf6c00851035a20288f9bc2d2de16b58b52f3e72e988c77a2e4d8744a3fbebfd27c2cb6895d4c4dccb
data/Rakefile CHANGED
@@ -50,14 +50,15 @@ namespace :build do
50
50
  end
51
51
  end
52
52
 
53
- def ci?
54
- ENV.fetch("CI", false)
55
- end
56
-
57
- unless ci?
58
- task :build do
59
- sh "./build_frontend.sh"
60
- end
53
+ task :build do
54
+ # Build Swagger dos
55
+ Rake::Task["build:swagger"].invoke
56
+ # Build ReDocs docs & frontend assets
57
+ sh "cd frontend && npm install && npm run docs && npm run build-only"
58
+ # Copy built assets into ./lib/web/public/
59
+ sh "rm -rf ./lib/mihari/web/public/"
60
+ sh "mkdir -p ./lib/mihari/web/public/"
61
+ sh "cp -r frontend/dist/* ./lib/mihari/web/public"
61
62
  end
62
63
 
63
64
  # require it later enables doing pre-build step (= build the frontend app)
data/build_frontend.sh CHANGED
@@ -3,7 +3,7 @@
3
3
  CURRENT_DIR=${PWD}
4
4
 
5
5
  cd frontend
6
- npm ci
6
+ npm install
7
7
  npm run build
8
8
 
9
9
  trash -r ${CURRENT_DIR}/lib/mihari/web/public/
data/lib/mihari/actor.rb CHANGED
@@ -53,10 +53,10 @@ module Mihari
53
53
  def validate_configuration!
54
54
  return if configured?
55
55
 
56
- joined = self.class.configuration_keys.join(", ")
57
- be = (self.class.configuration_keys.length > 1) ? "are" : "is"
58
- message = "#{self.class.class_key} is not configured correctly. #{joined} #{be} missing."
59
- raise ConfigurationError, message
56
+ message = "#{self.class.type.capitalize}:#{self.class.key} is not configured correctly"
57
+ detail = self.class.configuration_keys.map { |key| "#{key.upcase} is missing" }
58
+
59
+ raise ConfigurationError.new(message, detail)
60
60
  end
61
61
 
62
62
  def call(*args, **kwargs)
@@ -75,22 +75,30 @@ module Mihari
75
75
  #
76
76
  # @return [String]
77
77
  #
78
- def class_key
78
+ def key
79
79
  to_s.split("::").last.downcase
80
80
  end
81
81
 
82
82
  #
83
83
  # @return [Array<String>, nil]
84
84
  #
85
- def class_key_aliases
85
+ def key_aliases
86
86
  nil
87
87
  end
88
88
 
89
89
  #
90
90
  # @return [Array<String>]
91
91
  #
92
- def class_keys
93
- ([class_key] + [class_key_aliases]).flatten.compact.map(&:downcase)
92
+ def keys
93
+ ([key] + [key_aliases]).flatten.compact.map(&:downcase)
94
+ end
95
+
96
+ def type
97
+ return "analyzer" if ancestors.include?(Mihari::Analyzers::Base)
98
+ return "emitter" if ancestors.include?(Mihari::Emitters::Base)
99
+ return "enricher" if ancestors.include?(Mihari::Enrichers::Base)
100
+
101
+ nil
94
102
  end
95
103
  end
96
104
  end
@@ -65,7 +65,7 @@ module Mihari
65
65
  # It is set automatically in #initialize
66
66
  artifact = artifact.is_a?(Models::Artifact) ? artifact : Models::Artifact.new(data: artifact)
67
67
 
68
- artifact.source = self.class.class_key
68
+ artifact.source = self.class.key
69
69
  artifact.query = query
70
70
 
71
71
  artifact
@@ -93,14 +93,23 @@ module Mihari
93
93
  return result if result.success?
94
94
 
95
95
  # Wrap failure with AnalyzerError to explicitly name a failed analyzer
96
- error = AnalyzerError.new(result.failure.message, self.class.class_key, cause: result.failure)
96
+ error = AnalyzerError.new(result.failure.message, self.class.key, cause: result.failure)
97
97
  return Failure(error) unless ignore_error?
98
98
 
99
99
  # Return Success if ignore_error? is true with logging
100
- Mihari.logger.warn("Analyzer:#{self.class.class_key} failed - #{result.failure}")
100
+ Mihari.logger.warn("Analyzer:#{self.class.key} with #{truncated_query} failed - #{result.failure}")
101
101
  Success([])
102
102
  end
103
103
 
104
+ #
105
+ # Truncate query for logging
106
+ #
107
+ # @return [String]
108
+ #
109
+ def truncated_query
110
+ query.truncate(32)
111
+ end
112
+
104
113
  class << self
105
114
  #
106
115
  # Initialize an analyzer by query params
@@ -57,7 +57,7 @@ module Mihari
57
57
  #
58
58
  # @return [Array<String>, nil]
59
59
  #
60
- def class_key_aliases
60
+ def key_aliases
61
61
  ["pt"]
62
62
  end
63
63
  end
@@ -51,7 +51,7 @@ module Mihari
51
51
  #
52
52
  # @return [Array<String>, nil]
53
53
  #
54
- def class_key_aliases
54
+ def key_aliases
55
55
  ["st"]
56
56
  end
57
57
  end
@@ -46,7 +46,7 @@ module Mihari
46
46
  #
47
47
  # @return [Array<String>, nil]
48
48
  #
49
- def class_key_aliases
49
+ def key_aliases
50
50
  ["vt"]
51
51
  end
52
52
  end
@@ -34,14 +34,14 @@ module Mihari
34
34
  #
35
35
  # @return [String]
36
36
  #
37
- def class_key
37
+ def key
38
38
  "virustotal_intelligence"
39
39
  end
40
40
 
41
41
  #
42
42
  # @return [Array<String>, nil]
43
43
  #
44
- def class_key_aliases
44
+ def key_aliases
45
45
  ["vt_intel"]
46
46
  end
47
47
  end
@@ -10,10 +10,10 @@ module Mihari
10
10
  def included(thor)
11
11
  thor.class_eval do
12
12
  desc "web", "Start the web app"
13
- method_option :port, type: :numeric, default: 9292, desc: "Hostname to listen on"
14
- method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
13
+ method_option :port, type: :numeric, default: 9292, desc: "Port to listen on"
14
+ method_option :host, type: :string, default: "localhost", desc: "Hostname to listen on"
15
15
  method_option :threads, type: :string, default: "0:5", desc: "min:max threads to use"
16
- method_option :verbose, type: :boolean, default: true, desc: "Report each request"
16
+ method_option :verbose, type: :boolean, default: false, desc: "Don't report each request"
17
17
  method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
18
18
  method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
19
19
  method_option :env, type: :string, default: "production", desc: "Environment"
@@ -5,7 +5,7 @@ module Mihari
5
5
  DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
6
6
 
7
7
  # @return [Array<Hash>]
8
- DEFAULT_EMITTERS = Emitters::Database.class_keys.map { |name| { emitter: name.downcase } }.freeze
8
+ DEFAULT_EMITTERS = Emitters::Database.keys.map { |name| { emitter: name.downcase } }.freeze
9
9
 
10
10
  # @return [Array<Hash>]
11
11
  DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
@@ -19,6 +19,14 @@ module Mihari
19
19
  @rule = rule
20
20
  end
21
21
 
22
+ # A target to emit the data
23
+ #
24
+ # @return [String]
25
+ #
26
+ def target
27
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
28
+ end
29
+
22
30
  #
23
31
  # @param [Array<Mihari::Models::Artifact>] artifacts
24
32
  #
@@ -38,7 +46,9 @@ module Mihari
38
46
  ) { call(artifacts) }
39
47
  end.to_result
40
48
 
41
- Mihari.logger.warn("Emitter:#{self.class.class_key} failed - #{result.failure}") if result.failure?
49
+ if result.failure?
50
+ Mihari.logger.warn("Emitter:#{self.class.key} for #{target.truncate(32)} failed - #{result.failure}")
51
+ end
42
52
 
43
53
  result
44
54
  end
@@ -21,6 +21,10 @@ module Mihari
21
21
  alert
22
22
  end
23
23
 
24
+ def target
25
+ Mihari.config.database_url.host || Mihari.config.database_url.to_s
26
+ end
27
+
24
28
  class << self
25
29
  def configuration_keys
26
30
  %w[database_url]
@@ -56,6 +56,13 @@ module Mihari
56
56
  })
57
57
  end
58
58
 
59
+ #
60
+ # @return [String]
61
+ #
62
+ def target
63
+ URI(url).host || "N/A"
64
+ end
65
+
59
66
  class << self
60
67
  def configuration_keys
61
68
  %w[misp_url misp_api_key]
@@ -165,6 +165,13 @@ module Mihari
165
165
  webhook_url?
166
166
  end
167
167
 
168
+ #
169
+ # @return [String]
170
+ #
171
+ def target
172
+ channel
173
+ end
174
+
168
175
  #
169
176
  # @return [::Slack::Notifier]
170
177
  #
@@ -33,6 +33,13 @@ module Mihari
33
33
  api_key? && url?
34
34
  end
35
35
 
36
+ #
37
+ # @return [String]
38
+ #
39
+ def target
40
+ URI(url).host || "N/A"
41
+ end
42
+
36
43
  #
37
44
  # Create a Hive alert
38
45
  #
@@ -55,6 +55,13 @@ module Mihari
55
55
  %w[http https].include? url.scheme.downcase
56
56
  end
57
57
 
58
+ #
59
+ # @return [String]
60
+ #
61
+ def target
62
+ URI(url).host || "N/A"
63
+ end
64
+
58
65
  #
59
66
  # @param [Array<Mihari::Models::Artifact>] artifacts
60
67
  #
@@ -33,7 +33,9 @@ module Mihari
33
33
  ) { call value }
34
34
  end.to_result
35
35
 
36
- Mihari.logger.warn("Enricher:#{self.class.class_key} failed: #{result.failure}") if result.failure?
36
+ if result.failure?
37
+ Mihari.logger.warn("Enricher:#{self.class.key} for #{value.truncate(32)} failed: #{result.failure}")
38
+ end
37
39
 
38
40
  result
39
41
  end
@@ -21,7 +21,7 @@ module Mihari
21
21
  #
22
22
  # @return [String]
23
23
  #
24
- def class_key
24
+ def key
25
25
  "google_public_dns"
26
26
  end
27
27
  end
data/lib/mihari/errors.rb CHANGED
@@ -7,7 +7,20 @@ module Mihari
7
7
 
8
8
  class ValueError < Error; end
9
9
 
10
- class ConfigurationError < Error; end
10
+ class ConfigurationError < Error
11
+ # @return [Array<String>, nil]
12
+ attr_reader :detail
13
+
14
+ #
15
+ # @param [String] msg
16
+ # @param [Array<String>, nil] detail
17
+ #
18
+ def initialize(msg, detail)
19
+ super(msg)
20
+
21
+ @detail = detail
22
+ end
23
+ end
11
24
 
12
25
  class ResponseError < Error; end
13
26
 
@@ -10,12 +10,12 @@ module Mihari
10
10
 
11
11
  # Analyzer with API key and pagination
12
12
  [
13
- Mihari::Analyzers::BinaryEdge.class_keys,
14
- Mihari::Analyzers::GreyNoise.class_keys,
15
- Mihari::Analyzers::Onyphe.class_keys,
16
- Mihari::Analyzers::Shodan.class_keys,
17
- Mihari::Analyzers::Urlscan.class_keys,
18
- Mihari::Analyzers::VirusTotalIntelligence.class_keys
13
+ Mihari::Analyzers::BinaryEdge.keys,
14
+ Mihari::Analyzers::GreyNoise.keys,
15
+ Mihari::Analyzers::Onyphe.keys,
16
+ Mihari::Analyzers::Shodan.keys,
17
+ Mihari::Analyzers::Urlscan.keys,
18
+ Mihari::Analyzers::VirusTotalIntelligence.keys
19
19
  ].each do |keys|
20
20
  key = keys.first
21
21
  const_set(key.upcase, Dry::Schema.Params do
@@ -28,10 +28,10 @@ module Mihari
28
28
 
29
29
  # Analyzer with API key
30
30
  [
31
- Mihari::Analyzers::OTX.class_keys,
32
- Mihari::Analyzers::Pulsedive.class_keys,
33
- Mihari::Analyzers::VirusTotal.class_keys,
34
- Mihari::Analyzers::SecurityTrails.class_keys
31
+ Mihari::Analyzers::OTX.keys,
32
+ Mihari::Analyzers::Pulsedive.keys,
33
+ Mihari::Analyzers::VirusTotal.keys,
34
+ Mihari::Analyzers::SecurityTrails.keys
35
35
  ].each do |keys|
36
36
  key = keys.first
37
37
  const_set(key.upcase, Dry::Schema.Params do
@@ -43,13 +43,13 @@ module Mihari
43
43
  end
44
44
 
45
45
  DNSTwister = Dry::Schema.Params do
46
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::DNSTwister.class_keys))
46
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::DNSTwister.keys))
47
47
  required(:query).value(:string)
48
48
  optional(:options).hash(AnalyzerOptions)
49
49
  end
50
50
 
51
51
  Censys = Dry::Schema.Params do
52
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Censys.class_keys))
52
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Censys.keys))
53
53
  required(:query).value(:string)
54
54
  optional(:id).value(:string)
55
55
  optional(:secret).value(:string)
@@ -57,7 +57,7 @@ module Mihari
57
57
  end
58
58
 
59
59
  CIRCL = Dry::Schema.Params do
60
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::CIRCL.class_keys))
60
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::CIRCL.keys))
61
61
  required(:query).value(:string)
62
62
  optional(:username).value(:string)
63
63
  optional(:password).value(:string)
@@ -65,7 +65,7 @@ module Mihari
65
65
  end
66
66
 
67
67
  Fofa = Dry::Schema.Params do
68
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Fofa.class_keys))
68
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Fofa.keys))
69
69
  required(:query).value(:string)
70
70
  optional(:api_key).value(:string)
71
71
  optional(:email).value(:string)
@@ -73,7 +73,7 @@ module Mihari
73
73
  end
74
74
 
75
75
  PassiveTotal = Dry::Schema.Params do
76
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::PassiveTotal.class_keys))
76
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::PassiveTotal.keys))
77
77
  required(:query).value(:string)
78
78
  optional(:username).value(:string)
79
79
  optional(:api_key).value(:string)
@@ -81,14 +81,14 @@ module Mihari
81
81
  end
82
82
 
83
83
  ZoomEye = Dry::Schema.Params do
84
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::ZoomEye.class_keys))
84
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::ZoomEye.keys))
85
85
  required(:query).value(:string)
86
86
  required(:type).value(Types::String.enum("host", "web"))
87
87
  optional(:options).hash(AnalyzerPaginationOptions)
88
88
  end
89
89
 
90
90
  Crtsh = Dry::Schema.Params do
91
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Crtsh.class_keys))
91
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Crtsh.keys))
92
92
  required(:query).value(:string)
93
93
  optional(:exclude_expired).value(:bool).default(true)
94
94
  optional(:match).value(Types::String.enum("=", "ILIKE", "LIKE", "single", "any", "FTS")).default(nil)
@@ -96,7 +96,7 @@ module Mihari
96
96
  end
97
97
 
98
98
  HunterHow = Dry::Schema.Params do
99
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::HunterHow.class_keys))
99
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::HunterHow.keys))
100
100
  required(:query).value(:string)
101
101
  required(:start_time).value(:date)
102
102
  required(:end_time).value(:date)
@@ -105,7 +105,7 @@ module Mihari
105
105
  end
106
106
 
107
107
  Feed = Dry::Schema.Params do
108
- required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Feed.class_keys))
108
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Feed.keys))
109
109
  required(:query).value(:string)
110
110
  required(:selector).value(:string)
111
111
  optional(:method).value(Types::HTTPRequestMethods).default("GET")
@@ -9,33 +9,33 @@ module Mihari
9
9
  extend Concerns::Orrable
10
10
 
11
11
  Database = Dry::Schema.Params do
12
- required(:emitter).value(Types::String.enum(*Mihari::Emitters::Database.class_keys))
12
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::Database.keys))
13
13
  optional(:options).hash(Options)
14
14
  end
15
15
 
16
16
  MISP = Dry::Schema.Params do
17
- required(:emitter).value(Types::String.enum(*Mihari::Emitters::MISP.class_keys))
17
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::MISP.keys))
18
18
  optional(:url).value(:string)
19
19
  optional(:api_key).value(:string)
20
20
  optional(:options).hash(Options)
21
21
  end
22
22
 
23
23
  TheHive = Dry::Schema.Params do
24
- required(:emitter).value(Types::String.enum(*Mihari::Emitters::TheHive.class_keys))
24
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::TheHive.keys))
25
25
  optional(:url).value(:string)
26
26
  optional(:api_key).value(:string)
27
27
  optional(:options).hash(Options)
28
28
  end
29
29
 
30
30
  Slack = Dry::Schema.Params do
31
- required(:emitter).value(Types::String.enum(*Mihari::Emitters::Slack.class_keys))
31
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::Slack.keys))
32
32
  optional(:webhook_url).value(:string)
33
33
  optional(:channel).value(:string)
34
34
  optional(:options).hash(Options)
35
35
  end
36
36
 
37
37
  Webhook = Dry::Schema.Params do
38
- required(:emitter).value(Types::String.enum(*Mihari::Emitters::Webhook.class_keys))
38
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::Webhook.keys))
39
39
  required(:url).value(:string)
40
40
  optional(:method).value(Types::HTTPRequestMethods).default("POST")
41
41
  optional(:headers).value(:hash).default({})
@@ -9,22 +9,22 @@ module Mihari
9
9
  extend Concerns::Orrable
10
10
 
11
11
  MMDB = Dry::Schema.Params do
12
- required(:enricher).value(Types::String.enum(*Mihari::Enrichers::MMDB.class_keys))
12
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::MMDB.keys))
13
13
  optional(:options).hash(Options)
14
14
  end
15
15
 
16
16
  Whois = Dry::Schema.Params do
17
- required(:enricher).value(Types::String.enum(*Mihari::Enrichers::Whois.class_keys))
17
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::Whois.keys))
18
18
  optional(:options).hash(Options)
19
19
  end
20
20
 
21
21
  Shodan = Dry::Schema.Params do
22
- required(:enricher).value(Types::String.enum(*Mihari::Enrichers::Shodan.class_keys))
22
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::Shodan.keys))
23
23
  optional(:options).hash(Options)
24
24
  end
25
25
 
26
26
  GooglePublicDNS = Dry::Schema.Params do
27
- required(:enricher).value(Types::String.enum(*Mihari::Enrichers::GooglePublicDNS.class_keys))
27
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::GooglePublicDNS.keys))
28
28
  optional(:options).hash(Options)
29
29
  end
30
30
  end
@@ -20,21 +20,6 @@ module Mihari
20
20
  attribute :items, Types.Array(Types::Hash).optional
21
21
 
22
22
  class << self
23
- #
24
- # Get a type of a class
25
- #
26
- # @param [Class<Mihari::Analyzers::Base>, Class<Mihari::Emitters::Base>, Class<Mihari::Enrichers::Base>] klass
27
- #
28
- # @return [String, nil]
29
- #
30
- def get_type(klass)
31
- return "analyzer" if klass.ancestors.include?(Mihari::Analyzers::Base)
32
- return "emitter" if klass.ancestors.include?(Mihari::Emitters::Base)
33
- return "enricher" if klass.ancestors.include?(Mihari::Enrichers::Base)
34
-
35
- nil
36
- end
37
-
38
23
  #
39
24
  # Get a dummy instance
40
25
  #
@@ -43,8 +28,7 @@ module Mihari
43
28
  # @return [Mihari::Analyzers::Base, Mihari::Emitter::Base, Mihari::Enricher::Base] dummy
44
29
  #
45
30
  def get_dummy(klass)
46
- type = get_type(klass)
47
- case type
31
+ case klass.type
48
32
  when "analyzer"
49
33
  klass.new("dummy")
50
34
  when "emitter"
@@ -62,16 +46,15 @@ module Mihari
62
46
  def from_class(klass)
63
47
  return nil if klass == Mihari::Rule
64
48
 
65
- type = get_type(klass)
66
- return nil if type.nil?
49
+ return nil if klass.type.nil?
67
50
 
68
51
  begin
69
52
  instance = get_dummy(klass)
70
53
  new(
71
- name: klass.class_key,
54
+ name: klass.key,
72
55
  items: klass.configuration_items,
73
56
  configured: instance.configured?,
74
- type: type
57
+ type: klass.type
75
58
  )
76
59
  rescue ArgumentError
77
60
  nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "7.1.1"
4
+ VERSION = "7.1.3"
5
5
  end