mihari 7.1.3 → 7.3.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/Dockerfile +2 -2
- data/Rakefile +8 -1
- data/lefthook.yml +4 -1
- data/lib/mihari/actor.rb +16 -0
- data/lib/mihari/analyzers/base.rb +7 -25
- data/lib/mihari/analyzers/binaryedge.rb +0 -6
- data/lib/mihari/analyzers/censys.rb +0 -9
- data/lib/mihari/analyzers/circl.rb +0 -6
- data/lib/mihari/analyzers/fofa.rb +0 -6
- data/lib/mihari/analyzers/greynoise.rb +0 -6
- data/lib/mihari/analyzers/hunterhow.rb +0 -6
- data/lib/mihari/analyzers/onyphe.rb +0 -6
- data/lib/mihari/analyzers/otx.rb +0 -6
- data/lib/mihari/analyzers/passivetotal.rb +0 -4
- data/lib/mihari/analyzers/pulsedive.rb +0 -6
- data/lib/mihari/analyzers/securitytrails.rb +0 -4
- data/lib/mihari/analyzers/shodan.rb +0 -6
- data/lib/mihari/analyzers/urlscan.rb +0 -6
- data/lib/mihari/analyzers/virustotal.rb +0 -4
- data/lib/mihari/analyzers/virustotal_intelligence.rb +7 -6
- data/lib/mihari/analyzers/zoomeye.rb +0 -6
- data/lib/mihari/commands/web.rb +1 -1
- data/lib/mihari/concerns/falsepositive_normalizable.rb +30 -0
- data/lib/mihari/concerns/falsepositive_validatable.rb +1 -17
- data/lib/mihari/config.rb +1 -1
- data/lib/mihari/database.rb +18 -1
- data/lib/mihari/emitters/database.rb +0 -6
- data/lib/mihari/emitters/misp.rb +0 -6
- data/lib/mihari/emitters/slack.rb +5 -21
- data/lib/mihari/emitters/the_hive.rb +0 -6
- data/lib/mihari/enrichers/base.rb +54 -12
- data/lib/mihari/enrichers/google_public_dns.rb +28 -7
- data/lib/mihari/enrichers/mmdb.rb +25 -7
- data/lib/mihari/enrichers/shodan.rb +35 -4
- data/lib/mihari/enrichers/whois.rb +37 -31
- data/lib/mihari/entities/artifact.rb +6 -2
- data/lib/mihari/entities/autonomous_system.rb +1 -1
- data/lib/mihari/entities/cpe.rb +1 -1
- data/lib/mihari/entities/port.rb +1 -1
- data/lib/mihari/entities/vulnerability.rb +10 -0
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/models/alert.rb +12 -0
- data/lib/mihari/models/artifact.rb +118 -159
- data/lib/mihari/models/rule.rb +21 -0
- data/lib/mihari/models/vulnerability.rb +12 -0
- data/lib/mihari/rule.rb +44 -29
- data/lib/mihari/schemas/alert.rb +3 -3
- data/lib/mihari/schemas/analyzer.rb +27 -27
- data/lib/mihari/schemas/emitter.rb +9 -9
- data/lib/mihari/schemas/macros.rb +2 -2
- data/lib/mihari/schemas/options.rb +2 -5
- data/lib/mihari/schemas/rule.rb +19 -12
- data/lib/mihari/services/builders.rb +0 -134
- data/lib/mihari/services/enrichers.rb +3 -1
- data/lib/mihari/services/feed.rb +2 -5
- data/lib/mihari/services/getters.rb +1 -1
- data/lib/mihari/services/proxies.rb +3 -3
- data/lib/mihari/structs/censys.rb +2 -2
- data/lib/mihari/structs/greynoise.rb +1 -1
- data/lib/mihari/structs/onyphe.rb +1 -1
- data/lib/mihari/structs/shodan.rb +59 -21
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/artifacts.rb +4 -2
- data/lib/mihari/web/endpoints/rules.rb +1 -1
- data/lib/mihari/web/public/assets/{index-TOeU8PE2.js → index-JHS0L8KZ.js} +47 -47
- data/lib/mihari/web/public/assets/{index-dVaNxqTC.css → index-ReF8ffd-.css} +1 -1
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +17 -17
- data/lib/mihari.rb +3 -0
- data/mihari.gemspec +2 -2
- data/requirements.txt +1 -1
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 328a34edf637c36456cc7de39fccabdaaf75937b449b5e8a8e0434e71b9328c2
|
4
|
+
data.tar.gz: 8483c669cfb3e715c86b4a3878961885e4cd4f93611e487c4360cb3030267c18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9858608c1ceb30f846a27b487e4bcbbad463aba56a401991fdade512bf6a7a7f028e5facecd764b200d94795a1da1a2b631b85540dfe1bbf002c756100b6627f
|
7
|
+
data.tar.gz: c050fdafab0e7855eb610ad3d50762583ca931d31afeeb57fc654a2dc65ff381f20cb31eb28d0a61b5888c0a19df02320b09a529830fe332389b59f739721aba
|
data/Dockerfile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
FROM ruby:3.
|
1
|
+
FROM ruby:3.3.0-alpine3.19
|
2
2
|
|
3
3
|
ARG MIHARI_VERSION=0.0.0
|
4
4
|
|
5
|
-
RUN apk --no-cache add build-base ruby-dev libpq-dev && \
|
5
|
+
RUN apk --no-cache add build-base ruby-dev libpq-dev whois && \
|
6
6
|
echo 'gem: --no-document' >> /usr/local/etc/gemrc && \
|
7
7
|
gem install pg && \
|
8
8
|
gem install mihari -v ${MIHARI_VERSION} && \
|
data/Rakefile
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "time"
|
4
|
+
|
3
5
|
require "rspec/core/rake_task"
|
4
6
|
require "standard/rake"
|
5
7
|
|
@@ -46,13 +48,18 @@ namespace :build do
|
|
46
48
|
desc "Build Swagger doc"
|
47
49
|
task :swagger, [:path] do |_t, args|
|
48
50
|
args.with_defaults(path: "./frontend/swagger.yaml")
|
51
|
+
|
52
|
+
started_at = Time.now
|
49
53
|
build_swagger_doc args.path
|
54
|
+
elapsed = (Time.now - started_at).floor(2)
|
55
|
+
|
56
|
+
puts "Swagger doc is built in #{elapsed}s"
|
50
57
|
end
|
51
58
|
end
|
52
59
|
|
53
60
|
task :build do
|
54
|
-
# Build Swagger dos
|
55
61
|
Rake::Task["build:swagger"].invoke
|
62
|
+
|
56
63
|
# Build ReDocs docs & frontend assets
|
57
64
|
sh "cd frontend && npm install && npm run docs && npm run build-only"
|
58
65
|
# Copy built assets into ./lib/web/public/
|
data/lefthook.yml
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
pre-commit:
|
2
|
-
parallel: true
|
3
2
|
commands:
|
4
3
|
standard:
|
5
4
|
glob: "*.rb"
|
@@ -15,6 +14,10 @@ pre-commit:
|
|
15
14
|
glob: "*.{js,ts,vue}"
|
16
15
|
run: npx prettier --write {staged_files}
|
17
16
|
stage_fixed: true
|
17
|
+
type-check:
|
18
|
+
root: "frontend/"
|
19
|
+
glob: "*.{js,ts,vue}"
|
20
|
+
run: npm run type-check
|
18
21
|
actionlint:
|
19
22
|
glob: ".github/workflows/*.yaml"
|
20
23
|
run: actionlint
|
data/lib/mihari/actor.rb
CHANGED
@@ -50,6 +50,13 @@ module Mihari
|
|
50
50
|
options[:timeout]
|
51
51
|
end
|
52
52
|
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
def parallel?
|
57
|
+
options[:parallel] || Mihari.config.parallel
|
58
|
+
end
|
59
|
+
|
53
60
|
def validate_configuration!
|
54
61
|
return if configured?
|
55
62
|
|
@@ -93,6 +100,15 @@ module Mihari
|
|
93
100
|
([key] + [key_aliases]).flatten.compact.map(&:downcase)
|
94
101
|
end
|
95
102
|
|
103
|
+
def configuration_keys
|
104
|
+
# Automatically generate configuration keys based on key
|
105
|
+
# For example,
|
106
|
+
# - Shodan analyzer's key is "shodan"
|
107
|
+
# - Mihari.config has "shodan_api_key"
|
108
|
+
# - Select "shodan_api_key" by using "#{key}_" prefix
|
109
|
+
Mihari.config.keys.select { |config_key| config_key.start_with?("#{key}_") }
|
110
|
+
end
|
111
|
+
|
96
112
|
def type
|
97
113
|
return "analyzer" if ancestors.include?(Mihari::Analyzers::Base)
|
98
114
|
return "emitter" if ancestors.include?(Mihari::Emitters::Base)
|
@@ -40,13 +40,6 @@ module Mihari
|
|
40
40
|
options[:ignore_error] || Mihari.config.ignore_error
|
41
41
|
end
|
42
42
|
|
43
|
-
#
|
44
|
-
# @return [Boolean]
|
45
|
-
#
|
46
|
-
def parallel?
|
47
|
-
options[:parallel] || Mihari.config.parallel
|
48
|
-
end
|
49
|
-
|
50
43
|
# @return [Array<String>, Array<Mihari::Models::Artifact>]
|
51
44
|
def artifacts
|
52
45
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
@@ -63,12 +56,10 @@ module Mihari
|
|
63
56
|
artifacts.compact.sort.map do |artifact|
|
64
57
|
# No need to set data_type manually
|
65
58
|
# It is set automatically in #initialize
|
66
|
-
artifact
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
artifact
|
59
|
+
(artifact.is_a?(Models::Artifact) ? artifact : Models::Artifact.new(data: artifact)).tap do |normalized|
|
60
|
+
normalized.source = self.class.key
|
61
|
+
normalized.query = query
|
62
|
+
end
|
72
63
|
end.select(&:valid?).uniq(&:data)
|
73
64
|
end
|
74
65
|
|
@@ -118,18 +109,9 @@ module Mihari
|
|
118
109
|
#
|
119
110
|
# @return [Mihari::Analyzers::Base]
|
120
111
|
#
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
# convert params into arguments for initialization
|
125
|
-
query = copied[:query]
|
126
|
-
|
127
|
-
# delete analyzer and query
|
128
|
-
%i[analyzer query].each { |key| copied.delete key }
|
129
|
-
|
130
|
-
copied[:options] = copied[:options] || nil
|
131
|
-
|
132
|
-
new(query, **copied)
|
112
|
+
def from_params(params)
|
113
|
+
query = params.delete(:query)
|
114
|
+
new(query, **params)
|
133
115
|
end
|
134
116
|
|
135
117
|
def inherited(child)
|
data/lib/mihari/analyzers/otx.rb
CHANGED
@@ -24,12 +24,6 @@ module Mihari
|
|
24
24
|
client.intel_search_with_pagination(query, pagination_limit: pagination_limit).map(&:artifacts).flatten
|
25
25
|
end
|
26
26
|
|
27
|
-
class << self
|
28
|
-
def configuration_keys
|
29
|
-
%w[virustotal_api_key]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
27
|
class << self
|
34
28
|
#
|
35
29
|
# @return [String]
|
@@ -44,6 +38,13 @@ module Mihari
|
|
44
38
|
def key_aliases
|
45
39
|
["vt_intel"]
|
46
40
|
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @return [Array<String>]
|
44
|
+
#
|
45
|
+
def configuration_keys
|
46
|
+
%w[virustotal_api_key]
|
47
|
+
end
|
47
48
|
end
|
48
49
|
|
49
50
|
private
|
data/lib/mihari/commands/web.rb
CHANGED
@@ -12,7 +12,7 @@ module Mihari
|
|
12
12
|
desc "web", "Start the web app"
|
13
13
|
method_option :port, type: :numeric, default: 9292, desc: "Port to listen on"
|
14
14
|
method_option :host, type: :string, default: "localhost", desc: "Hostname to listen on"
|
15
|
-
method_option :threads, type: :string, default: "0:
|
15
|
+
method_option :threads, type: :string, default: "0:3", desc: "min:max threads to use"
|
16
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"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Concerns
|
5
|
+
#
|
6
|
+
# False positive normalizable concern
|
7
|
+
#
|
8
|
+
module FalsePositiveNormalizable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
prepend MemoWise
|
12
|
+
|
13
|
+
#
|
14
|
+
# Normalize a falsepositive value
|
15
|
+
#
|
16
|
+
# @param [String] value
|
17
|
+
#
|
18
|
+
# @return [String, Regexp]
|
19
|
+
#
|
20
|
+
def normalize_falsepositive(value)
|
21
|
+
return value if !value.start_with?("/") || !value.end_with?("/")
|
22
|
+
|
23
|
+
# if a value is surrounded by slashes, take it as a regexp
|
24
|
+
value_without_slashes = value[1..-2]
|
25
|
+
Regexp.compile value_without_slashes.to_s
|
26
|
+
end
|
27
|
+
memo_wise :normalize_falsepositive
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -8,23 +8,7 @@ module Mihari
|
|
8
8
|
module FalsePositiveValidatable
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
# Normalize a falsepositive value
|
15
|
-
#
|
16
|
-
# @param [String] value
|
17
|
-
#
|
18
|
-
# @return [String, Regexp]
|
19
|
-
#
|
20
|
-
def normalize_falsepositive(value)
|
21
|
-
return value if !value.start_with?("/") || !value.end_with?("/")
|
22
|
-
|
23
|
-
# if a value is surrounded by slashes, take it as a regexp
|
24
|
-
value_without_slashes = value[1..-2]
|
25
|
-
Regexp.compile value_without_slashes.to_s
|
26
|
-
end
|
27
|
-
memo_wise :normalize_falsepositive
|
11
|
+
include FalsePositiveNormalizable
|
28
12
|
|
29
13
|
#
|
30
14
|
# Check whether a value is valid format as a disallowed data value
|
data/lib/mihari/config.rb
CHANGED
data/lib/mihari/database.rb
CHANGED
@@ -104,11 +104,28 @@ class V7Schema < ActiveRecord::Migration[7.1]
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
+
class V72Schema < ActiveRecord::Migration[7.1]
|
108
|
+
def change
|
109
|
+
create_table :vulnerabilities, if_not_exists: true do |t|
|
110
|
+
t.string :name, null: false
|
111
|
+
t.datetime :created_at
|
112
|
+
|
113
|
+
t.belongs_to :artifact, foreign_key: true, null: false
|
114
|
+
end
|
115
|
+
|
116
|
+
rename_column :cpes, :cpe, :name if ActiveRecord::Base.connection.column_exists?(:cpes, :cpe)
|
117
|
+
rename_column :autonomous_systems, :asn, :number if ActiveRecord::Base.connection.column_exists?(
|
118
|
+
:autonomous_systems, :asn
|
119
|
+
)
|
120
|
+
rename_column :ports, :port, :number if ActiveRecord::Base.connection.column_exists?(:ports, :port)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
107
124
|
#
|
108
125
|
# @return [Array<ActiveRecord::Migration>] schemas
|
109
126
|
#
|
110
127
|
def schemas
|
111
|
-
[V7Schema]
|
128
|
+
[V7Schema, V72Schema]
|
112
129
|
end
|
113
130
|
|
114
131
|
module Mihari
|
data/lib/mihari/emitters/misp.rb
CHANGED
@@ -176,21 +176,11 @@ module Mihari
|
|
176
176
|
# @return [::Slack::Notifier]
|
177
177
|
#
|
178
178
|
def notifier
|
179
|
-
@notifier ||=
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
)
|
185
|
-
else
|
186
|
-
::Slack::Notifier.new(
|
187
|
-
webhook_url,
|
188
|
-
channel: channel,
|
189
|
-
username: username,
|
190
|
-
http_options: { timeout: timeout }
|
191
|
-
)
|
192
|
-
end
|
193
|
-
end.first
|
179
|
+
@notifier ||= lambda do
|
180
|
+
return ::Slack::Notifier.new(webhook_url, channel: channel, username: username) if timeout.nil?
|
181
|
+
|
182
|
+
::Slack::Notifier.new(webhook_url, channel: channel, username: username, http_options: { timeout: timeout })
|
183
|
+
end.call
|
194
184
|
end
|
195
185
|
|
196
186
|
#
|
@@ -227,12 +217,6 @@ module Mihari
|
|
227
217
|
|
228
218
|
notifier.post(text: text, attachments: attachments, mrkdwn: true)
|
229
219
|
end
|
230
|
-
|
231
|
-
class << self
|
232
|
-
def configuration_keys
|
233
|
-
%w[slack_webhook_url slack_channel]
|
234
|
-
end
|
235
|
-
end
|
236
220
|
end
|
237
221
|
end
|
238
222
|
end
|
@@ -6,46 +6,88 @@ module Mihari
|
|
6
6
|
# Base class for enrichers
|
7
7
|
#
|
8
8
|
class Base < Actor
|
9
|
-
|
10
|
-
|
9
|
+
#
|
10
|
+
# @param [Hash, nil] options
|
11
|
+
#
|
11
12
|
def initialize(options: nil)
|
12
13
|
super(options: options)
|
13
14
|
end
|
14
15
|
|
15
16
|
#
|
16
|
-
#
|
17
|
+
# Enrich an artifact
|
18
|
+
#
|
19
|
+
# @param [Mihari::Models::Artifact] artifact
|
17
20
|
#
|
18
|
-
|
21
|
+
# @return [Mihari::Models::Artifact]
|
22
|
+
#
|
23
|
+
def call(artifact)
|
19
24
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
20
25
|
end
|
21
26
|
|
22
27
|
#
|
23
|
-
# @param [Mihari::Models::Artifact]
|
28
|
+
# @param [Mihari::Models::Artifact] artifact
|
24
29
|
#
|
25
30
|
# @return [Dry::Monads::Result::Success<Object>, Dry::Monads::Result::Failure]
|
26
31
|
#
|
27
|
-
def result(
|
32
|
+
def result(artifact)
|
33
|
+
return unless callable?(artifact)
|
34
|
+
|
28
35
|
result = Try[StandardError] do
|
29
|
-
retry_on_error(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
) { call value }
|
36
|
+
retry_on_error(times: retry_times, interval: retry_interval,
|
37
|
+
exponential_backoff: retry_exponential_backoff) do
|
38
|
+
call artifact
|
39
|
+
end
|
34
40
|
end.to_result
|
35
41
|
|
36
42
|
if result.failure?
|
37
|
-
Mihari.logger.warn("Enricher:#{self.class.key} for #{
|
43
|
+
Mihari.logger.warn("Enricher:#{self.class.key} for #{artifact.data.truncate(32)} failed: #{result.failure}")
|
38
44
|
end
|
39
45
|
|
40
46
|
result
|
41
47
|
end
|
42
48
|
|
49
|
+
#
|
50
|
+
# @param [Mihari::Models::Artifact] artifact
|
51
|
+
#
|
52
|
+
# @return [Boolean]
|
53
|
+
#
|
54
|
+
def callable?(artifact)
|
55
|
+
callable_data_type?(artifact) && callable_relationships?(artifact)
|
56
|
+
end
|
57
|
+
|
43
58
|
class << self
|
44
59
|
def inherited(child)
|
45
60
|
super
|
46
61
|
Mihari.enrichers << child
|
47
62
|
end
|
48
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
#
|
68
|
+
# @param [Mihari::Models::Artifact] artifact
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
#
|
72
|
+
def callable_data_type?(artifact)
|
73
|
+
supported_data_types.include? artifact.data_type
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# @param [Mihari::Models::Artifact] artifact
|
78
|
+
#
|
79
|
+
# @return [Boolean]
|
80
|
+
#
|
81
|
+
def callable_relationships?(artifact)
|
82
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# @return [Array<String>]
|
87
|
+
#
|
88
|
+
def supported_data_types
|
89
|
+
[]
|
90
|
+
end
|
49
91
|
end
|
50
92
|
end
|
51
93
|
end
|