mihari 7.4.0 → 7.5.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/clients/base.rb +3 -2
- data/lib/mihari/clients/whois.rb +118 -0
- data/lib/mihari/clients/yeti.rb +38 -0
- data/lib/mihari/config.rb +8 -0
- data/lib/mihari/data_type.rb +1 -3
- data/lib/mihari/emitters/yeti.rb +107 -0
- data/lib/mihari/enrichers/whois.rb +6 -91
- data/lib/mihari/schemas/emitter.rb +7 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/{index-qLffdzXi.css → index-80oZkhZG.css} +1 -1
- data/lib/mihari/web/public/assets/index-BNLbw8nG.js +1783 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari.rb +3 -0
- data/mihari.gemspec +6 -6
- data/requirements.txt +1 -1
- metadata +19 -16
- data/lib/mihari/web/public/assets/index-DsMIBgVm.js +0 -1787
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71b5cf7cdeb320c813848b90973908a3999c839f742b9307ae819c1fbf20829b
|
|
4
|
+
data.tar.gz: 18b06e35086d2888016d6d1fd7a61c9e62f72e31f6a384e0c1a96f4fde8ab592
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2861d810f7ade8e177fd343e706c55a4ef64f734c3f19ae324a58d29c2992b3a11ccc8ad6d6735dff9831b15ce552305e3981c72b9326d55b83f621154f9d2f9
|
|
7
|
+
data.tar.gz: edf9f41d660252298cb5909eec731a6ca39968eab5d73c3d6eca3d55f8b452337ee22898427e9165312881058f6e2ba474d6f548aac922431c1b483b7a9c9746
|
data/lib/mihari/clients/base.rb
CHANGED
|
@@ -87,11 +87,12 @@ module Mihari
|
|
|
87
87
|
#
|
|
88
88
|
# @param [String] path
|
|
89
89
|
# @param [Hash, nil] json
|
|
90
|
+
# @param [Hash, nil] headers
|
|
90
91
|
#
|
|
91
92
|
# @return [Hash]
|
|
92
93
|
#
|
|
93
|
-
def post_json(path, json: {})
|
|
94
|
-
res = http.post(url_for(path), json:)
|
|
94
|
+
def post_json(path, json: {}, headers: nil)
|
|
95
|
+
res = http.post(url_for(path), json:, headers: headers || {})
|
|
95
96
|
JSON.parse res.body.to_s
|
|
96
97
|
end
|
|
97
98
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "whois-parser"
|
|
4
|
+
|
|
5
|
+
module Mihari
|
|
6
|
+
module Clients
|
|
7
|
+
#
|
|
8
|
+
# Whois client
|
|
9
|
+
#
|
|
10
|
+
class Whois
|
|
11
|
+
# @return [Integer, nil]
|
|
12
|
+
attr_reader :timeout
|
|
13
|
+
|
|
14
|
+
# @return [::Whois::Client]
|
|
15
|
+
attr_reader :client
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# @param [Integer, nil] timeout
|
|
19
|
+
#
|
|
20
|
+
def initialize(timeout: nil)
|
|
21
|
+
@timeout = timeout
|
|
22
|
+
|
|
23
|
+
@client = lambda do
|
|
24
|
+
return ::Whois::Client.new if timeout.nil?
|
|
25
|
+
|
|
26
|
+
::Whois::Client.new(timeout:)
|
|
27
|
+
end.call
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Query IAIA Whois API
|
|
32
|
+
#
|
|
33
|
+
# @param [Mihari::Models::Artifact] artifact
|
|
34
|
+
#
|
|
35
|
+
# @param [Object] domain
|
|
36
|
+
def lookup(domain)
|
|
37
|
+
record = client.lookup(domain)
|
|
38
|
+
return if record.parser.available?
|
|
39
|
+
|
|
40
|
+
Models::WhoisRecord.new(
|
|
41
|
+
domain:,
|
|
42
|
+
created_on: get_created_on(record.parser),
|
|
43
|
+
updated_on: get_updated_on(record.parser),
|
|
44
|
+
expires_on: get_expires_on(record.parser),
|
|
45
|
+
registrar: get_registrar(record.parser),
|
|
46
|
+
contacts: get_contacts(record.parser)
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# Get created_on
|
|
54
|
+
#
|
|
55
|
+
# @param [::Whois::Parser] parser
|
|
56
|
+
#
|
|
57
|
+
# @return [Date, nil]
|
|
58
|
+
#
|
|
59
|
+
def get_created_on(parser)
|
|
60
|
+
parser.created_on
|
|
61
|
+
rescue ::Whois::AttributeNotImplemented
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Get updated_on
|
|
67
|
+
#
|
|
68
|
+
# @param [::Whois::Parser] parser
|
|
69
|
+
#
|
|
70
|
+
# @return [Date, nil]
|
|
71
|
+
#
|
|
72
|
+
def get_updated_on(parser)
|
|
73
|
+
parser.updated_on
|
|
74
|
+
rescue ::Whois::AttributeNotImplemented
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
# Get expires_on
|
|
80
|
+
#
|
|
81
|
+
# @param [::Whois::Parser] parser
|
|
82
|
+
#
|
|
83
|
+
# @return [Date, nil]
|
|
84
|
+
#
|
|
85
|
+
def get_expires_on(parser)
|
|
86
|
+
parser.expires_on
|
|
87
|
+
rescue ::Whois::AttributeNotImplemented
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
#
|
|
92
|
+
# Get registrar
|
|
93
|
+
#
|
|
94
|
+
# @param [::Whois::Parser] parser
|
|
95
|
+
#
|
|
96
|
+
# @return [Hash, nil]
|
|
97
|
+
#
|
|
98
|
+
def get_registrar(parser)
|
|
99
|
+
parser.registrar&.to_h
|
|
100
|
+
rescue ::Whois::AttributeNotImplemented
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
#
|
|
105
|
+
# Get contacts
|
|
106
|
+
#
|
|
107
|
+
# @param [::Whois::Parser] parser
|
|
108
|
+
#
|
|
109
|
+
# @return [Array<Hash>, nil]
|
|
110
|
+
#
|
|
111
|
+
def get_contacts(parser)
|
|
112
|
+
parser.contacts.map(&:to_h)
|
|
113
|
+
rescue ::Whois::AttributeNotImplemented
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Clients
|
|
5
|
+
#
|
|
6
|
+
# Yeti API client
|
|
7
|
+
#
|
|
8
|
+
class Yeti < Base
|
|
9
|
+
#
|
|
10
|
+
# @param [String] base_url
|
|
11
|
+
# @param [String, nil] api_key
|
|
12
|
+
# @param [Hash] headers
|
|
13
|
+
# @param [Integer, nil] timeout
|
|
14
|
+
#
|
|
15
|
+
def initialize(base_url, api_key:, headers: {}, timeout: nil)
|
|
16
|
+
raise(ArgumentError, "api_key is required") unless api_key
|
|
17
|
+
|
|
18
|
+
headers["x-yeti-apikey"] = api_key
|
|
19
|
+
super(base_url, headers:, timeout:)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_token
|
|
23
|
+
res = post_json("/api/v2/auth/api-token")
|
|
24
|
+
res["access_token"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# @param [Hash] json
|
|
29
|
+
#
|
|
30
|
+
# @return [Hash]
|
|
31
|
+
#
|
|
32
|
+
def create_observables(json)
|
|
33
|
+
token = get_token
|
|
34
|
+
post_json("/api/v2/observables/bulk", json:, headers: {authorization: "Bearer #{token}"})
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/mihari/config.rb
CHANGED
|
@@ -34,6 +34,8 @@ module Mihari
|
|
|
34
34
|
thehive_url: nil,
|
|
35
35
|
urlscan_api_key: nil,
|
|
36
36
|
virustotal_api_key: nil,
|
|
37
|
+
yeti_api_key: nil,
|
|
38
|
+
yeti_url: nil,
|
|
37
39
|
zoomeye_api_key: nil,
|
|
38
40
|
# sidekiq
|
|
39
41
|
sidekiq_redis_url: nil,
|
|
@@ -123,6 +125,12 @@ module Mihari
|
|
|
123
125
|
# @!attribute [r] virustotal_api_key
|
|
124
126
|
# @return [String, nil]
|
|
125
127
|
|
|
128
|
+
# @!attribute [r] yeti_url
|
|
129
|
+
# @return [String, nil]
|
|
130
|
+
|
|
131
|
+
# @!attribute [r] yeti_api_key
|
|
132
|
+
# @return [String, nil]
|
|
133
|
+
|
|
126
134
|
# @!attribute [r] zoomeye_api_key
|
|
127
135
|
# @return [String, nil]
|
|
128
136
|
|
data/lib/mihari/data_type.rb
CHANGED
|
@@ -26,9 +26,7 @@ module Mihari
|
|
|
26
26
|
|
|
27
27
|
# @return [Boolean]
|
|
28
28
|
def ip?
|
|
29
|
-
Try[IPAddr::InvalidAddressError]
|
|
30
|
-
IPAddr.new(data).to_s == data
|
|
31
|
-
end.recover { false }.value!
|
|
29
|
+
Try[IPAddr::InvalidAddressError] { IPAddr.new(data).to_s == data }.recover { false }.value!
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
# @return [Boolean]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Emitters
|
|
5
|
+
class Yeti < Base
|
|
6
|
+
# @return [String, nil]
|
|
7
|
+
attr_reader :url
|
|
8
|
+
|
|
9
|
+
# @return [String, nil]
|
|
10
|
+
attr_reader :api_key
|
|
11
|
+
|
|
12
|
+
# @return [Array<Mihari::Models::Artifact>]
|
|
13
|
+
attr_accessor :artifacts
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# @param [Mihari::Rule] rule
|
|
17
|
+
# @param [Hash, nil] options
|
|
18
|
+
# @param [Hash] params
|
|
19
|
+
#
|
|
20
|
+
def initialize(rule:, options: nil, **params)
|
|
21
|
+
super(rule:, options:)
|
|
22
|
+
|
|
23
|
+
@url = params[:url] || Mihari.config.yeti_url
|
|
24
|
+
@api_key = params[:api_key] || Mihari.config.yeti_api_key
|
|
25
|
+
|
|
26
|
+
@artifacts = []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
#
|
|
32
|
+
def configured?
|
|
33
|
+
api_key? && url?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Create a Hive alert
|
|
38
|
+
#
|
|
39
|
+
# @param [Array<Mihari::Models::Artifact>] artifacts
|
|
40
|
+
#
|
|
41
|
+
def call(artifacts)
|
|
42
|
+
return if artifacts.empty?
|
|
43
|
+
|
|
44
|
+
@artifacts = artifacts
|
|
45
|
+
|
|
46
|
+
client.create_observables({observables:})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
# @return [String]
|
|
51
|
+
#
|
|
52
|
+
def target
|
|
53
|
+
URI(url).host || "N/A"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def client
|
|
59
|
+
Clients::Yeti.new(url, api_key:, timeout:)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# Check whether a URL is set or not
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
#
|
|
67
|
+
def url?
|
|
68
|
+
!url.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def acceptable_artifacts
|
|
72
|
+
artifacts.reject { |artifact| artifact.data_type == "mail" }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# @param [Mihari::Models::Artifact] artifact
|
|
77
|
+
#
|
|
78
|
+
# @return [Hash]
|
|
79
|
+
#
|
|
80
|
+
def artifact_to_observable(artifact)
|
|
81
|
+
convert_table = {
|
|
82
|
+
domain: "hostname",
|
|
83
|
+
ip: "ipv4"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type = lambda do
|
|
87
|
+
detailed_type = DataType.detailed_type(artifact.data)
|
|
88
|
+
convert_table[detailed_type.to_sym] || detailed_type || artifact.data_type
|
|
89
|
+
end.call
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
tags:,
|
|
93
|
+
type:,
|
|
94
|
+
value: artifact.data
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def tags
|
|
99
|
+
@tags ||= rule.tags.map(&:name)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def observables
|
|
103
|
+
acceptable_artifacts.map { |artifact| artifact_to_observable(artifact) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "whois-parser"
|
|
4
|
-
|
|
5
3
|
module Mihari
|
|
6
4
|
module Enrichers
|
|
7
5
|
#
|
|
@@ -18,22 +16,15 @@ module Mihari
|
|
|
18
16
|
def call(artifact)
|
|
19
17
|
return if artifact.domain.nil?
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
record = memoized_lookup(domain)
|
|
23
|
-
return if record.parser.available?
|
|
24
|
-
|
|
25
|
-
artifact.whois_record ||= Models::WhoisRecord.new(
|
|
26
|
-
domain:,
|
|
27
|
-
created_on: get_created_on(record.parser),
|
|
28
|
-
updated_on: get_updated_on(record.parser),
|
|
29
|
-
expires_on: get_expires_on(record.parser),
|
|
30
|
-
registrar: get_registrar(record.parser),
|
|
31
|
-
contacts: get_contacts(record.parser)
|
|
32
|
-
)
|
|
19
|
+
artifact.whois_record ||= memoized_lookup(PublicSuffix.domain(artifact.domain))
|
|
33
20
|
end
|
|
34
21
|
|
|
35
22
|
private
|
|
36
23
|
|
|
24
|
+
def client
|
|
25
|
+
@client ||= Clients::Whois.new(timeout:)
|
|
26
|
+
end
|
|
27
|
+
|
|
37
28
|
#
|
|
38
29
|
# @param [Mihari::Models::Artifact] artifact
|
|
39
30
|
#
|
|
@@ -53,85 +44,9 @@ module Mihari
|
|
|
53
44
|
# @return [Mihari::Models::WhoisRecord, nil]
|
|
54
45
|
#
|
|
55
46
|
def memoized_lookup(domain)
|
|
56
|
-
|
|
47
|
+
client.lookup domain
|
|
57
48
|
end
|
|
58
49
|
memo_wise :memoized_lookup
|
|
59
|
-
|
|
60
|
-
#
|
|
61
|
-
# @return [::Whois::Client]
|
|
62
|
-
#
|
|
63
|
-
def whois
|
|
64
|
-
@whois ||= lambda do
|
|
65
|
-
return ::Whois::Client.new if timeout.nil?
|
|
66
|
-
|
|
67
|
-
::Whois::Client.new(timeout:)
|
|
68
|
-
end.call
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
# Get created_on
|
|
73
|
-
#
|
|
74
|
-
# @param [::Whois::Parser] parser
|
|
75
|
-
#
|
|
76
|
-
# @return [Date, nil]
|
|
77
|
-
#
|
|
78
|
-
def get_created_on(parser)
|
|
79
|
-
parser.created_on
|
|
80
|
-
rescue ::Whois::AttributeNotImplemented
|
|
81
|
-
nil
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
# Get updated_on
|
|
86
|
-
#
|
|
87
|
-
# @param [::Whois::Parser] parser
|
|
88
|
-
#
|
|
89
|
-
# @return [Date, nil]
|
|
90
|
-
#
|
|
91
|
-
def get_updated_on(parser)
|
|
92
|
-
parser.updated_on
|
|
93
|
-
rescue ::Whois::AttributeNotImplemented
|
|
94
|
-
nil
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
#
|
|
98
|
-
# Get expires_on
|
|
99
|
-
#
|
|
100
|
-
# @param [::Whois::Parser] parser
|
|
101
|
-
#
|
|
102
|
-
# @return [Date, nil]
|
|
103
|
-
#
|
|
104
|
-
def get_expires_on(parser)
|
|
105
|
-
parser.expires_on
|
|
106
|
-
rescue ::Whois::AttributeNotImplemented
|
|
107
|
-
nil
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
#
|
|
111
|
-
# Get registrar
|
|
112
|
-
#
|
|
113
|
-
# @param [::Whois::Parser] parser
|
|
114
|
-
#
|
|
115
|
-
# @return [Hash, nil]
|
|
116
|
-
#
|
|
117
|
-
def get_registrar(parser)
|
|
118
|
-
parser.registrar&.to_h
|
|
119
|
-
rescue ::Whois::AttributeNotImplemented
|
|
120
|
-
nil
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
# Get contacts
|
|
125
|
-
#
|
|
126
|
-
# @param [::Whois::Parser] parser
|
|
127
|
-
#
|
|
128
|
-
# @return [Array<Hash>, nil]
|
|
129
|
-
#
|
|
130
|
-
def get_contacts(parser)
|
|
131
|
-
parser.contacts.map(&:to_h)
|
|
132
|
-
rescue ::Whois::AttributeNotImplemented
|
|
133
|
-
nil
|
|
134
|
-
end
|
|
135
50
|
end
|
|
136
51
|
end
|
|
137
52
|
end
|
|
@@ -29,6 +29,13 @@ module Mihari
|
|
|
29
29
|
optional(:options).hash(EmitterOptions)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
Yeti = Dry::Schema.Params do
|
|
33
|
+
required(:emitter).value(Types::String.enum(*Mihari::Emitters::Yeti.keys))
|
|
34
|
+
optional(:url).filled(:string)
|
|
35
|
+
optional(:api_key).filled(:string)
|
|
36
|
+
optional(:options).hash(EmitterOptions)
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
Slack = Dry::Schema.Params do
|
|
33
40
|
required(:emitter).value(Types::String.enum(*Mihari::Emitters::Slack.keys))
|
|
34
41
|
optional(:webhook_url).filled(:string)
|
data/lib/mihari/version.rb
CHANGED