mihari 4.7.0 → 4.7.3
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/clients/otx.rb +36 -0
- data/lib/mihari/analyzers/otx.rb +19 -11
- data/lib/mihari/analyzers/rule.rb +0 -1
- data/lib/mihari/commands/init.rb +25 -2
- data/lib/mihari/commands/search.rb +2 -7
- data/lib/mihari/commands/validator.rb +10 -5
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/models/alert.rb +6 -1
- data/lib/mihari/models/geolocation.rb +2 -4
- data/lib/mihari/models/port.rb +1 -1
- data/lib/mihari/models/rule.rb +7 -2
- data/lib/mihari/structs/filters.rb +71 -0
- data/lib/mihari/structs/ipinfo.rb +4 -4
- data/lib/mihari/structs/rule.rb +188 -139
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/alerts.rb +1 -1
- data/lib/mihari/web/endpoints/rules.rb +13 -5
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +796 -763
- data/lib/mihari/web/public/static/css/chunk-vendors.5013d549.css +7 -0
- data/lib/mihari/web/public/static/js/app.524d9ed2.js +2 -0
- data/lib/mihari/web/public/static/js/app.524d9ed2.js.map +1 -0
- data/lib/mihari/web/public/static/js/{chunk-vendors.dde2116c.js → chunk-vendors.64580a1f.js} +7 -7
- data/lib/mihari/web/public/static/js/chunk-vendors.64580a1f.js.map +1 -0
- data/lib/mihari.rb +1 -2
- data/mihari.gemspec +10 -11
- data/sig/lib/mihari/cli/base.rbs +0 -2
- data/sig/lib/mihari/models/alert.rbs +3 -3
- data/sig/lib/mihari/models/rule.rbs +2 -2
- data/sig/lib/mihari/structs/filters.rbs +40 -0
- data/sig/lib/mihari/structs/ipinfo.rbs +2 -2
- data/sig/lib/mihari/structs/rule.rbs +36 -43
- metadata +30 -49
- data/lib/mihari/mixins/rule.rb +0 -84
- data/lib/mihari/structs/alert.rb +0 -44
- data/lib/mihari/web/public/static/css/chunk-vendors.06251949.css +0 -7
- data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js +0 -2
- data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js.map +0 -1
- data/lib/mihari/web/public/static/js/app.823b5af7.js +0 -2
- data/lib/mihari/web/public/static/js/app.823b5af7.js.map +0 -1
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js +0 -25
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js.map +0 -1
- data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js.map +0 -1
- data/sig/lib/mihari/mixins/rule.rbs +0 -36
- data/sig/lib/mihari/structs/alert.rbs +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c96da785a17ea290f65f5ce17b43d7fa4a57086d8de7ee867e8369f832f7c25c
|
|
4
|
+
data.tar.gz: 7b5c2d5e14d4b1a6c6c4434387881d34d3b04c33802ad84a4430d360b4d22e6c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7d66fcac98d91d6910bcabc9a7a2b29f96e562619bcf0615e2f10e2bd25c72f5439f390836d27f083f5b8da4f6696a23358881a4d6b78a6e1ae5e5b57aaa70e
|
|
7
|
+
data.tar.gz: 4476349d708f35c064af3c4a1d126790fbb008af34a62e2a485d1f0ef72a097723233e046985cc4f88f6543f84caba480dd85f1076c3ba0638951fdcae07e8dd
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Analyzers
|
|
5
|
+
module Clients
|
|
6
|
+
class OTX
|
|
7
|
+
attr_reader :api_key
|
|
8
|
+
|
|
9
|
+
def initialize(api_key)
|
|
10
|
+
@api_key = api_key
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def query_by_ip(ip)
|
|
14
|
+
get "https://otx.alienvault.com/api/v1/indicators/IPv4/#{ip}/passive_dns"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def query_by_domain(domain)
|
|
18
|
+
get "https://otx.alienvault.com/api/v1/indicators/domain/#{domain}/passive_dns"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def headers
|
|
24
|
+
{ "x-otx-api-key": api_key }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get(url)
|
|
28
|
+
res = HTTP.get(url, headers: headers)
|
|
29
|
+
JSON.parse(res.body.to_s)
|
|
30
|
+
rescue HTTPError
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/mihari/analyzers/otx.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "mihari/analyzers/clients/otx"
|
|
4
4
|
|
|
5
5
|
module Mihari
|
|
6
6
|
module Analyzers
|
|
@@ -34,12 +34,8 @@ module Mihari
|
|
|
34
34
|
%w[otx_api_key]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
@
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def ip_client
|
|
42
|
-
@ip_client ||= ::OTX::IP.new(api_key)
|
|
37
|
+
def client
|
|
38
|
+
@client ||= Mihari::Analyzers::Clients::OTX.new(api_key)
|
|
43
39
|
end
|
|
44
40
|
|
|
45
41
|
#
|
|
@@ -73,9 +69,15 @@ module Mihari
|
|
|
73
69
|
# @return [Array<String>]
|
|
74
70
|
#
|
|
75
71
|
def domain_search
|
|
76
|
-
|
|
72
|
+
res = client.query_by_domain(query)
|
|
73
|
+
return [] if res.nil?
|
|
74
|
+
|
|
75
|
+
records = res["passive_dns"] || []
|
|
77
76
|
records.filter_map do |record|
|
|
78
|
-
|
|
77
|
+
record_type = record["record_type"]
|
|
78
|
+
address = record["address"]
|
|
79
|
+
|
|
80
|
+
address if record_type == "A"
|
|
79
81
|
end.uniq
|
|
80
82
|
end
|
|
81
83
|
|
|
@@ -85,9 +87,15 @@ module Mihari
|
|
|
85
87
|
# @return [Array<String>]
|
|
86
88
|
#
|
|
87
89
|
def ip_search
|
|
88
|
-
|
|
90
|
+
res = client.query_by_ip(query)
|
|
91
|
+
return [] if res.nil?
|
|
92
|
+
|
|
93
|
+
records = res["passive_dns"] || []
|
|
89
94
|
records.filter_map do |record|
|
|
90
|
-
|
|
95
|
+
record_type = record["record_type"]
|
|
96
|
+
hostname = record["hostname"]
|
|
97
|
+
|
|
98
|
+
hostname if record_type == "A"
|
|
91
99
|
end.uniq
|
|
92
100
|
end
|
|
93
101
|
end
|
data/lib/mihari/commands/init.rb
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Commands
|
|
5
5
|
module Initialization
|
|
6
|
-
include Mixins::Rule
|
|
7
|
-
|
|
8
6
|
def self.included(thor)
|
|
9
7
|
thor.class_eval do
|
|
10
8
|
desc "rule", "Create a rule file"
|
|
@@ -21,6 +19,31 @@ module Mihari
|
|
|
21
19
|
|
|
22
20
|
Mihari.logger.info "The rule file is initialized as #{filename}."
|
|
23
21
|
end
|
|
22
|
+
|
|
23
|
+
no_commands do
|
|
24
|
+
#
|
|
25
|
+
# Returns a template for rule
|
|
26
|
+
#
|
|
27
|
+
# @return [String] A template for rule
|
|
28
|
+
#
|
|
29
|
+
def rule_template
|
|
30
|
+
rule = Structs::Rule.from_path_or_id File.expand_path("../templates/rule.yml.erb", __dir__)
|
|
31
|
+
rule.yaml
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# Create (blank) rule file
|
|
36
|
+
#
|
|
37
|
+
# @param [String] filename
|
|
38
|
+
# @param [Dry::Files] files
|
|
39
|
+
# @param [String] template
|
|
40
|
+
#
|
|
41
|
+
# @return [nil]
|
|
42
|
+
#
|
|
43
|
+
def initialize_rule_yaml(filename, files = Dry::Files.new, template: rule_template)
|
|
44
|
+
files.write(filename, template)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
24
47
|
end
|
|
25
48
|
end
|
|
26
49
|
end
|
|
@@ -4,7 +4,6 @@ module Mihari
|
|
|
4
4
|
module Commands
|
|
5
5
|
module Search
|
|
6
6
|
include Mixins::Database
|
|
7
|
-
include Mixins::Rule
|
|
8
7
|
include Mixins::ErrorNotification
|
|
9
8
|
|
|
10
9
|
def self.included(thor)
|
|
@@ -12,14 +11,10 @@ module Mihari
|
|
|
12
11
|
desc "search [RULE]", "Search by a rule"
|
|
13
12
|
method_option :yes, type: :boolean, aliases: "-y", desc: "yes to overwrite the rule in the database"
|
|
14
13
|
def search_by_rule(path_or_id)
|
|
15
|
-
rule =
|
|
14
|
+
rule = Structs::Rule.from_path_or_id path_or_id
|
|
16
15
|
|
|
17
16
|
# validate
|
|
18
|
-
|
|
19
|
-
validate_rule! rule
|
|
20
|
-
rescue RuleValidationError => e
|
|
21
|
-
raise e
|
|
22
|
-
end
|
|
17
|
+
rule.validate!
|
|
23
18
|
|
|
24
19
|
# check update
|
|
25
20
|
id = rule.id
|
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Commands
|
|
5
5
|
module Validator
|
|
6
|
-
include Mixins::Rule
|
|
7
|
-
|
|
8
6
|
def self.included(thor)
|
|
9
7
|
thor.class_eval do
|
|
10
|
-
desc "rule [PATH]", "Validate
|
|
8
|
+
desc "rule [PATH]", "Validate rule file format"
|
|
9
|
+
#
|
|
10
|
+
# Validate format of a rule
|
|
11
|
+
#
|
|
12
|
+
# @param [String] path
|
|
13
|
+
#
|
|
14
|
+
# @return [nil]
|
|
15
|
+
#
|
|
11
16
|
def rule(path)
|
|
12
|
-
rule =
|
|
17
|
+
rule = Structs::Rule.from_path_or_id(path)
|
|
13
18
|
|
|
14
19
|
begin
|
|
15
|
-
|
|
20
|
+
rule.validate!
|
|
16
21
|
Mihari.logger.info "Valid format. The input is parsed as the following:\n#{rule.data.to_yaml}"
|
|
17
22
|
rescue RuleValidationError
|
|
18
23
|
nil
|
data/lib/mihari/errors.rb
CHANGED
data/lib/mihari/models/alert.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Mihari
|
|
|
12
12
|
#
|
|
13
13
|
# Search alerts
|
|
14
14
|
#
|
|
15
|
-
# @param [Structs::Alert::SearchFilterWithPagination] filter
|
|
15
|
+
# @param [Structs::Filters::Alert::SearchFilterWithPagination] filter
|
|
16
16
|
#
|
|
17
17
|
# @return [Array<Alert>]
|
|
18
18
|
#
|
|
@@ -58,6 +58,11 @@ module Mihari
|
|
|
58
58
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
+
#
|
|
62
|
+
# @param [Structs::Filters::Alert::SearchFilter] filter
|
|
63
|
+
#
|
|
64
|
+
# @return [Mihari::Alert]
|
|
65
|
+
#
|
|
61
66
|
def build_relation(filter)
|
|
62
67
|
artifact_ids = []
|
|
63
68
|
artifact = Artifact.includes(:autonomous_system, :dns_records, :reverse_dns_names)
|
|
@@ -17,11 +17,9 @@ module Mihari
|
|
|
17
17
|
def build_by_ip(ip)
|
|
18
18
|
res = Enrichers::IPInfo.query(ip)
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
return new(country: NormalizeCountry(res.country_code, to: :short), country_code: res.country_code)
|
|
22
|
-
end
|
|
20
|
+
return nil if res&.country_code.nil?
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
new(country: NormalizeCountry(res.country_code, to: :short), country_code: res.country_code)
|
|
25
23
|
end
|
|
26
24
|
end
|
|
27
25
|
end
|
data/lib/mihari/models/port.rb
CHANGED
data/lib/mihari/models/rule.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Mihari
|
|
|
28
28
|
#
|
|
29
29
|
# Search rules
|
|
30
30
|
#
|
|
31
|
-
# @param [Structs::Rule::SearchFilterWithPagination] filter
|
|
31
|
+
# @param [Structs::Filters::Rule::SearchFilterWithPagination] filter
|
|
32
32
|
#
|
|
33
33
|
# @return [Array<Rule>]
|
|
34
34
|
#
|
|
@@ -51,7 +51,7 @@ module Mihari
|
|
|
51
51
|
#
|
|
52
52
|
# Count alerts
|
|
53
53
|
#
|
|
54
|
-
# @param [Structs::Rule::SearchFilterWithPagination] filter
|
|
54
|
+
# @param [Structs::Filters::Rule::SearchFilterWithPagination] filter
|
|
55
55
|
#
|
|
56
56
|
# @return [Integer]
|
|
57
57
|
#
|
|
@@ -62,6 +62,11 @@ module Mihari
|
|
|
62
62
|
|
|
63
63
|
private
|
|
64
64
|
|
|
65
|
+
#
|
|
66
|
+
# @param [Structs::Filters::Rule::SearchFilter] filter
|
|
67
|
+
#
|
|
68
|
+
# @return [Mihari::Rule]
|
|
69
|
+
#
|
|
65
70
|
def build_relation(filter)
|
|
66
71
|
relation = self
|
|
67
72
|
relation = relation.includes(alerts: :tags)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Structs
|
|
5
|
+
module Filters
|
|
6
|
+
module Alert
|
|
7
|
+
class SearchFilter < Dry::Struct
|
|
8
|
+
attribute? :artifact_data, Types::String.optional
|
|
9
|
+
attribute? :description, Types::String.optional
|
|
10
|
+
attribute? :source, Types::String.optional
|
|
11
|
+
attribute? :tag_name, Types::String.optional
|
|
12
|
+
attribute? :title, Types::String.optional
|
|
13
|
+
attribute? :from_at, Types::DateTime.optional
|
|
14
|
+
attribute? :to_at, Types::DateTime.optional
|
|
15
|
+
attribute? :asn, Types::Int.optional
|
|
16
|
+
attribute? :dns_record, Types::String.optional
|
|
17
|
+
attribute? :reverse_dns_name, Types::String.optional
|
|
18
|
+
|
|
19
|
+
def valid_artifact_filters?
|
|
20
|
+
!(artifact_data || asn || dns_record || reverse_dns_name).nil?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class SearchFilterWithPagination < SearchFilter
|
|
25
|
+
attribute? :page, Types::Int.default(1)
|
|
26
|
+
attribute? :limit, Types::Int.default(10)
|
|
27
|
+
|
|
28
|
+
def without_pagination
|
|
29
|
+
SearchFilter.new(
|
|
30
|
+
artifact_data: artifact_data,
|
|
31
|
+
description: description,
|
|
32
|
+
from_at: from_at,
|
|
33
|
+
source: source,
|
|
34
|
+
tag_name: tag_name,
|
|
35
|
+
title: title,
|
|
36
|
+
to_at: to_at,
|
|
37
|
+
asn: asn,
|
|
38
|
+
dns_record: dns_record,
|
|
39
|
+
reverse_dns_name: reverse_dns_name
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Rule
|
|
46
|
+
class SearchFilter < Dry::Struct
|
|
47
|
+
attribute? :description, Types::String.optional
|
|
48
|
+
attribute? :tag_name, Types::String.optional
|
|
49
|
+
attribute? :title, Types::String.optional
|
|
50
|
+
attribute? :from_at, Types::DateTime.optional
|
|
51
|
+
attribute? :to_at, Types::DateTime.optional
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class SearchFilterWithPagination < SearchFilter
|
|
55
|
+
attribute? :page, Types::Int.default(1)
|
|
56
|
+
attribute? :limit, Types::Int.default(10)
|
|
57
|
+
|
|
58
|
+
def without_pagination
|
|
59
|
+
SearchFilter.new(
|
|
60
|
+
description: description,
|
|
61
|
+
from_at: from_at,
|
|
62
|
+
tag_name: tag_name,
|
|
63
|
+
title: title,
|
|
64
|
+
to_at: to_at
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -6,8 +6,8 @@ module Mihari
|
|
|
6
6
|
class Response < Dry::Struct
|
|
7
7
|
attribute :ip, Types::String
|
|
8
8
|
attribute :hostname, Types::String.optional
|
|
9
|
-
attribute :loc, Types::String
|
|
10
|
-
attribute :country_code, Types::String
|
|
9
|
+
attribute :loc, Types::String.optional
|
|
10
|
+
attribute :country_code, Types::String.optional
|
|
11
11
|
attribute :asn, Types::Integer.optional
|
|
12
12
|
|
|
13
13
|
class << self
|
|
@@ -23,9 +23,9 @@ module Mihari
|
|
|
23
23
|
|
|
24
24
|
new(
|
|
25
25
|
ip: d.fetch("ip"),
|
|
26
|
-
loc: d
|
|
26
|
+
loc: d["loc"],
|
|
27
27
|
hostname: d["hostname"],
|
|
28
|
-
country_code: d
|
|
28
|
+
country_code: d["country"],
|
|
29
29
|
asn: asn
|
|
30
30
|
)
|
|
31
31
|
end
|