mihari 2.4.0 → 3.0.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/.gitignore +7 -0
- data/.overcommit.yml +12 -0
- data/README.md +1 -9
- data/exe/mihari +1 -1
- data/lib/mihari.rb +88 -15
- data/lib/mihari/analyzers/base.rb +49 -8
- data/lib/mihari/analyzers/basic.rb +1 -2
- data/lib/mihari/analyzers/binaryedge.rb +7 -13
- data/lib/mihari/analyzers/censys.rb +26 -63
- data/lib/mihari/analyzers/circl.rb +20 -17
- data/lib/mihari/analyzers/crtsh.rb +6 -13
- data/lib/mihari/analyzers/dnpedia.rb +6 -12
- data/lib/mihari/analyzers/dnstwister.rb +13 -10
- data/lib/mihari/analyzers/onyphe.rb +6 -12
- data/lib/mihari/analyzers/otx.rb +22 -19
- data/lib/mihari/analyzers/passivetotal.rb +22 -21
- data/lib/mihari/analyzers/pulsedive.rb +16 -13
- data/lib/mihari/analyzers/rule.rb +99 -0
- data/lib/mihari/analyzers/securitytrails.rb +22 -19
- data/lib/mihari/analyzers/shodan.rb +7 -13
- data/lib/mihari/analyzers/spyse.rb +12 -19
- data/lib/mihari/analyzers/urlscan.rb +22 -27
- data/lib/mihari/analyzers/virustotal.rb +25 -22
- data/lib/mihari/analyzers/zoomeye.rb +14 -20
- data/lib/mihari/cli/analyzer.rb +44 -0
- data/lib/mihari/cli/base.rb +27 -0
- data/lib/mihari/cli/init.rb +13 -0
- data/lib/mihari/cli/main.rb +30 -0
- data/lib/mihari/cli/mixins/utils.rb +88 -0
- data/lib/mihari/cli/validator.rb +11 -0
- data/lib/mihari/commands/binaryedge.rb +1 -1
- data/lib/mihari/commands/censys.rb +1 -1
- data/lib/mihari/commands/circl.rb +2 -2
- data/lib/mihari/commands/crtsh.rb +1 -1
- data/lib/mihari/commands/dnpedia.rb +1 -1
- data/lib/mihari/commands/dnstwister.rb +2 -2
- data/lib/mihari/commands/init.rb +46 -0
- data/lib/mihari/commands/json.rb +1 -1
- data/lib/mihari/commands/onyphe.rb +1 -1
- data/lib/mihari/commands/otx.rb +2 -2
- data/lib/mihari/commands/passivetotal.rb +2 -2
- data/lib/mihari/commands/pulsedive.rb +2 -2
- data/lib/mihari/commands/search.rb +77 -0
- data/lib/mihari/commands/securitytrails.rb +2 -2
- data/lib/mihari/commands/shodan.rb +1 -1
- data/lib/mihari/commands/spyse.rb +1 -1
- data/lib/mihari/commands/urlscan.rb +2 -2
- data/lib/mihari/commands/validator.rb +38 -0
- data/lib/mihari/commands/virustotal.rb +2 -2
- data/lib/mihari/commands/zoomeye.rb +1 -1
- data/lib/mihari/constraints.rb +5 -0
- data/lib/mihari/database.rb +13 -2
- data/lib/mihari/emitters/base.rb +2 -2
- data/lib/mihari/emitters/database.rb +1 -1
- data/lib/mihari/emitters/misp.rb +1 -1
- data/lib/mihari/emitters/slack.rb +5 -6
- data/lib/mihari/emitters/the_hive.rb +1 -1
- data/lib/mihari/emitters/webhook.rb +2 -9
- data/lib/mihari/mixins/configurable.rb +38 -0
- data/lib/mihari/mixins/configuration.rb +85 -0
- data/lib/mihari/mixins/hash.rb +20 -0
- data/lib/mihari/mixins/refang.rb +21 -0
- data/lib/mihari/mixins/retriable.rb +27 -0
- data/lib/mihari/mixins/rule.rb +79 -0
- data/lib/mihari/models/alert.rb +28 -1
- data/lib/mihari/models/artifact.rb +10 -0
- data/lib/mihari/notifiers/base.rb +9 -1
- data/lib/mihari/notifiers/exception_notifier.rb +50 -0
- data/lib/mihari/notifiers/slack.rb +29 -0
- data/lib/mihari/schemas/configuration.rb +42 -0
- data/lib/mihari/schemas/macros.rb +17 -0
- data/lib/mihari/schemas/rule.rb +72 -0
- data/lib/mihari/serializers/artifact.rb +1 -1
- data/lib/mihari/status.rb +14 -0
- data/lib/mihari/templates/rule.yml.erb +19 -0
- data/lib/mihari/type_checker.rb +8 -3
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/controllers/base_controller.rb +1 -1
- data/lib/mihari/web/public/index.html +1 -21
- data/lib/mihari/web/public/redoc-static.html +2 -2
- data/lib/mihari/web/public/static/js/app.ab213f7c.js +12 -0
- data/lib/mihari/web/public/static/js/app.ab213f7c.js.map +1 -0
- data/mihari.gemspec +12 -5
- metadata +123 -50
- data/.rubocop.yml +0 -161
- data/lib/mihari/analyzers/free_text.rb +0 -48
- data/lib/mihari/analyzers/http_hash.rb +0 -100
- data/lib/mihari/analyzers/passive_dns.rb +0 -59
- data/lib/mihari/analyzers/passive_ssl.rb +0 -55
- data/lib/mihari/analyzers/reverse_whois.rb +0 -55
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +0 -59
- data/lib/mihari/analyzers/ssh_fingerprint.rb +0 -58
- data/lib/mihari/cli.rb +0 -126
- data/lib/mihari/commands/config.rb +0 -27
- data/lib/mihari/commands/free_text.rb +0 -21
- data/lib/mihari/commands/http_hash.rb +0 -25
- data/lib/mihari/commands/passive_dns.rb +0 -21
- data/lib/mihari/commands/passive_ssl.rb +0 -21
- data/lib/mihari/commands/reverse_whois.rb +0 -21
- data/lib/mihari/commands/securitytrails_domain_feed.rb +0 -23
- data/lib/mihari/commands/ssh_fingerprint.rb +0 -21
- data/lib/mihari/config.rb +0 -85
- data/lib/mihari/configurable.rb +0 -21
- data/lib/mihari/html.rb +0 -43
- data/lib/mihari/retriable.rb +0 -17
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
require "pathname"
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "erb"
|
|
5
|
+
|
|
6
|
+
module Mihari
|
|
7
|
+
module Mixins
|
|
8
|
+
module Rule
|
|
9
|
+
#
|
|
10
|
+
# Load rule into hash
|
|
11
|
+
#
|
|
12
|
+
# @param [String] path Path to YAML file or YAML string
|
|
13
|
+
#
|
|
14
|
+
# @return [Hash]
|
|
15
|
+
#
|
|
16
|
+
def load_rule(path)
|
|
17
|
+
return YAML.safe_load(File.read(path), permitted_classes: [Date], symbolize_names: true) if Pathname(path).exist?
|
|
18
|
+
|
|
19
|
+
YAML.safe_load(path, symbolize_names: true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Validate rule schema
|
|
24
|
+
#
|
|
25
|
+
# @param [Hash] rule
|
|
26
|
+
#
|
|
27
|
+
def validate_rule(rule)
|
|
28
|
+
error_message = "Failed to parse the input as a rule!"
|
|
29
|
+
|
|
30
|
+
contract = Schemas::RuleContract.new
|
|
31
|
+
begin
|
|
32
|
+
result = contract.call(rule)
|
|
33
|
+
unless result.errors.empty?
|
|
34
|
+
messages = result.errors.messages.map do |message|
|
|
35
|
+
path = message.path.map(&:to_s).join
|
|
36
|
+
"#{path} #{message.text}"
|
|
37
|
+
end
|
|
38
|
+
puts error_message.colorize(:red)
|
|
39
|
+
raise ArgumentError, messages.join("\n")
|
|
40
|
+
end
|
|
41
|
+
rescue NoMethodError
|
|
42
|
+
puts error_message.colorize(:red)
|
|
43
|
+
raise ArgumentError, "Invalid rule schema"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# Returns a template for rule
|
|
49
|
+
#
|
|
50
|
+
# @return [String] A template for rule
|
|
51
|
+
#
|
|
52
|
+
def rule_template
|
|
53
|
+
# Use ERB to fill created_on and updated_on with Date.today
|
|
54
|
+
data = File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
|
|
55
|
+
template = ERB.new(data)
|
|
56
|
+
data = template.result
|
|
57
|
+
|
|
58
|
+
# validate the template of rule for just in case
|
|
59
|
+
rule = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
|
|
60
|
+
validate_rule rule
|
|
61
|
+
|
|
62
|
+
data
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Create (blank) rule file
|
|
67
|
+
#
|
|
68
|
+
# @param [String] filename
|
|
69
|
+
# @param [Dry::Files] files
|
|
70
|
+
# @param [String] template
|
|
71
|
+
#
|
|
72
|
+
# @return [nil]
|
|
73
|
+
#
|
|
74
|
+
def initialize_rule_yaml(filename, files = Dry::Files.new, template: rule_template)
|
|
75
|
+
files.write(filename, template)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/mihari/models/alert.rb
CHANGED
|
@@ -10,6 +10,21 @@ module Mihari
|
|
|
10
10
|
has_many :tags, through: :taggings
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
|
+
#
|
|
14
|
+
# Search alerts
|
|
15
|
+
#
|
|
16
|
+
# @param [String, nil] artifact_data
|
|
17
|
+
# @param [String, nil] description
|
|
18
|
+
# @param [String, nil] source
|
|
19
|
+
# @param [String, nil] tag_name
|
|
20
|
+
# @param [String, nil] title
|
|
21
|
+
# @param [String, nil] from_at
|
|
22
|
+
# @param [String, nil] to_at
|
|
23
|
+
# @param [Integer, nil] limit
|
|
24
|
+
# @param [Integer, nil] page
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<Hash>]
|
|
27
|
+
#
|
|
13
28
|
def search(artifact_data: nil, description: nil, source: nil, tag_name: nil, title: nil, from_at: nil, to_at: nil, limit: 10, page: 1)
|
|
14
29
|
limit = limit.to_i
|
|
15
30
|
raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
|
|
@@ -20,7 +35,6 @@ module Mihari
|
|
|
20
35
|
offset = (page - 1) * limit
|
|
21
36
|
|
|
22
37
|
relation = build_relation(artifact_data: artifact_data, title: title, description: description, source: source, tag_name: tag_name, from_at: from_at, to_at: to_at)
|
|
23
|
-
# relation = relation.group("alerts.id")
|
|
24
38
|
|
|
25
39
|
alerts = relation.limit(limit).offset(offset).order(id: :desc)
|
|
26
40
|
|
|
@@ -32,6 +46,19 @@ module Mihari
|
|
|
32
46
|
end
|
|
33
47
|
end
|
|
34
48
|
|
|
49
|
+
#
|
|
50
|
+
# Count alerts
|
|
51
|
+
#
|
|
52
|
+
# @param [String, nil] artifact_data
|
|
53
|
+
# @param [String, nil] description
|
|
54
|
+
# @param [String, nil] source
|
|
55
|
+
# @param [String, nil] tag_name
|
|
56
|
+
# @param [String, nil] title
|
|
57
|
+
# @param [String, nil] from_at
|
|
58
|
+
# @param [String, nil] to_at
|
|
59
|
+
#
|
|
60
|
+
# @return [Integer]
|
|
61
|
+
#
|
|
35
62
|
def count(artifact_data: nil, description: nil, source: nil, tag_name: nil, title: nil, from_at: nil, to_at: nil)
|
|
36
63
|
relation = build_relation(artifact_data: artifact_data, title: title, description: description, source: source, tag_name: tag_name, from_at: from_at, to_at: to_at)
|
|
37
64
|
relation.distinct("alerts.id").count
|
|
@@ -16,13 +16,23 @@ end
|
|
|
16
16
|
module Mihari
|
|
17
17
|
class Artifact < ActiveRecord::Base
|
|
18
18
|
include ActiveModel::Validations
|
|
19
|
+
|
|
19
20
|
validates_with ArtifactValidator
|
|
20
21
|
|
|
21
22
|
def initialize(attributes)
|
|
22
23
|
super
|
|
24
|
+
|
|
23
25
|
self.data_type = TypeChecker.type(data)
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
#
|
|
29
|
+
# Check uniqueness of artifact
|
|
30
|
+
#
|
|
31
|
+
# @param [Boolean] ignore_old_artifacts
|
|
32
|
+
# @param [Integer] ignore_threshold
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean] true if it is unique. Otherwise false.
|
|
35
|
+
#
|
|
26
36
|
def unique?(ignore_old_artifacts: false, ignore_threshold: 0)
|
|
27
37
|
artifact = self.class.where(data: data).order(created_at: :desc).first
|
|
28
38
|
return true if artifact.nil?
|
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Notifiers
|
|
5
5
|
class Base
|
|
6
|
-
#
|
|
6
|
+
# Validate notifier availability
|
|
7
|
+
#
|
|
8
|
+
# @return [Boolean]
|
|
9
|
+
#
|
|
7
10
|
def valid?
|
|
8
11
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
|
9
12
|
end
|
|
10
13
|
|
|
14
|
+
#
|
|
15
|
+
# Send a notification
|
|
16
|
+
#
|
|
17
|
+
# @return [nil]
|
|
18
|
+
#
|
|
11
19
|
def notify
|
|
12
20
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
|
13
21
|
end
|
|
@@ -24,10 +24,25 @@ module Mihari
|
|
|
24
24
|
notify_to_slack(text: clean_message, attachments: attachments) if @slack.valid?
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
#
|
|
28
|
+
# Send notification to Slack
|
|
29
|
+
#
|
|
30
|
+
# @param [String] text
|
|
31
|
+
# @param [Array<Hash>] attachments
|
|
32
|
+
#
|
|
33
|
+
# @return [nil]
|
|
34
|
+
#
|
|
27
35
|
def notify_to_slack(text:, attachments:)
|
|
28
36
|
@slack.notify(text: text, attachments: attachments)
|
|
29
37
|
end
|
|
30
38
|
|
|
39
|
+
#
|
|
40
|
+
# Send notification to STDOUT
|
|
41
|
+
#
|
|
42
|
+
# @param [Exception] exception
|
|
43
|
+
#
|
|
44
|
+
# @return [nil]
|
|
45
|
+
#
|
|
31
46
|
def notify_to_stdout(exception)
|
|
32
47
|
text = to_text(exception.class).chomp
|
|
33
48
|
message = "#{text}: #{exception.message}"
|
|
@@ -35,6 +50,14 @@ module Mihari
|
|
|
35
50
|
puts format_backtrace(exception.backtrace) if exception.backtrace
|
|
36
51
|
end
|
|
37
52
|
|
|
53
|
+
#
|
|
54
|
+
# Convert exception to attachments (for Slack)
|
|
55
|
+
#
|
|
56
|
+
# @param [Exception] exception
|
|
57
|
+
# @param [String] clean_message
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Hash>]
|
|
60
|
+
#
|
|
38
61
|
def to_attachments(exception, clean_message)
|
|
39
62
|
text = to_text(exception.class)
|
|
40
63
|
backtrace = exception.backtrace
|
|
@@ -43,12 +66,27 @@ module Mihari
|
|
|
43
66
|
[color: @color, text: text, fields: fields, mrkdwn_in: %w[text fields]]
|
|
44
67
|
end
|
|
45
68
|
|
|
69
|
+
#
|
|
70
|
+
# Convert exception class to text
|
|
71
|
+
#
|
|
72
|
+
# @param [Class<Exception>] exception_class
|
|
73
|
+
#
|
|
74
|
+
# @return [String]
|
|
75
|
+
#
|
|
46
76
|
def to_text(exception_class)
|
|
47
77
|
measure_word = /^[aeiou]/i.match?(exception_class.to_s) ? "An" : "A"
|
|
48
78
|
exception_name = "*#{measure_word}* `#{exception_class}`"
|
|
49
79
|
"#{exception_name} *occured in background*\n"
|
|
50
80
|
end
|
|
51
81
|
|
|
82
|
+
#
|
|
83
|
+
# Convert clean_message and backtrace into fields (for Slack)
|
|
84
|
+
#
|
|
85
|
+
# @param [String] clean_message
|
|
86
|
+
# @param [Array] backtrace
|
|
87
|
+
#
|
|
88
|
+
# @return [Array<Hash>]
|
|
89
|
+
#
|
|
52
90
|
def to_fields(clean_message, backtrace)
|
|
53
91
|
fields = [
|
|
54
92
|
{ title: "Exception", value: clean_message },
|
|
@@ -62,12 +100,24 @@ module Mihari
|
|
|
62
100
|
fields
|
|
63
101
|
end
|
|
64
102
|
|
|
103
|
+
#
|
|
104
|
+
# Hostname of runnning instance
|
|
105
|
+
#
|
|
106
|
+
# @return [String]
|
|
107
|
+
#
|
|
65
108
|
def hostname
|
|
66
109
|
Socket.gethostname
|
|
67
110
|
rescue StandardError => _e
|
|
68
111
|
"N/A"
|
|
69
112
|
end
|
|
70
113
|
|
|
114
|
+
#
|
|
115
|
+
# Format backtrace in string
|
|
116
|
+
#
|
|
117
|
+
# @param [Array] backtrace
|
|
118
|
+
#
|
|
119
|
+
# @return [String]
|
|
120
|
+
#
|
|
71
121
|
def format_backtrace(backtrace)
|
|
72
122
|
return nil unless backtrace
|
|
73
123
|
|
|
@@ -9,22 +9,51 @@ module Mihari
|
|
|
9
9
|
SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
|
|
10
10
|
DEFAULT_USERNAME = "mihari"
|
|
11
11
|
|
|
12
|
+
#
|
|
13
|
+
# Slack channel to post
|
|
14
|
+
#
|
|
15
|
+
# @return [String]
|
|
16
|
+
#
|
|
12
17
|
def slack_channel
|
|
13
18
|
Mihari.config.slack_channel || "#general"
|
|
14
19
|
end
|
|
15
20
|
|
|
21
|
+
#
|
|
22
|
+
# Slack webhook URL
|
|
23
|
+
#
|
|
24
|
+
# @return [String]
|
|
25
|
+
#
|
|
16
26
|
def slack_webhook_url
|
|
17
27
|
Mihari.config.slack_webhook_url
|
|
18
28
|
end
|
|
19
29
|
|
|
30
|
+
#
|
|
31
|
+
# Check Slack webhook URL is set
|
|
32
|
+
#
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
#
|
|
20
35
|
def slack_webhook_url?
|
|
21
36
|
!Mihari.config.slack_webhook_url.nil?
|
|
22
37
|
end
|
|
23
38
|
|
|
39
|
+
#
|
|
40
|
+
# Check Slack webhook URL is set. Alias of #slack_webhook_url?.
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
#
|
|
24
44
|
def valid?
|
|
25
45
|
slack_webhook_url?
|
|
26
46
|
end
|
|
27
47
|
|
|
48
|
+
#
|
|
49
|
+
# Send notification to Slack
|
|
50
|
+
#
|
|
51
|
+
# @param [String] text
|
|
52
|
+
# @param [Array<Hash>] attachments
|
|
53
|
+
# @param [Boolean] mrkdwn
|
|
54
|
+
#
|
|
55
|
+
# @return [nil]
|
|
56
|
+
#
|
|
28
57
|
def notify(text:, attachments: [], mrkdwn: true)
|
|
29
58
|
notifier = ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel, username: DEFAULT_USERNAME)
|
|
30
59
|
notifier.post(text: text, attachments: attachments, mrkdwn: mrkdwn)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema"
|
|
4
|
+
require "dry/validation"
|
|
5
|
+
|
|
6
|
+
require "mihari/schemas/macros"
|
|
7
|
+
|
|
8
|
+
module Mihari
|
|
9
|
+
module Schemas
|
|
10
|
+
Configuration = Dry::Schema.Params do
|
|
11
|
+
optional(:binaryedge_api_key).value(:string)
|
|
12
|
+
optional(:censys_id).value(:string)
|
|
13
|
+
optional(:censys_secret).value(:string)
|
|
14
|
+
optional(:circl_passive_password).value(:string)
|
|
15
|
+
optional(:circl_passive_username).value(:string)
|
|
16
|
+
optional(:misp_api_endpoint).value(:string)
|
|
17
|
+
optional(:misp_api_key).value(:string)
|
|
18
|
+
optional(:onyphe_api_key).value(:string)
|
|
19
|
+
optional(:otx_api_key).value(:string)
|
|
20
|
+
optional(:passivetotal_api_key).value(:string)
|
|
21
|
+
optional(:passivetotal_username).value(:string)
|
|
22
|
+
optional(:pulsedive_api_key).value(:string)
|
|
23
|
+
optional(:securitytrails_api_key).value(:string)
|
|
24
|
+
optional(:shodan_api_key).value(:string)
|
|
25
|
+
optional(:slack_channel).value(:string)
|
|
26
|
+
optional(:slack_webhook_url).value(:string)
|
|
27
|
+
optional(:spyse_api_key).value(:string)
|
|
28
|
+
optional(:thehive_api_endpoint).value(:string)
|
|
29
|
+
optional(:thehive_api_key).value(:string)
|
|
30
|
+
optional(:urlscan_api_key).value(:string)
|
|
31
|
+
optional(:virustotal_api_key).value(:string)
|
|
32
|
+
optional(:zoomeye_api_key).value(:string)
|
|
33
|
+
optional(:webhook_url).value(:string)
|
|
34
|
+
optional(:webhook_use_json_body).value(:bool)
|
|
35
|
+
optional(:database).value(:string)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class ConfigurationContract < Dry::Validation::Contract
|
|
39
|
+
params(Configuration)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/types"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
module Macros
|
|
8
|
+
class DSL
|
|
9
|
+
def default(value)
|
|
10
|
+
schema_dsl.before(:rule_applier) do |result|
|
|
11
|
+
result.update(name => value) unless result[name]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema"
|
|
4
|
+
require "dry/validation"
|
|
5
|
+
require "dry/types"
|
|
6
|
+
|
|
7
|
+
require "mihari/schemas/macros"
|
|
8
|
+
|
|
9
|
+
module Mihari
|
|
10
|
+
module Types
|
|
11
|
+
include Dry.Types()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
DataTypes = Types::String.enum(*ALLOWED_DATA_TYPES)
|
|
15
|
+
|
|
16
|
+
AnalyzerTypes = Types::String.enum(
|
|
17
|
+
"binaryedge", "censys", "circl", "dnpedia", "dnstwister",
|
|
18
|
+
"onyphe", "otx", "passivetotal", "pulsedive", "securitytrails",
|
|
19
|
+
"shodan", "virustotal"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
module Schemas
|
|
23
|
+
Analyzer = Dry::Schema.Params do
|
|
24
|
+
required(:analyzer).value(AnalyzerTypes)
|
|
25
|
+
required(:query).value(:string)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Spyse = Dry::Schema.Params do
|
|
29
|
+
required(:analyzer).value(Types::String.enum("spyse"))
|
|
30
|
+
required(:query).value(:string)
|
|
31
|
+
required(:type).value(Types::String.enum("ip", "domain"))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
ZoomEye = Dry::Schema.Params do
|
|
35
|
+
required(:analyzer).value(Types::String.enum("zoomeye"))
|
|
36
|
+
required(:query).value(:string)
|
|
37
|
+
required(:type).value(Types::String.enum("host", "web"))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Crtsh = Dry::Schema.Params do
|
|
41
|
+
required(:analyzer).value(Types::String.enum("crtsh"))
|
|
42
|
+
required(:query).value(:string)
|
|
43
|
+
optional(:exclude_expired).value(:bool).default(true)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Urlscan = Dry::Schema.Params do
|
|
47
|
+
required(:analyzer).value(Types::String.enum("urlscan"))
|
|
48
|
+
required(:query).value(:string)
|
|
49
|
+
optional(:use_similarity).value(:bool).default(true)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Rule = Dry::Schema.Params do
|
|
53
|
+
required(:title).value(:string)
|
|
54
|
+
required(:description).value(:string)
|
|
55
|
+
|
|
56
|
+
optional(:tags).value(array[:string]).default([])
|
|
57
|
+
optional(:id).value(:string)
|
|
58
|
+
|
|
59
|
+
optional(:author).value(:string)
|
|
60
|
+
optional(:created_on).value(:date)
|
|
61
|
+
optional(:updated_on).value(:date)
|
|
62
|
+
|
|
63
|
+
required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh }
|
|
64
|
+
|
|
65
|
+
optional(:allowed_data_types).value(array[DataTypes]).default(ALLOWED_DATA_TYPES)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class RuleContract < Dry::Validation::Contract
|
|
69
|
+
params(Rule)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|