mihari 5.2.2 → 5.2.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/binaryedge.rb +0 -1
- data/lib/mihari/analyzers/censys.rb +7 -2
- data/lib/mihari/analyzers/circl.rb +1 -1
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +63 -72
- data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -2
- data/lib/mihari/clients/base.rb +1 -1
- data/lib/mihari/commands/database.rb +12 -11
- data/lib/mihari/commands/rule.rb +47 -45
- data/lib/mihari/commands/search.rb +66 -47
- data/lib/mihari/commands/version.rb +8 -6
- data/lib/mihari/commands/web.rb +26 -23
- data/lib/mihari/emitters/base.rb +14 -1
- data/lib/mihari/emitters/database.rb +3 -10
- data/lib/mihari/emitters/misp.rb +16 -5
- data/lib/mihari/emitters/slack.rb +13 -15
- data/lib/mihari/emitters/the_hive.rb +17 -19
- data/lib/mihari/emitters/webhook.rb +23 -23
- data/lib/mihari/enrichers/whois.rb +1 -0
- data/lib/mihari/feed/parser.rb +1 -0
- data/lib/mihari/feed/reader.rb +29 -14
- data/lib/mihari/mixins/configurable.rb +13 -4
- data/lib/mihari/structs/censys.rb +96 -82
- data/lib/mihari/structs/config.rb +23 -21
- data/lib/mihari/structs/google_public_dns.rb +27 -23
- data/lib/mihari/structs/greynoise.rb +44 -38
- data/lib/mihari/structs/onyphe.rb +34 -30
- data/lib/mihari/structs/shodan.rb +77 -69
- data/lib/mihari/structs/urlscan.rb +42 -36
- data/lib/mihari/structs/virustotal_intelligence.rb +57 -49
- data/lib/mihari/type_checker.rb +10 -8
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +3 -3
- metadata +8 -8
@@ -4,28 +4,21 @@ module Mihari
|
|
4
4
|
module Emitters
|
5
5
|
class Database < Base
|
6
6
|
def valid?
|
7
|
-
|
7
|
+
configured?
|
8
8
|
end
|
9
9
|
|
10
10
|
#
|
11
11
|
# Create an alert
|
12
12
|
#
|
13
|
-
# @param [Arra<Mihari::Artifact>] artifacts
|
14
|
-
# @param [Mihari::Structs::Rule] rule
|
15
|
-
#
|
16
13
|
# @return [Mihari::Alert]
|
17
14
|
#
|
18
|
-
def emit
|
15
|
+
def emit
|
19
16
|
return if artifacts.empty?
|
20
17
|
|
21
18
|
tags = rule.tags.filter_map { |name| Tag.find_or_create_by(name: name) }.uniq
|
22
19
|
taggings = tags.map { |tag| Tagging.new(tag_id: tag.id) }
|
23
20
|
|
24
|
-
alert = Alert.new(
|
25
|
-
artifacts: artifacts,
|
26
|
-
taggings: taggings,
|
27
|
-
rule_id: rule.id
|
28
|
-
)
|
21
|
+
alert = Alert.new(artifacts: artifacts, taggings: taggings, rule_id: rule.id)
|
29
22
|
alert.save
|
30
23
|
alert
|
31
24
|
end
|
data/lib/mihari/emitters/misp.rb
CHANGED
@@ -9,11 +9,22 @@ module Mihari
|
|
9
9
|
# @return [String, nil]
|
10
10
|
attr_reader :api_key
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# @return [Array<Mihari::Artifact>]
|
13
|
+
attr_reader :artifacts
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
# @return [Mihari::Structs::Rule]
|
16
|
+
attr_reader :rule
|
17
|
+
|
18
|
+
#
|
19
|
+
# @param [Array<Mihari::Artifact>] artifacts
|
20
|
+
# @param [Mihari::Structs::Rule] rule
|
21
|
+
# @param [Hash] **options
|
22
|
+
#
|
23
|
+
def initialize(artifacts:, rule:, **options)
|
24
|
+
super(artifacts: artifacts, rule: rule, **options)
|
25
|
+
|
26
|
+
@url = options[:url] || Mihari.config.misp_url
|
27
|
+
@api_key = options[:api_key] || Mihari.config.misp_api_key
|
17
28
|
end
|
18
29
|
|
19
30
|
# @return [Boolean]
|
@@ -40,7 +51,7 @@ module Mihari
|
|
40
51
|
#
|
41
52
|
# @return [::MISP::Event]
|
42
53
|
#
|
43
|
-
def emit
|
54
|
+
def emit
|
44
55
|
return if artifacts.empty?
|
45
56
|
|
46
57
|
client.create_event({
|
@@ -121,11 +121,16 @@ module Mihari
|
|
121
121
|
# @return [String]
|
122
122
|
attr_reader :username
|
123
123
|
|
124
|
-
|
125
|
-
|
124
|
+
#
|
125
|
+
# @param [Array<Mihari::Artifact>] artifacts
|
126
|
+
# @param [Mihari::Structs::Rule] rule
|
127
|
+
# @param [Hash] **_options
|
128
|
+
#
|
129
|
+
def initialize(artifacts:, rule:, **options)
|
130
|
+
super(artifacts: artifacts, rule: rule, **options)
|
126
131
|
|
127
|
-
@webhook_url =
|
128
|
-
@channel =
|
132
|
+
@webhook_url = options[:webhook_url] || Mihari.config.slack_webhook_url
|
133
|
+
@channel = options[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
|
129
134
|
@username = DEFAULT_USERNAME
|
130
135
|
end
|
131
136
|
|
@@ -152,13 +157,11 @@ module Mihari
|
|
152
157
|
end
|
153
158
|
|
154
159
|
#
|
155
|
-
# Build
|
156
|
-
#
|
157
|
-
# @param [Array<Mihari::Artifact>] artifacts
|
160
|
+
# Build attachments
|
158
161
|
#
|
159
162
|
# @return [Array<Mihari::Emitters::Attachment>]
|
160
163
|
#
|
161
|
-
def
|
164
|
+
def attachments
|
162
165
|
artifacts.map do |artifact|
|
163
166
|
Attachment.new(data: artifact.data, data_type: artifact.data_type).to_a
|
164
167
|
end.flatten
|
@@ -167,11 +170,9 @@ module Mihari
|
|
167
170
|
#
|
168
171
|
# Build a text
|
169
172
|
#
|
170
|
-
# @param [Mihari::Structs::Rule] rule
|
171
|
-
#
|
172
173
|
# @return [String]
|
173
174
|
#
|
174
|
-
def
|
175
|
+
def text
|
175
176
|
tags = rule.tags
|
176
177
|
tags = ["N/A"] if tags.empty?
|
177
178
|
|
@@ -182,12 +183,9 @@ module Mihari
|
|
182
183
|
].join("\n")
|
183
184
|
end
|
184
185
|
|
185
|
-
def emit
|
186
|
+
def emit
|
186
187
|
return if artifacts.empty?
|
187
188
|
|
188
|
-
attachments = to_attachments(artifacts)
|
189
|
-
text = to_text(rule)
|
190
|
-
|
191
189
|
notifier.post(text: text, attachments: attachments, mrkdwn: true)
|
192
190
|
end
|
193
191
|
|
@@ -12,12 +12,17 @@ module Mihari
|
|
12
12
|
# @return [String, nil]
|
13
13
|
attr_reader :api_version
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
#
|
16
|
+
# @param [Array<Mihari::Artifact>] artifacts
|
17
|
+
# @param [Mihari::Structs::Rule] rule
|
18
|
+
# @param [Hash] **options
|
19
|
+
#
|
20
|
+
def initialize(artifacts:, rule:, **options)
|
21
|
+
super(artifacts: artifacts, rule: rule, **options)
|
17
22
|
|
18
|
-
@url =
|
19
|
-
@api_key =
|
20
|
-
@api_version =
|
23
|
+
@url = options[:url] || Mihari.config.thehive_url
|
24
|
+
@api_key = options[:api_key] || Mihari.config.thehive_api_key
|
25
|
+
@api_version = options[:api_version] || Mihari.config.thehive_api_version
|
21
26
|
end
|
22
27
|
|
23
28
|
# @return [Boolean]
|
@@ -39,15 +44,11 @@ module Mihari
|
|
39
44
|
#
|
40
45
|
# Create a Hive alert
|
41
46
|
#
|
42
|
-
# @param [Arra<Mihari::Artifact>] artifacts
|
43
|
-
# @param [Mihari::Structs::Rule] rule
|
44
|
-
#
|
45
47
|
# @return [::MISP::Event]
|
46
48
|
#
|
47
|
-
def emit
|
49
|
+
def emit
|
48
50
|
return if artifacts.empty?
|
49
51
|
|
50
|
-
payload = payload(rule: rule, artifacts: artifacts)
|
51
52
|
client.alert(payload)
|
52
53
|
end
|
53
54
|
|
@@ -102,18 +103,15 @@ module Mihari
|
|
102
103
|
#
|
103
104
|
# Build payload for alert
|
104
105
|
#
|
105
|
-
# @
|
106
|
-
# @param [Mihari::Structs::Rule] rule
|
107
|
-
#
|
108
|
-
# @return [<Type>] <description>
|
106
|
+
# @return [Hash]
|
109
107
|
#
|
110
|
-
def payload
|
111
|
-
return v4_payload
|
108
|
+
def payload
|
109
|
+
return v4_payload if normalized_api_version.nil?
|
112
110
|
|
113
|
-
v5_payload
|
111
|
+
v5_payload
|
114
112
|
end
|
115
113
|
|
116
|
-
def v4_payload
|
114
|
+
def v4_payload
|
117
115
|
{
|
118
116
|
title: rule.title,
|
119
117
|
description: rule.description,
|
@@ -130,7 +128,7 @@ module Mihari
|
|
130
128
|
}
|
131
129
|
end
|
132
130
|
|
133
|
-
def v5_payload
|
131
|
+
def v5_payload
|
134
132
|
{
|
135
133
|
title: rule.title,
|
136
134
|
description: rule.description,
|
@@ -55,30 +55,26 @@ module Mihari
|
|
55
55
|
# @return [String, nil]
|
56
56
|
attr_reader :template
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
@url = Addressable::URI.parse(url)
|
67
|
-
@headers = headers
|
68
|
-
@method = method
|
69
|
-
@template = template
|
58
|
+
#
|
59
|
+
# @param [Array<Mihari::Artifact>] artifacts
|
60
|
+
# @param [Mihari::Structs::Rule] rule
|
61
|
+
# @param [Hash] **options
|
62
|
+
#
|
63
|
+
def initialize(artifacts:, rule:, **options)
|
64
|
+
super(artifacts: artifacts, rule: rule, **options)
|
65
|
+
|
66
|
+
@url = Addressable::URI.parse(options[:url])
|
67
|
+
@headers = options[:headers] || {}
|
68
|
+
@method = options[:method] || "POST"
|
69
|
+
@template = options[:template]
|
70
70
|
end
|
71
71
|
|
72
|
-
def emit
|
72
|
+
def emit
|
73
73
|
return if artifacts.empty?
|
74
74
|
|
75
|
-
res = nil
|
76
|
-
|
77
|
-
payload_ = payload_as_string(artifacts: artifacts, rule: rule)
|
78
|
-
payload = JSON.parse(payload_)
|
79
|
-
|
80
75
|
client = Mihari::HTTP.new(url, headers: headers)
|
81
76
|
|
77
|
+
res = nil
|
82
78
|
case method
|
83
79
|
when "GET"
|
84
80
|
res = client.get
|
@@ -100,13 +96,10 @@ module Mihari
|
|
100
96
|
#
|
101
97
|
# Convert payload into string
|
102
98
|
#
|
103
|
-
# @param [Array<Mihari::Artifact>] artifacts
|
104
|
-
# @param [Mihari::Structs::Rule] rule
|
105
|
-
#
|
106
99
|
# @return [String]
|
107
100
|
#
|
108
|
-
def payload_as_string
|
109
|
-
|
101
|
+
def payload_as_string
|
102
|
+
[].tap do |out|
|
110
103
|
options = {}
|
111
104
|
options[:template] = File.read(template) unless template.nil?
|
112
105
|
|
@@ -118,6 +111,13 @@ module Mihari
|
|
118
111
|
out << payload_template.result
|
119
112
|
end.first
|
120
113
|
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# @return [Hash]
|
117
|
+
#
|
118
|
+
def payload
|
119
|
+
JSON.parse payload_as_string
|
120
|
+
end
|
121
121
|
end
|
122
122
|
end
|
123
123
|
end
|
data/lib/mihari/feed/parser.rb
CHANGED
data/lib/mihari/feed/reader.rb
CHANGED
@@ -6,7 +6,23 @@ require "insensitive_hash"
|
|
6
6
|
module Mihari
|
7
7
|
module Feed
|
8
8
|
class Reader
|
9
|
-
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :url
|
11
|
+
|
12
|
+
# @return [Hash]
|
13
|
+
attr_reader :headers
|
14
|
+
|
15
|
+
# @return [Hash, nil]
|
16
|
+
attr_reader :params
|
17
|
+
|
18
|
+
# @return [Hash, nil]
|
19
|
+
attr_reader :json
|
20
|
+
|
21
|
+
# @return [Hash, nil]
|
22
|
+
attr_reader :data
|
23
|
+
|
24
|
+
# @return [String]
|
25
|
+
attr_reader :method
|
10
26
|
|
11
27
|
def initialize(url, headers: {}, method: "GET", params: nil, json: nil, data: nil)
|
12
28
|
@url = Addressable::URI.parse(url)
|
@@ -20,6 +36,9 @@ module Mihari
|
|
20
36
|
headers["content-type"] = "application/json" unless json.nil?
|
21
37
|
end
|
22
38
|
|
39
|
+
#
|
40
|
+
# @return [Array<Hash>]
|
41
|
+
#
|
23
42
|
def read
|
24
43
|
return read_file(url.path) if url.scheme == "file"
|
25
44
|
|
@@ -33,11 +52,9 @@ module Mihari
|
|
33
52
|
|
34
53
|
body = res.body
|
35
54
|
content_type = res["Content-Type"].to_s
|
36
|
-
if content_type.include?("application/json")
|
37
|
-
|
38
|
-
|
39
|
-
convert_as_csv(body)
|
40
|
-
end
|
55
|
+
return convert_as_json(body) if content_type.include?("application/json")
|
56
|
+
|
57
|
+
convert_as_csv(body)
|
41
58
|
end
|
42
59
|
|
43
60
|
#
|
@@ -48,10 +65,10 @@ module Mihari
|
|
48
65
|
# @return [Array<Hash>]
|
49
66
|
#
|
50
67
|
def convert_as_json(text)
|
51
|
-
|
52
|
-
return
|
68
|
+
parsed = JSON.parse(text, symbolize_names: true)
|
69
|
+
return parsed if parsed.is_a?(Array)
|
53
70
|
|
54
|
-
[
|
71
|
+
[parsed]
|
55
72
|
end
|
56
73
|
|
57
74
|
#
|
@@ -77,11 +94,9 @@ module Mihari
|
|
77
94
|
def read_file(path)
|
78
95
|
text = File.read(path)
|
79
96
|
|
80
|
-
if path.end_with?(".json")
|
81
|
-
|
82
|
-
|
83
|
-
convert_as_csv text
|
84
|
-
end
|
97
|
+
return convert_as_json(text) if path.end_with?(".json")
|
98
|
+
|
99
|
+
convert_as_csv text
|
85
100
|
end
|
86
101
|
end
|
87
102
|
end
|
@@ -4,14 +4,23 @@ module Mihari
|
|
4
4
|
module Mixins
|
5
5
|
module Configurable
|
6
6
|
#
|
7
|
-
# Check whether
|
7
|
+
# Check whether there are configuration key-values or not
|
8
8
|
#
|
9
9
|
# @return [Boolean]
|
10
10
|
#
|
11
|
-
def
|
11
|
+
def configuration_keys?
|
12
12
|
return true if configuration_keys.empty?
|
13
13
|
|
14
|
-
configuration_keys.all? { |key| Mihari.config.send(key) }
|
14
|
+
configuration_keys.all? { |key| Mihari.config.send(key) }
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Check whether it is configured or not
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
#
|
22
|
+
def configured?
|
23
|
+
configuration_keys? || api_key?
|
15
24
|
end
|
16
25
|
|
17
26
|
#
|
@@ -32,7 +41,7 @@ module Mihari
|
|
32
41
|
#
|
33
42
|
# Configuration keys
|
34
43
|
#
|
35
|
-
# @return [Array<String>] A list of
|
44
|
+
# @return [Array<String>] A list of configuration keys
|
36
45
|
#
|
37
46
|
def configuration_keys
|
38
47
|
[]
|
@@ -22,16 +22,18 @@ module Mihari
|
|
22
22
|
Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
class << self
|
26
|
+
#
|
27
|
+
# @param [Hash] d
|
28
|
+
#
|
29
|
+
# @return [AutonomousSystem]
|
30
|
+
#
|
31
|
+
def from_dynamic!(d)
|
32
|
+
d = Types::Hash[d]
|
33
|
+
new(
|
34
|
+
asn: d.fetch("asn")
|
35
|
+
)
|
36
|
+
end
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -67,17 +69,19 @@ module Mihari
|
|
67
69
|
)
|
68
70
|
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
72
|
+
class << self
|
73
|
+
#
|
74
|
+
# @param [Hash] d
|
75
|
+
#
|
76
|
+
# @return [Location]
|
77
|
+
#
|
78
|
+
def from_dynamic!(d)
|
79
|
+
d = Types::Hash[d]
|
80
|
+
new(
|
81
|
+
country: d["country"],
|
82
|
+
country_code: d["country_code"]
|
83
|
+
)
|
84
|
+
end
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
@@ -98,16 +102,18 @@ module Mihari
|
|
98
102
|
Port.new(port: port)
|
99
103
|
end
|
100
104
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
105
|
+
class << self
|
106
|
+
#
|
107
|
+
# @param [Hash] d
|
108
|
+
#
|
109
|
+
# @return [Service]
|
110
|
+
#
|
111
|
+
def from_dynamic!(d)
|
112
|
+
d = Types::Hash[d]
|
113
|
+
new(
|
114
|
+
port: d.fetch("port")
|
115
|
+
)
|
116
|
+
end
|
111
117
|
end
|
112
118
|
end
|
113
119
|
|
@@ -173,20 +179,22 @@ module Mihari
|
|
173
179
|
)
|
174
180
|
end
|
175
181
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
182
|
+
class << self
|
183
|
+
#
|
184
|
+
# @param [Hash] d
|
185
|
+
#
|
186
|
+
# @return [Hit]
|
187
|
+
#
|
188
|
+
def from_dynamic!(d)
|
189
|
+
d = Types::Hash[d]
|
190
|
+
new(
|
191
|
+
ip: d.fetch("ip"),
|
192
|
+
location: Location.from_dynamic!(d.fetch("location")),
|
193
|
+
autonomous_system: AutonomousSystem.from_dynamic!(d.fetch("autonomous_system")),
|
194
|
+
metadata: d,
|
195
|
+
services: d.fetch("services", []).map { |x| Service.from_dynamic!(x) }
|
196
|
+
)
|
197
|
+
end
|
190
198
|
end
|
191
199
|
end
|
192
200
|
|
@@ -208,17 +216,19 @@ module Mihari
|
|
208
216
|
attributes[:prev]
|
209
217
|
end
|
210
218
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
219
|
+
class << self
|
220
|
+
#
|
221
|
+
# @param [Hash] d
|
222
|
+
#
|
223
|
+
# @return [Links]
|
224
|
+
#
|
225
|
+
def from_dynamic!(d)
|
226
|
+
d = Types::Hash[d]
|
227
|
+
new(
|
228
|
+
next: d["next"],
|
229
|
+
prev: d["prev"]
|
230
|
+
)
|
231
|
+
end
|
222
232
|
end
|
223
233
|
end
|
224
234
|
|
@@ -260,22 +270,24 @@ module Mihari
|
|
260
270
|
# @return [Array<Mihari::Artifact>]
|
261
271
|
#
|
262
272
|
def to_artifacts
|
263
|
-
hits.map
|
273
|
+
hits.map(&:to_artifact)
|
264
274
|
end
|
265
275
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
276
|
+
class << self
|
277
|
+
#
|
278
|
+
# @param [Hash] d
|
279
|
+
#
|
280
|
+
# @return [Result]
|
281
|
+
#
|
282
|
+
def from_dynamic!(d)
|
283
|
+
d = Types::Hash[d]
|
284
|
+
new(
|
285
|
+
query: d.fetch("query"),
|
286
|
+
total: d.fetch("total"),
|
287
|
+
hits: d.fetch("hits", []).map { |x| Hit.from_dynamic!(x) },
|
288
|
+
links: Links.from_dynamic!(d.fetch("links"))
|
289
|
+
)
|
290
|
+
end
|
279
291
|
end
|
280
292
|
end
|
281
293
|
|
@@ -305,18 +317,20 @@ module Mihari
|
|
305
317
|
attributes[:result]
|
306
318
|
end
|
307
319
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
+
class << self
|
321
|
+
#
|
322
|
+
# @param [Hash] d
|
323
|
+
#
|
324
|
+
# @return [Response]
|
325
|
+
#
|
326
|
+
def from_dynamic!(d)
|
327
|
+
d = Types::Hash[d]
|
328
|
+
new(
|
329
|
+
code: d.fetch("code"),
|
330
|
+
status: d.fetch("status"),
|
331
|
+
result: Result.from_dynamic!(d.fetch("result"))
|
332
|
+
)
|
333
|
+
end
|
320
334
|
end
|
321
335
|
end
|
322
336
|
end
|