mihari 4.3.0 → 4.4.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/lib/mihari/analyzers/binaryedge.rb +10 -1
- data/lib/mihari/analyzers/censys.rb +26 -1
- data/lib/mihari/analyzers/circl.rb +23 -1
- data/lib/mihari/analyzers/greynoise.rb +10 -1
- data/lib/mihari/analyzers/onyphe.rb +10 -1
- data/lib/mihari/analyzers/otx.rb +8 -2
- data/lib/mihari/analyzers/passivetotal.rb +25 -3
- data/lib/mihari/analyzers/pulsedive.rb +7 -1
- data/lib/mihari/analyzers/securitytrails.rb +7 -1
- data/lib/mihari/analyzers/shodan.rb +10 -1
- data/lib/mihari/analyzers/spyse.rb +10 -1
- data/lib/mihari/analyzers/urlscan.rb +6 -1
- data/lib/mihari/analyzers/virustotal.rb +7 -2
- data/lib/mihari/analyzers/virustotal_intelligence.rb +6 -1
- data/lib/mihari/analyzers/zoomeye.rb +16 -2
- data/lib/mihari/cli/main.rb +2 -0
- data/lib/mihari/commands/search.rb +14 -0
- data/lib/mihari/commands/version.rb +18 -0
- data/lib/mihari/database.rb +10 -2
- data/lib/mihari/emitters/misp.rb +14 -8
- data/lib/mihari/emitters/slack.rb +20 -28
- data/lib/mihari/emitters/the_hive.rb +18 -6
- data/lib/mihari/entities/rule.rb +1 -12
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/mixins/configurable.rb +12 -1
- data/lib/mihari/mixins/rule.rb +16 -19
- data/lib/mihari/models/artifact.rb +7 -2
- data/lib/mihari/models/rule.rb +12 -3
- data/lib/mihari/schemas/analyzer.rb +89 -10
- data/lib/mihari/schemas/emitter.rb +35 -0
- data/lib/mihari/schemas/rule.rb +5 -62
- data/lib/mihari/structs/rule.rb +33 -5
- data/lib/mihari/types.rb +0 -25
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/rules.rb +20 -3
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +11 -11
- data/lib/mihari/web/public/static/css/app.de5845d8.css +1 -0
- data/lib/mihari/web/public/static/css/chunk-vendors.da2a7bfc.css +7 -0
- data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js +2 -0
- data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.40749592.js +2 -0
- data/lib/mihari/web/public/static/js/app.40749592.js.map +1 -0
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js +25 -0
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js.map +1 -0
- data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js +31 -0
- data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js.map +1 -0
- data/mihari.gemspec +2 -2
- data/sig/lib/mihari/analyzers/binaryedge.rbs +2 -0
- data/sig/lib/mihari/analyzers/censys.rbs +4 -0
- data/sig/lib/mihari/analyzers/circl.rbs +5 -1
- data/sig/lib/mihari/analyzers/onyphe.rbs +2 -0
- data/sig/lib/mihari/analyzers/otx.rbs +3 -1
- data/sig/lib/mihari/analyzers/passivetotal.rbs +5 -1
- data/sig/lib/mihari/analyzers/pulsedive.rbs +3 -1
- data/sig/lib/mihari/analyzers/securitytrails.rbs +3 -1
- data/sig/lib/mihari/analyzers/shodan.rbs +2 -0
- data/sig/lib/mihari/analyzers/spyse.rbs +3 -1
- data/sig/lib/mihari/analyzers/urlscan.rbs +2 -0
- data/sig/lib/mihari/analyzers/virustotal.rbs +3 -1
- data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +2 -0
- data/sig/lib/mihari/analyzers/zoomeye.rbs +3 -1
- data/sig/lib/mihari/emitters/misp.rbs +6 -0
- data/sig/lib/mihari/emitters/slack.rbs +8 -20
- data/sig/lib/mihari/emitters/the_hive.rbs +4 -0
- data/sig/lib/mihari/mixins/configurable.rbs +4 -0
- data/sig/lib/mihari/mixins/rule.rbs +2 -0
- data/sig/lib/mihari/models/rule.rbs +3 -0
- data/sig/lib/mihari/structs/rule.rbs +5 -1
- metadata +18 -6
@@ -109,48 +109,46 @@ module Mihari
|
|
109
109
|
end
|
110
110
|
|
111
111
|
class Slack < Base
|
112
|
-
|
113
|
-
|
114
|
-
|
112
|
+
DEFAULT_CHANNEL = "#general"
|
113
|
+
DEFAULT_USERNAME = "Mihari"
|
114
|
+
|
115
|
+
# @return [String, nil]
|
116
|
+
attr_reader :webhook_url
|
115
117
|
|
116
|
-
#
|
117
|
-
# Slack channel to post
|
118
|
-
#
|
119
118
|
# @return [String]
|
120
|
-
|
121
|
-
def slack_channel
|
122
|
-
Mihari.config.slack_channel || "#general"
|
123
|
-
end
|
119
|
+
attr_reader :channel
|
124
120
|
|
125
|
-
#
|
126
|
-
# Slack webhook URL
|
127
|
-
#
|
128
121
|
# @return [String]
|
129
|
-
|
130
|
-
|
131
|
-
|
122
|
+
attr_reader :username
|
123
|
+
|
124
|
+
def initialize(*args, **kwargs)
|
125
|
+
super(*args, **kwargs)
|
126
|
+
|
127
|
+
@webhook_url = kwargs[:webhook_url] || Mihari.config.slack_webhook_url
|
128
|
+
@channel = kwargs[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
|
129
|
+
@username = DEFAULT_USERNAME
|
132
130
|
end
|
133
131
|
|
134
132
|
#
|
135
|
-
# Check
|
133
|
+
# Check webhook URL is set
|
136
134
|
#
|
137
135
|
# @return [Boolean]
|
138
136
|
#
|
139
|
-
def
|
140
|
-
!
|
137
|
+
def webhook_url?
|
138
|
+
!webhook_url.nil?
|
141
139
|
end
|
142
140
|
|
143
141
|
#
|
144
|
-
# Check
|
142
|
+
# Check webhook URL is set. Alias of #webhook_url?
|
145
143
|
#
|
146
144
|
# @return [Boolean]
|
147
145
|
#
|
148
146
|
def valid?
|
149
|
-
|
147
|
+
webhook_url?
|
150
148
|
end
|
151
149
|
|
152
150
|
def notifier
|
153
|
-
@notifier ||= ::Slack::Notifier.new(
|
151
|
+
@notifier ||= ::Slack::Notifier.new(webhook_url, channel: channel, username: username)
|
154
152
|
end
|
155
153
|
|
156
154
|
#
|
@@ -193,12 +191,6 @@ module Mihari
|
|
193
191
|
|
194
192
|
notifier.post(text: text, attachments: attachments, mrkdwn: true)
|
195
193
|
end
|
196
|
-
|
197
|
-
private
|
198
|
-
|
199
|
-
def configuration_keys
|
200
|
-
%w[slack_webhook_url]
|
201
|
-
end
|
202
194
|
end
|
203
195
|
end
|
204
196
|
end
|
@@ -5,6 +5,19 @@ require "hachi"
|
|
5
5
|
module Mihari
|
6
6
|
module Emitters
|
7
7
|
class TheHive < Base
|
8
|
+
# @return [String, nil]
|
9
|
+
attr_reader :api_endpoint
|
10
|
+
|
11
|
+
# @return [String, nil]
|
12
|
+
attr_reader :api_key
|
13
|
+
|
14
|
+
def initialize(*args, **kwargs)
|
15
|
+
super(*args, **kwargs)
|
16
|
+
|
17
|
+
@api_endpoint = kwargs[:api_endpoint] || Mihari.config.thehive_api_endpoint
|
18
|
+
@api_key = kwargs[:api_key] || Mihari.config.thehive_api_key
|
19
|
+
end
|
20
|
+
|
8
21
|
# @return [Boolean]
|
9
22
|
def valid?
|
10
23
|
api_endpont? && api_key? && ping?
|
@@ -30,7 +43,7 @@ module Mihari
|
|
30
43
|
end
|
31
44
|
|
32
45
|
def api
|
33
|
-
@api ||= Hachi::API.new(api_endpoint:
|
46
|
+
@api ||= Hachi::API.new(api_endpoint: api_endpoint, api_key: api_key)
|
34
47
|
end
|
35
48
|
|
36
49
|
#
|
@@ -39,16 +52,16 @@ module Mihari
|
|
39
52
|
# @return [Boolean]
|
40
53
|
#
|
41
54
|
def api_endpont?
|
42
|
-
!
|
55
|
+
!api_endpoint.nil?
|
43
56
|
end
|
44
57
|
|
45
58
|
#
|
46
59
|
# Check whether an API key is set or not
|
47
60
|
#
|
48
61
|
# @return [Boolean]
|
49
|
-
#
|
62
|
+
#
|
50
63
|
def api_key?
|
51
|
-
!
|
64
|
+
!api_key.nil?
|
52
65
|
end
|
53
66
|
|
54
67
|
#
|
@@ -57,8 +70,7 @@ module Mihari
|
|
57
70
|
# @return [Boolean]
|
58
71
|
#
|
59
72
|
def ping?
|
60
|
-
base_url =
|
61
|
-
base_url = base_url.end_with?("/") ? base_url[0..-2] : base_url
|
73
|
+
base_url = api_endpoint.end_with?("/") ? api_endpoint[0..-2] : api_endpoint
|
62
74
|
url = "#{base_url}/index.html"
|
63
75
|
|
64
76
|
http = Net::Ping::HTTP.new(url)
|
data/lib/mihari/entities/rule.rb
CHANGED
@@ -14,20 +14,9 @@ module Mihari
|
|
14
14
|
|
15
15
|
class Rule < Grape::Entity
|
16
16
|
expose :id, documentation: { type: String, required: true }
|
17
|
-
|
18
17
|
expose :yaml, documentation: { type: String, required: true }
|
19
|
-
|
20
|
-
expose :title, documentation: { type: String, required: true }
|
21
|
-
expose :description, documentation: { type: String, required: true }
|
22
|
-
expose :queries, using: Entities::Query, documentation: { type: Entities::Query, is_array: true, required: true }
|
23
|
-
expose :emitters, using: Entities::Emitter, documentation: { type: Entities::Emitter, is_array: true, required: false }
|
24
|
-
expose :tags, documentation: { type: String, is_array: true }
|
25
|
-
expose :allowed_data_types, documentation: { type: String, is_array: true }, as: :allowedDtaTypes
|
26
|
-
expose :disallowed_data_values, documentation: { type: String, is_array: true }, as: :disallowedDataValues
|
27
|
-
expose :ignore_old_artifacts, documentation: { type: "boolean", required: true }, as: :ignoreOldArtifacts
|
28
|
-
expose :ignore_threshold, documentation: { type: Integer, required: true }, as: :ignoreThreshold
|
29
|
-
|
30
18
|
expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
|
19
|
+
expose :updated_at, documentation: { type: DateTime, required: true }, as: :updatedAt
|
31
20
|
end
|
32
21
|
|
33
22
|
class RulesWithPagination < Grape::Entity
|
data/lib/mihari/errors.rb
CHANGED
@@ -9,7 +9,9 @@ module Mihari
|
|
9
9
|
# @return [Boolean]
|
10
10
|
#
|
11
11
|
def configured?
|
12
|
-
|
12
|
+
return true if configuration_keys.empty?
|
13
|
+
|
14
|
+
configuration_keys.all? { |key| Mihari.config.send(key) } || api_key?
|
13
15
|
end
|
14
16
|
|
15
17
|
#
|
@@ -33,6 +35,15 @@ module Mihari
|
|
33
35
|
def configuration_keys
|
34
36
|
[]
|
35
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def api_key?
|
42
|
+
value = method(:api_key).call
|
43
|
+
!value.nil?
|
44
|
+
rescue NameError
|
45
|
+
true
|
46
|
+
end
|
36
47
|
end
|
37
48
|
end
|
38
49
|
end
|
data/lib/mihari/mixins/rule.rb
CHANGED
@@ -9,6 +9,10 @@ module Mihari
|
|
9
9
|
module Rule
|
10
10
|
include Mixins::Database
|
11
11
|
|
12
|
+
def load_erb_yaml(yaml)
|
13
|
+
YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date], symbolize_names: true)
|
14
|
+
end
|
15
|
+
|
12
16
|
#
|
13
17
|
# Load rule into hash
|
14
18
|
#
|
@@ -17,24 +21,24 @@ module Mihari
|
|
17
21
|
# @return [Mihari::Structs::Rule::Rule]
|
18
22
|
#
|
19
23
|
def load_rule(path_or_id)
|
20
|
-
|
24
|
+
yaml = nil
|
21
25
|
|
22
|
-
|
23
|
-
|
26
|
+
yaml = load_yaml_from_file(path_or_id) if File.exist?(path_or_id)
|
27
|
+
yaml = load_yaml_from_db(path_or_id) if yaml.nil?
|
24
28
|
|
25
|
-
Structs::Rule::Rule.
|
29
|
+
Structs::Rule::Rule.from_yaml yaml
|
26
30
|
end
|
27
31
|
|
28
|
-
def
|
29
|
-
return
|
32
|
+
def load_yaml_from_file(path)
|
33
|
+
return nil unless Pathname(path).exist?
|
30
34
|
|
31
|
-
|
35
|
+
File.read path
|
32
36
|
end
|
33
37
|
|
34
|
-
def
|
38
|
+
def load_yaml_from_db(id)
|
35
39
|
with_db_connection do
|
36
40
|
rule = Mihari::Rule.find(id)
|
37
|
-
rule.
|
41
|
+
rule.yaml || rule.symbolized_data.to_yaml
|
38
42
|
rescue ActiveRecord::RecordNotFound
|
39
43
|
raise ArgumentError, "ID:#{id} is not found in the database"
|
40
44
|
end
|
@@ -58,16 +62,9 @@ module Mihari
|
|
58
62
|
# @return [String] A template for rule
|
59
63
|
#
|
60
64
|
def rule_template
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
data = template.result
|
65
|
-
|
66
|
-
# validate the template of rule for just in case
|
67
|
-
hashed_data = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
|
68
|
-
Structs::Rule::Rule.new(hashed_data)
|
69
|
-
|
70
|
-
data
|
65
|
+
yaml = File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
|
66
|
+
Structs::Rule::Rule.from_yaml yaml
|
67
|
+
yaml
|
71
68
|
end
|
72
69
|
|
73
70
|
#
|
@@ -25,8 +25,13 @@ module Mihari
|
|
25
25
|
|
26
26
|
attr_accessor :tags
|
27
27
|
|
28
|
-
def initialize(
|
29
|
-
|
28
|
+
def initialize(*args, **kwargs)
|
29
|
+
attrs = args.first || kwargs
|
30
|
+
data_ = attrs[:data]
|
31
|
+
|
32
|
+
raise InvalidArtifactFormatError if data_.is_a?(Array) || data_.is_a?(Hash)
|
33
|
+
|
34
|
+
super(*args, **kwargs)
|
30
35
|
|
31
36
|
self.data_type = TypeChecker.type(data)
|
32
37
|
self.tags = []
|
data/lib/mihari/models/rule.rb
CHANGED
@@ -1,18 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "yaml"
|
4
|
+
|
3
5
|
module Mihari
|
4
6
|
class Rule < ActiveRecord::Base
|
5
7
|
has_many :alerts, foreign_key: :source
|
6
8
|
|
9
|
+
def symbolized_data
|
10
|
+
@symbolized_data ||= data.deep_symbolize_keys
|
11
|
+
end
|
12
|
+
|
7
13
|
#
|
8
14
|
# Returns a hash representative
|
9
15
|
#
|
10
16
|
# @return [Hash]
|
11
17
|
#
|
12
18
|
def to_h
|
13
|
-
|
14
|
-
|
15
|
-
|
19
|
+
{
|
20
|
+
id: id,
|
21
|
+
yaml: yaml || data.to_yaml,
|
22
|
+
created_at: created_at,
|
23
|
+
updated_at: updated_at
|
24
|
+
}
|
16
25
|
end
|
17
26
|
|
18
27
|
class << self
|
@@ -2,19 +2,98 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
module Schemas
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
AnalyzerOptions = Dry::Schema.Params do
|
6
|
+
optional(:interval).value(:integer)
|
7
|
+
end
|
8
|
+
|
9
|
+
AnalyzerWithoutAPIKey = Dry::Schema.Params do
|
10
|
+
required(:analyzer).value(Types::String.enum("crtsh", "dnpedia", "dnstwister"))
|
11
|
+
required(:query).value(:string)
|
12
|
+
optional(:options).hash(AnalyzerOptions)
|
13
|
+
end
|
14
|
+
|
15
|
+
AnalyzerWithAPIKey = Dry::Schema.Params do
|
16
|
+
required(:analyzer).value(
|
17
|
+
Types::String.enum(
|
18
|
+
"binaryedge",
|
19
|
+
"greynoise",
|
20
|
+
"onyphe",
|
21
|
+
"otx",
|
22
|
+
"pulsedive",
|
23
|
+
"securitytrails",
|
24
|
+
"shodan",
|
25
|
+
"st",
|
26
|
+
"virustotal_intelligence",
|
27
|
+
"virustotal",
|
28
|
+
"vt_intel",
|
29
|
+
"vt"
|
30
|
+
)
|
31
|
+
)
|
32
|
+
required(:query).value(:string)
|
33
|
+
optional(:api_key).value(:string)
|
34
|
+
optional(:options).hash(AnalyzerOptions)
|
35
|
+
end
|
36
|
+
|
37
|
+
Censys = Dry::Schema.Params do
|
38
|
+
required(:analyzer).value(Types::String.enum("censys"))
|
39
|
+
required(:query).value(:string)
|
40
|
+
optional(:id).value(:string)
|
41
|
+
optional(:secret).value(:string)
|
42
|
+
optional(:options).hash(AnalyzerOptions)
|
43
|
+
end
|
44
|
+
|
45
|
+
CIRCL = Dry::Schema.Params do
|
46
|
+
required(:analyzer).value(Types::String.enum("circl"))
|
47
|
+
required(:query).value(:string)
|
48
|
+
optional(:username).value(:string)
|
49
|
+
optional(:password).value(:string)
|
50
|
+
optional(:options).hash(AnalyzerOptions)
|
51
|
+
end
|
52
|
+
|
53
|
+
PassiveTotal = Dry::Schema.Params do
|
54
|
+
required(:analyzer).value(Types::String.enum("passivetotal", "pt"))
|
55
|
+
required(:query).value(:string)
|
56
|
+
optional(:username).value(:string)
|
57
|
+
optional(:api_key).value(:string)
|
58
|
+
optional(:options).hash(AnalyzerOptions)
|
59
|
+
end
|
60
|
+
|
61
|
+
Spyse = Dry::Schema.Params do
|
62
|
+
required(:analyzer).value(Types::String.enum("spyse"))
|
63
|
+
required(:query).value(:string)
|
64
|
+
required(:type).value(Types::String.enum("ip", "domain"))
|
65
|
+
optional(:options).hash(AnalyzerOptions)
|
66
|
+
end
|
67
|
+
|
68
|
+
ZoomEye = Dry::Schema.Params do
|
69
|
+
required(:analyzer).value(Types::String.enum("zoomeye"))
|
70
|
+
required(:query).value(:string)
|
71
|
+
required(:type).value(Types::String.enum("host", "web"))
|
72
|
+
optional(:options).hash(AnalyzerOptions)
|
73
|
+
end
|
74
|
+
|
75
|
+
Crtsh = Dry::Schema.Params do
|
76
|
+
required(:analyzer).value(Types::String.enum("crtsh"))
|
77
|
+
required(:query).value(:string)
|
78
|
+
optional(:exclude_expired).value(:bool).default(true)
|
79
|
+
optional(:options).hash(AnalyzerOptions)
|
80
|
+
end
|
10
81
|
|
11
|
-
|
12
|
-
|
13
|
-
|
82
|
+
Urlscan = Dry::Schema.Params do
|
83
|
+
required(:analyzer).value(Types::String.enum("urlscan"))
|
84
|
+
required(:query).value(:string)
|
85
|
+
optional(:options).hash(AnalyzerOptions)
|
14
86
|
end
|
15
87
|
|
16
|
-
|
17
|
-
|
88
|
+
Feed = Dry::Schema.Params do
|
89
|
+
required(:analyzer).value(Types::String.enum("feed"))
|
90
|
+
required(:query).value(:string)
|
91
|
+
required(:selector).value(:string)
|
92
|
+
optional(:http_request_method).value(Types::HttpRequestMethods).default("GET")
|
93
|
+
optional(:http_request_headers).value(:hash).default({})
|
94
|
+
optional(:http_request_payload).value(:hash).default({})
|
95
|
+
optional(:http_request_payload_type).value(Types::HttpRequestPayloadTypes)
|
96
|
+
optional(:options).hash(AnalyzerOptions)
|
18
97
|
end
|
19
98
|
end
|
20
99
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Schemas
|
5
|
+
Emitter = Dry::Schema.Params do
|
6
|
+
required(:emitter).value(Types::EmitterTypes)
|
7
|
+
end
|
8
|
+
|
9
|
+
MISP = Dry::Schema.Params do
|
10
|
+
required(:emitter).value(Types::String.enum("misp"))
|
11
|
+
optional(:api_endpoint).value(:string)
|
12
|
+
optional(:api_key).value(:string)
|
13
|
+
end
|
14
|
+
|
15
|
+
TheHive = Dry::Schema.Params do
|
16
|
+
required(:emitter).value(Types::String.enum("the_hive"))
|
17
|
+
optional(:api_endpoint).value(:string)
|
18
|
+
optional(:api_key).value(:string)
|
19
|
+
end
|
20
|
+
|
21
|
+
Slack = Dry::Schema.Params do
|
22
|
+
required(:emitter).value(Types::String.enum("slack"))
|
23
|
+
optional(:webhook_url).value(:string)
|
24
|
+
optional(:channel).value(:string)
|
25
|
+
end
|
26
|
+
|
27
|
+
HTTP = Dry::Schema.Params do
|
28
|
+
required(:emitter).value(Types::String.enum("http"))
|
29
|
+
required(:url).value(:string)
|
30
|
+
optional(:http_request_method).value(Types::HttpRequestMethods).default("POST")
|
31
|
+
optional(:http_request_headers).value(:hash).default({})
|
32
|
+
optional(:template).value(:string)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/mihari/schemas/rule.rb
CHANGED
@@ -1,67 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "mihari/schemas/analyzer"
|
4
|
+
require "mihari/schemas/emitter"
|
5
|
+
|
3
6
|
module Mihari
|
4
7
|
module Schemas
|
5
|
-
AnalyzerOptions = Dry::Schema.Params do
|
6
|
-
optional(:interval).value(:integer)
|
7
|
-
end
|
8
|
-
|
9
|
-
Analyzer = Dry::Schema.Params do
|
10
|
-
required(:analyzer).value(Types::AnalyzerTypes)
|
11
|
-
required(:query).value(:string)
|
12
|
-
optional(:options).hash(AnalyzerOptions)
|
13
|
-
end
|
14
|
-
|
15
|
-
Spyse = Dry::Schema.Params do
|
16
|
-
required(:analyzer).value(Types::String.enum("spyse"))
|
17
|
-
required(:query).value(:string)
|
18
|
-
required(:type).value(Types::String.enum("ip", "domain"))
|
19
|
-
optional(:options).hash(AnalyzerOptions)
|
20
|
-
end
|
21
|
-
|
22
|
-
ZoomEye = Dry::Schema.Params do
|
23
|
-
required(:analyzer).value(Types::String.enum("zoomeye"))
|
24
|
-
required(:query).value(:string)
|
25
|
-
required(:type).value(Types::String.enum("host", "web"))
|
26
|
-
optional(:options).hash(AnalyzerOptions)
|
27
|
-
end
|
28
|
-
|
29
|
-
Crtsh = Dry::Schema.Params do
|
30
|
-
required(:analyzer).value(Types::String.enum("crtsh"))
|
31
|
-
required(:query).value(:string)
|
32
|
-
optional(:exclude_expired).value(:bool).default(true)
|
33
|
-
optional(:options).hash(AnalyzerOptions)
|
34
|
-
end
|
35
|
-
|
36
|
-
Urlscan = Dry::Schema.Params do
|
37
|
-
required(:analyzer).value(Types::String.enum("urlscan"))
|
38
|
-
required(:query).value(:string)
|
39
|
-
optional(:options).hash(AnalyzerOptions)
|
40
|
-
end
|
41
|
-
|
42
|
-
Feed = Dry::Schema.Params do
|
43
|
-
required(:analyzer).value(Types::String.enum("feed"))
|
44
|
-
required(:query).value(:string)
|
45
|
-
required(:selector).value(:string)
|
46
|
-
optional(:http_request_method).value(Types::HttpRequestMethods).default("GET")
|
47
|
-
optional(:http_request_headers).value(:hash).default({})
|
48
|
-
optional(:http_request_payload).value(:hash).default({})
|
49
|
-
optional(:http_request_payload_type).value(Types::HttpRequestPayloadTypes)
|
50
|
-
optional(:options).hash(AnalyzerOptions)
|
51
|
-
end
|
52
|
-
|
53
|
-
Emitter = Dry::Schema.Params do
|
54
|
-
required(:emitter).value(Types::EmitterTypes)
|
55
|
-
end
|
56
|
-
|
57
|
-
HTTP = Dry::Schema.Params do
|
58
|
-
required(:emitter).value(Types::String.enum("http"))
|
59
|
-
required(:url).value(:string)
|
60
|
-
optional(:http_request_method).value(Types::HttpRequestMethods).default("POST")
|
61
|
-
optional(:http_request_headers).value(:hash).default({})
|
62
|
-
optional(:template).value(:string)
|
63
|
-
end
|
64
|
-
|
65
8
|
Rule = Dry::Schema.Params do
|
66
9
|
required(:title).value(:string)
|
67
10
|
required(:description).value(:string)
|
@@ -73,9 +16,9 @@ module Mihari
|
|
73
16
|
optional(:created_on).value(:date)
|
74
17
|
optional(:updated_on).value(:date)
|
75
18
|
|
76
|
-
required(:queries).value(:array).each {
|
19
|
+
required(:queries).value(:array).each { AnalyzerWithoutAPIKey | AnalyzerWithAPIKey | Censys | CIRCL | PassiveTotal | Spyse | ZoomEye | Urlscan | Crtsh | Feed }
|
77
20
|
|
78
|
-
optional(:emitters).value(:array).each { Emitter | HTTP }
|
21
|
+
optional(:emitters).value(:array).each { Emitter | MISP | TheHive | Slack | HTTP }
|
79
22
|
|
80
23
|
optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
|
81
24
|
optional(:disallowed_data_values).value(array[:string]).default([])
|
data/lib/mihari/structs/rule.rb
CHANGED
@@ -27,10 +27,19 @@ module Mihari
|
|
27
27
|
end
|
28
28
|
|
29
29
|
class Rule
|
30
|
-
|
30
|
+
# @return [Hash]
|
31
|
+
attr_reader :data
|
31
32
|
|
32
|
-
|
33
|
+
# @return [String]
|
34
|
+
attr_reader :yaml
|
35
|
+
|
36
|
+
# @return [Array, nil]
|
37
|
+
attr_reader :errors
|
38
|
+
|
39
|
+
def initialize(data, yaml)
|
33
40
|
@data = data.deep_symbolize_keys
|
41
|
+
@yaml = yaml
|
42
|
+
|
34
43
|
@errors = nil
|
35
44
|
@no_method_error = nil
|
36
45
|
|
@@ -76,7 +85,7 @@ module Mihari
|
|
76
85
|
|
77
86
|
raise RuleValidationError, error_messages.join("\n") if errors?
|
78
87
|
|
79
|
-
raise RuleValidationError, "Something wrong with queries" unless @no_method_error.nil?
|
88
|
+
raise RuleValidationError, "Something wrong with queries or emitters." unless @no_method_error.nil?
|
80
89
|
end
|
81
90
|
|
82
91
|
def [](key)
|
@@ -109,16 +118,20 @@ module Mihari
|
|
109
118
|
#
|
110
119
|
def to_model
|
111
120
|
rule = Mihari::Rule.find(id)
|
121
|
+
|
112
122
|
rule.title = title
|
113
123
|
rule.description = description
|
114
124
|
rule.data = data
|
125
|
+
rule.yaml = yaml
|
126
|
+
|
115
127
|
rule
|
116
128
|
rescue ActiveRecord::RecordNotFound
|
117
129
|
Mihari::Rule.new(
|
118
130
|
id: id,
|
119
131
|
title: title,
|
120
132
|
description: description,
|
121
|
-
data: data
|
133
|
+
data: data,
|
134
|
+
yaml: yaml
|
122
135
|
)
|
123
136
|
end
|
124
137
|
|
@@ -143,13 +156,28 @@ module Mihari
|
|
143
156
|
end
|
144
157
|
|
145
158
|
class << self
|
159
|
+
include Mixins::Rule
|
160
|
+
|
146
161
|
#
|
147
162
|
# @param [Mihari::Rule] model
|
148
163
|
#
|
149
164
|
# @return [Mihari::Structs::Rule::Rule]
|
150
165
|
#
|
151
166
|
def from_model(model)
|
152
|
-
|
167
|
+
data = model.data.deep_symbolize_keys
|
168
|
+
data[:id] = model.id unless data.key?(:id)
|
169
|
+
|
170
|
+
Structs::Rule::Rule.new(data, model.yaml)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# @param [String] yaml
|
175
|
+
#
|
176
|
+
# @return [Mihari::Structs::Rule::Rule]
|
177
|
+
#
|
178
|
+
def from_yaml(yaml)
|
179
|
+
data = load_erb_yaml(yaml)
|
180
|
+
Structs::Rule::Rule.new(data, yaml)
|
153
181
|
end
|
154
182
|
end
|
155
183
|
end
|
data/lib/mihari/types.rb
CHANGED
@@ -16,36 +16,11 @@ module Mihari
|
|
16
16
|
|
17
17
|
UrlscanDataTypes = Types::String.enum("ip", "domain", "url")
|
18
18
|
|
19
|
-
AnalyzerTypes = Types::String.enum(
|
20
|
-
"binaryedge",
|
21
|
-
"censys",
|
22
|
-
"circl",
|
23
|
-
"dnpedia",
|
24
|
-
"dnstwister",
|
25
|
-
"greynoise",
|
26
|
-
"onyphe",
|
27
|
-
"otx",
|
28
|
-
"passivetotal",
|
29
|
-
"pt",
|
30
|
-
"pulsedive",
|
31
|
-
"securitytrails",
|
32
|
-
"shodan",
|
33
|
-
"st",
|
34
|
-
"virustotal_intelligence",
|
35
|
-
"virustotal",
|
36
|
-
"vt_intel",
|
37
|
-
"vt"
|
38
|
-
)
|
39
|
-
|
40
19
|
HttpRequestMethods = Types::String.enum("GET", "POST")
|
41
20
|
HttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
|
42
21
|
|
43
22
|
EmitterTypes = Types::String.enum(
|
44
23
|
"database",
|
45
|
-
"http",
|
46
|
-
"misp",
|
47
|
-
"slack",
|
48
|
-
"the_hive",
|
49
24
|
"webhook"
|
50
25
|
)
|
51
26
|
end
|
data/lib/mihari/version.rb
CHANGED