mihari 6.2.0 → 6.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/lib/mihari/entities/artifact.rb +8 -0
- data/lib/mihari/models/alert.rb +4 -27
- data/lib/mihari/models/artifact.rb +64 -2
- data/lib/mihari/models/rule.rb +2 -5
- data/lib/mihari/structs/filters.rb +53 -9
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/alerts.rb +2 -10
- data/lib/mihari/web/endpoints/artifacts.rb +64 -0
- data/lib/mihari/web/endpoints/exports.rb +0 -0
- data/lib/mihari/web/endpoints/rules.rb +1 -8
- data/lib/mihari/web/public/assets/index-81613_nX.js +1763 -0
- data/lib/mihari/web/public/assets/index-Wv6xUrTI.css +1 -0
- data/lib/mihari/web/public/index.html +2 -3
- data/lib/mihari/web/public/redoc-static.html +19 -15
- data/mihari.gemspec +4 -4
- data/requirements.txt +1 -1
- metadata +14 -13
- data/lib/mihari/web/public/assets/index-1d77cd61.js +0 -1756
- data/lib/mihari/web/public/assets/index-4c8509ee.css +0 -1
- /data/lib/mihari/web/public/assets/{mode-yaml-24faa242.js → mode-yaml-BC4MIiYj.js} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f027c5c24759f4e09d5dad277df4cad58a1705478eba1f3b9b6b354a896a29a5
|
4
|
+
data.tar.gz: a908c432c313bab4f4af70b0b32fb1f2e65c9f40926dc3099c4869330080005c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8110df87f854b6e06ea8477c94c6563b2450dae4585738f256da9c94203d14c6fb3ce11e775a470dae04d4d3934549ae71068e6b3f3d3eca48ea3689d87a4cd4
|
7
|
+
data.tar.gz: 375855d7dba59d13ff894472f5f6d5ccc82f36de73aeb12c4144c05863c64283f2ff451dd4531643ba3ddbb218181937d57da1a42bb28970f78170e3fae5dfbf
|
@@ -42,5 +42,13 @@ module Mihari
|
|
42
42
|
status.ports.empty? ? nil : status.ports
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
class ArtifactsWithPagination < Grape::Entity
|
47
|
+
expose :artifacts, using: Entities::Artifact,
|
48
|
+
documentation: { type: Entities::Artifact, is_array: true, required: true }
|
49
|
+
expose :total, documentation: { type: Integer, required: true }
|
50
|
+
expose :current_page, documentation: { type: Integer, required: true }, as: :currentPage
|
51
|
+
expose :page_size, documentation: { type: Integer, required: true }, as: :pageSize
|
52
|
+
end
|
45
53
|
end
|
46
54
|
end
|
data/lib/mihari/models/alert.rb
CHANGED
@@ -30,8 +30,7 @@ module Mihari
|
|
30
30
|
offset = (page - 1) * limit
|
31
31
|
|
32
32
|
relation = build_relation(filter.without_pagination)
|
33
|
-
|
34
|
-
eager_load(:artifacts, :tags).where(id: [alert_ids]).order(id: :desc)
|
33
|
+
relation.limit(limit).offset(offset).order(id: :desc)
|
35
34
|
end
|
36
35
|
|
37
36
|
#
|
@@ -48,39 +47,17 @@ module Mihari
|
|
48
47
|
|
49
48
|
private
|
50
49
|
|
51
|
-
#
|
52
|
-
# @param [Mihari::Structs::Filters::Alert::SearchFilter] filter
|
53
|
-
#
|
54
|
-
# @return [Array<Integer>]
|
55
|
-
#
|
56
|
-
def get_artifact_ids_by_filter(filter)
|
57
|
-
artifact_ids = []
|
58
|
-
|
59
|
-
if filter.artifact_data
|
60
|
-
artifact = Artifact.where(data: filter.artifact_data)
|
61
|
-
artifact_ids = artifact.pluck(:id)
|
62
|
-
# set invalid ID if nothing is matched with the filters
|
63
|
-
artifact_ids = [-1] if artifact_ids.empty?
|
64
|
-
end
|
65
|
-
|
66
|
-
artifact_ids
|
67
|
-
end
|
68
|
-
|
69
50
|
#
|
70
51
|
# @param [Mihari::Structs::Filters::Alert::SearchFilter] filter
|
71
52
|
#
|
72
53
|
# @return [Mihari::Models::Alert]
|
73
54
|
#
|
74
55
|
def build_relation(filter)
|
75
|
-
|
76
|
-
|
77
|
-
relation = includes(:artifacts, :tags)
|
78
|
-
|
79
|
-
relation = relation.where(artifacts: { id: artifact_ids }) unless artifact_ids.empty?
|
80
|
-
relation = relation.where(tags: { name: filter.tag_name }) if filter.tag_name
|
56
|
+
relation = eager_load(:artifacts, :tags)
|
81
57
|
|
58
|
+
relation = relation.where(artifacts: { data: filter.artifact }) if filter.artifact
|
59
|
+
relation = relation.where(tags: { name: filter.tag }) if filter.tag
|
82
60
|
relation = relation.where(rule_id: filter.rule_id) if filter.rule_id
|
83
|
-
|
84
61
|
relation = relation.where("alerts.created_at >= ?", filter.from_at) if filter.from_at
|
85
62
|
relation = relation.where("alerts.created_at <= ?", filter.to_at) if filter.to_at
|
86
63
|
|
@@ -77,6 +77,18 @@ module Mihari
|
|
77
77
|
artifact.created_at < decayed_at
|
78
78
|
end
|
79
79
|
|
80
|
+
#
|
81
|
+
# Count artifacts
|
82
|
+
#
|
83
|
+
# @param [Mihari::Structs::Filters::Artifact::SearchFilter] filter
|
84
|
+
#
|
85
|
+
# @return [Integer]
|
86
|
+
#
|
87
|
+
def count(filter)
|
88
|
+
relation = build_relation(filter)
|
89
|
+
relation.distinct("artifact.id").count
|
90
|
+
end
|
91
|
+
|
80
92
|
#
|
81
93
|
# Enrich whois record
|
82
94
|
#
|
@@ -105,7 +117,7 @@ module Mihari
|
|
105
117
|
# @param [Mihari::Enrichers::Shodan] enricher
|
106
118
|
#
|
107
119
|
def enrich_reverse_dns(enricher = Enrichers::Shodan.new)
|
108
|
-
return unless
|
120
|
+
return unless can_enrich_reverse_dns?
|
109
121
|
|
110
122
|
self.reverse_dns_names = ReverseDnsName.build_by_ip(data, enricher: enricher)
|
111
123
|
end
|
@@ -195,6 +207,56 @@ module Mihari
|
|
195
207
|
methods.each { |method| send(method, enricher) if respond_to?(method) }
|
196
208
|
end
|
197
209
|
|
210
|
+
class << self
|
211
|
+
#
|
212
|
+
# Search artifacts
|
213
|
+
#
|
214
|
+
# @param [Mihari::Structs::Filters::Artifact::SearchFilterWithPagination] filter
|
215
|
+
#
|
216
|
+
# @return [Array<Artifact>]
|
217
|
+
#
|
218
|
+
def search(filter)
|
219
|
+
limit = filter.limit.to_i
|
220
|
+
raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
|
221
|
+
|
222
|
+
page = filter.page.to_i
|
223
|
+
raise ArgumentError, "page should be bigger than zero" unless page.positive?
|
224
|
+
|
225
|
+
offset = (page - 1) * limit
|
226
|
+
|
227
|
+
relation = build_relation(filter.without_pagination)
|
228
|
+
relation.limit(limit).offset(offset).order(id: :desc)
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Count artifacts
|
233
|
+
#
|
234
|
+
# @param [Mihari::Structs::Filters::Artifact::SearchFilter] filter
|
235
|
+
#
|
236
|
+
# @return [Integer]
|
237
|
+
#
|
238
|
+
def count(filter)
|
239
|
+
relation = build_relation(filter)
|
240
|
+
relation.distinct("artifacts.id").count
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# @param [Mihari::Structs::Filters::Artifact::SearchFilter] filter
|
245
|
+
#
|
246
|
+
# @return [Mihari::Models::Artifact]
|
247
|
+
#
|
248
|
+
def build_relation(filter)
|
249
|
+
relation = eager_load(alert: :tags)
|
250
|
+
|
251
|
+
relation = relation.where(alert: { rule_id: filter.rule_id }) if filter.rule_id
|
252
|
+
relation = relation.where(alert: { tags: { name: filter.tag } }) if filter.tag
|
253
|
+
relation = relation.where("artifacts.created_at >= ?", filter.from_at) if filter.from_at
|
254
|
+
relation = relation.where("artifacts.created_at <= ?", filter.to_at) if filter.to_at
|
255
|
+
|
256
|
+
relation
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
198
260
|
private
|
199
261
|
|
200
262
|
def ipinfo
|
@@ -219,7 +281,7 @@ module Mihari
|
|
219
281
|
%w[domain url].include?(data_type) && dns_records.empty?
|
220
282
|
end
|
221
283
|
|
222
|
-
def
|
284
|
+
def can_enrich_reverse_dns?
|
223
285
|
data_type == "ip" && reverse_dns_names.empty?
|
224
286
|
end
|
225
287
|
|
data/lib/mihari/models/rule.rb
CHANGED
@@ -40,10 +40,7 @@ module Mihari
|
|
40
40
|
offset = (page - 1) * limit
|
41
41
|
|
42
42
|
relation = build_relation(filter.without_pagination)
|
43
|
-
|
44
|
-
# TODO: improve queires
|
45
|
-
rule_ids = relation.limit(limit).offset(offset).order(created_at: :desc).pluck(:id).uniq
|
46
|
-
where(id: [rule_ids]).order(created_at: :desc)
|
43
|
+
relation.limit(limit).offset(offset).order(created_at: :desc)
|
47
44
|
end
|
48
45
|
|
49
46
|
#
|
@@ -68,7 +65,7 @@ module Mihari
|
|
68
65
|
def build_relation(filter)
|
69
66
|
relation = includes(alerts: :tags)
|
70
67
|
|
71
|
-
relation = relation.where(alerts: { tags: { name: filter.
|
68
|
+
relation = relation.where(alerts: { tags: { name: filter.tag } }) if filter.tag
|
72
69
|
|
73
70
|
relation = relation.where("rules.title LIKE ?", "%#{filter.title}%") if filter.title
|
74
71
|
relation = relation.where("rules.description LIKE ?", "%#{filter.description}%") if filter.description
|
@@ -3,19 +3,63 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Structs
|
5
5
|
module Filters
|
6
|
+
module Artifact
|
7
|
+
class SearchFilter < Dry::Struct
|
8
|
+
# @!attribute [r] data_type
|
9
|
+
# @return [String, nil]
|
10
|
+
attribute? :data_type, Types::String.optional
|
11
|
+
|
12
|
+
# @!attribute [r] rule_id
|
13
|
+
# @return [String, nil]
|
14
|
+
attribute? :rule_id, Types::String.optional
|
15
|
+
|
16
|
+
# @!attribute [r] tag
|
17
|
+
# @return [String, nil]
|
18
|
+
attribute? :tag, Types::String.optional
|
19
|
+
|
20
|
+
# @!attribute [r] from_at
|
21
|
+
# @return [DateTime, nil]
|
22
|
+
attribute? :from_at, Types::DateTime.optional
|
23
|
+
|
24
|
+
# @!attribute [r] to_at
|
25
|
+
# @return [DateTime, nil]
|
26
|
+
attribute? :to_at, Types::DateTime.optional
|
27
|
+
end
|
28
|
+
|
29
|
+
class SearchFilterWithPagination < SearchFilter
|
30
|
+
# @!attribute [r] page
|
31
|
+
# @return [Integer, nil]
|
32
|
+
attribute? :page, Types::Int.default(1)
|
33
|
+
|
34
|
+
# @!attribute [r] limit
|
35
|
+
# @return [Integer, nil]
|
36
|
+
attribute? :limit, Types::Int.default(10)
|
37
|
+
|
38
|
+
def without_pagination
|
39
|
+
SearchFilter.new(
|
40
|
+
data_type: data_type,
|
41
|
+
from_at: from_at,
|
42
|
+
rule_id: rule_id,
|
43
|
+
tag: tag,
|
44
|
+
to_at: to_at
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
6
50
|
module Alert
|
7
51
|
class SearchFilter < Dry::Struct
|
8
|
-
# @!attribute [r]
|
52
|
+
# @!attribute [r] artifact
|
9
53
|
# @return [String, nil]
|
10
|
-
attribute? :
|
54
|
+
attribute? :artifact, Types::String.optional
|
11
55
|
|
12
56
|
# @!attribute [r] rule_id
|
13
57
|
# @return [String, nil]
|
14
58
|
attribute? :rule_id, Types::String.optional
|
15
59
|
|
16
|
-
# @!attribute [r]
|
60
|
+
# @!attribute [r] tag
|
17
61
|
# @return [String, nil]
|
18
|
-
attribute? :
|
62
|
+
attribute? :tag, Types::String.optional
|
19
63
|
|
20
64
|
# @!attribute [r] from_at
|
21
65
|
# @return [DateTime, nil]
|
@@ -37,10 +81,10 @@ module Mihari
|
|
37
81
|
|
38
82
|
def without_pagination
|
39
83
|
SearchFilter.new(
|
40
|
-
|
84
|
+
artifact: artifact,
|
41
85
|
from_at: from_at,
|
42
86
|
rule_id: rule_id,
|
43
|
-
|
87
|
+
tag: tag,
|
44
88
|
to_at: to_at
|
45
89
|
)
|
46
90
|
end
|
@@ -53,9 +97,9 @@ module Mihari
|
|
53
97
|
# @return [String, nil]
|
54
98
|
attribute? :description, Types::String.optional
|
55
99
|
|
56
|
-
# @!attribute [r]
|
100
|
+
# @!attribute [r] tag
|
57
101
|
# @return [String, nil]
|
58
|
-
attribute? :
|
102
|
+
attribute? :tag, Types::String.optional
|
59
103
|
|
60
104
|
# @!attribute [r] title
|
61
105
|
# @return [String, nil]
|
@@ -83,7 +127,7 @@ module Mihari
|
|
83
127
|
SearchFilter.new(
|
84
128
|
description: description,
|
85
129
|
from_at: from_at,
|
86
|
-
|
130
|
+
tag: tag,
|
87
131
|
title: title,
|
88
132
|
to_at: to_at
|
89
133
|
)
|
data/lib/mihari/version.rb
CHANGED
@@ -36,18 +36,10 @@ module Mihari
|
|
36
36
|
# @return [ResultValue]
|
37
37
|
#
|
38
38
|
def call(params)
|
39
|
-
filter = params.to_h.to_snake_keys
|
40
|
-
|
41
|
-
# normalize keys
|
42
|
-
filter["artifact_data"] = filter["artifact"]
|
43
|
-
filter["tag_name"] = filter["tag"]
|
44
|
-
# symbolize hash keys
|
45
|
-
filter = filter.to_h.symbolize_keys
|
46
|
-
|
39
|
+
filter = params.to_h.to_snake_keys.symbolize_keys
|
47
40
|
search_filter_with_pagination = Structs::Filters::Alert::SearchFilterWithPagination.new(**filter)
|
48
41
|
alerts = Mihari::Models::Alert.search(search_filter_with_pagination)
|
49
42
|
total = Mihari::Models::Alert.count(search_filter_with_pagination.without_pagination)
|
50
|
-
|
51
43
|
ResultValue.new(alerts: alerts, total: total, filter: filter)
|
52
44
|
end
|
53
45
|
end
|
@@ -83,7 +75,7 @@ module Mihari
|
|
83
75
|
optional :page, type: Integer, default: 1
|
84
76
|
optional :limit, type: Integer, default: 10
|
85
77
|
optional :artifact, type: String
|
86
|
-
optional :
|
78
|
+
optional :ruleId, type: String
|
87
79
|
optional :tag, type: String
|
88
80
|
optional :fromAt, type: DateTime
|
89
81
|
optional :toAt, type: DateTime
|
@@ -61,7 +61,71 @@ module Mihari
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
class ArtifactSearcher < Mihari::Service
|
65
|
+
class ResultValue
|
66
|
+
# @return [Array<Mihari::Models::Artifacts>]
|
67
|
+
attr_reader :artifacts
|
68
|
+
|
69
|
+
# @return [Integer]
|
70
|
+
attr_reader :total
|
71
|
+
|
72
|
+
# @return [Mihari::Structs::Filters::Artifact::SearchFilterWithPagination]
|
73
|
+
attr_reader :filter
|
74
|
+
|
75
|
+
#
|
76
|
+
# @param [Array<Mihari::Models::Artifact>] artifacts
|
77
|
+
# @param [Integer] total
|
78
|
+
# @param [Mihari::Structs::Filters::Artifacts::SearchFilterWithPagination] filter
|
79
|
+
#
|
80
|
+
def initialize(artifacts:, total:, filter:)
|
81
|
+
@artifacts = artifacts
|
82
|
+
@total = total
|
83
|
+
@filter = filter
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# @param [Hash] params
|
89
|
+
#
|
90
|
+
# @return [ResultValue]
|
91
|
+
#
|
92
|
+
def call(params)
|
93
|
+
filter = params.to_h.to_snake_keys.symbolize_keys
|
94
|
+
search_filter_with_pagination = Structs::Filters::Artifact::SearchFilterWithPagination.new(**filter)
|
95
|
+
artifacts = Mihari::Models::Artifact.search(search_filter_with_pagination)
|
96
|
+
total = Mihari::Models::Artifact.count(search_filter_with_pagination.without_pagination)
|
97
|
+
ResultValue.new(artifacts: artifacts, total: total, filter: filter)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
64
101
|
namespace :artifacts do
|
102
|
+
desc "Search artifacts", {
|
103
|
+
is_array: true,
|
104
|
+
success: Entities::ArtifactsWithPagination,
|
105
|
+
summary: "Search artifacts"
|
106
|
+
}
|
107
|
+
params do
|
108
|
+
optional :page, type: Integer, default: 1
|
109
|
+
optional :limit, type: Integer, default: 10
|
110
|
+
optional :dataType, type: String
|
111
|
+
optional :ruleId, type: String
|
112
|
+
optional :tag, type: String
|
113
|
+
optional :fromAt, type: DateTime
|
114
|
+
optional :toAt, type: DateTime
|
115
|
+
end
|
116
|
+
get "/" do
|
117
|
+
value = ArtifactSearcher.call(params.to_h)
|
118
|
+
present(
|
119
|
+
{
|
120
|
+
artifacts: value.artifacts,
|
121
|
+
total: value.total,
|
122
|
+
current_page: value.filter[:page].to_i,
|
123
|
+
page_size: value.filter[:limit].to_i
|
124
|
+
},
|
125
|
+
with: Entities::ArtifactsWithPagination
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
65
129
|
desc "Get an artifact", {
|
66
130
|
success: Entities::Artifact,
|
67
131
|
failure: [{ code: 404, model: Entities::Message }],
|
File without changes
|
@@ -36,17 +36,10 @@ module Mihari
|
|
36
36
|
# @return [ResultValue]
|
37
37
|
#
|
38
38
|
def call(params)
|
39
|
-
filter = params.to_h.to_snake_keys
|
40
|
-
|
41
|
-
# normalize keys
|
42
|
-
filter["tag_name"] = filter["tag"]
|
43
|
-
# symbolize hash keys
|
44
|
-
filter = filter.to_h.symbolize_keys
|
45
|
-
|
39
|
+
filter = params.to_h.to_snake_keys.symbolize_keys
|
46
40
|
search_filter_with_pagination = Mihari::Structs::Filters::Rule::SearchFilterWithPagination.new(**filter)
|
47
41
|
rules = Mihari::Models::Rule.search(search_filter_with_pagination)
|
48
42
|
total = Mihari::Models::Rule.count(search_filter_with_pagination.without_pagination)
|
49
|
-
|
50
43
|
ResultValue.new(rules: rules, total: total, filter: filter)
|
51
44
|
end
|
52
45
|
end
|