mihari 5.2.1 → 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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/lib/mihari/analyzers/base.rb +20 -115
  4. data/lib/mihari/analyzers/binaryedge.rb +0 -1
  5. data/lib/mihari/analyzers/censys.rb +26 -3
  6. data/lib/mihari/analyzers/circl.rb +1 -1
  7. data/lib/mihari/analyzers/onyphe.rb +1 -1
  8. data/lib/mihari/analyzers/passivetotal.rb +1 -1
  9. data/lib/mihari/analyzers/rule.rb +122 -75
  10. data/lib/mihari/analyzers/shodan.rb +1 -1
  11. data/lib/mihari/analyzers/urlscan.rb +6 -9
  12. data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -6
  13. data/lib/mihari/cli/main.rb +2 -2
  14. data/lib/mihari/clients/base.rb +1 -1
  15. data/lib/mihari/commands/database.rb +12 -11
  16. data/lib/mihari/commands/rule.rb +47 -45
  17. data/lib/mihari/commands/search.rb +88 -0
  18. data/lib/mihari/commands/version.rb +8 -6
  19. data/lib/mihari/commands/web.rb +26 -23
  20. data/lib/mihari/emitters/base.rb +14 -1
  21. data/lib/mihari/emitters/database.rb +3 -10
  22. data/lib/mihari/emitters/misp.rb +16 -5
  23. data/lib/mihari/emitters/slack.rb +13 -15
  24. data/lib/mihari/emitters/the_hive.rb +17 -19
  25. data/lib/mihari/emitters/webhook.rb +23 -23
  26. data/lib/mihari/enrichers/whois.rb +1 -0
  27. data/lib/mihari/feed/parser.rb +1 -0
  28. data/lib/mihari/feed/reader.rb +29 -14
  29. data/lib/mihari/mixins/configurable.rb +13 -4
  30. data/lib/mihari/mixins/error_notification.rb +0 -2
  31. data/lib/mihari/models/artifact.rb +1 -1
  32. data/lib/mihari/schemas/rule.rb +2 -17
  33. data/lib/mihari/structs/censys.rb +226 -56
  34. data/lib/mihari/structs/config.rb +48 -18
  35. data/lib/mihari/structs/google_public_dns.rb +56 -14
  36. data/lib/mihari/structs/greynoise.rb +122 -29
  37. data/lib/mihari/structs/ipinfo.rb +40 -0
  38. data/lib/mihari/structs/onyphe.rb +112 -26
  39. data/lib/mihari/structs/rule.rb +4 -2
  40. data/lib/mihari/structs/shodan.rb +189 -47
  41. data/lib/mihari/structs/urlscan.rb +123 -20
  42. data/lib/mihari/structs/virustotal_intelligence.rb +129 -26
  43. data/lib/mihari/type_checker.rb +10 -8
  44. data/lib/mihari/version.rb +1 -1
  45. data/lib/mihari.rb +1 -0
  46. data/mihari.gemspec +11 -10
  47. metadata +35 -36
  48. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  49. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
  50. data/.github/workflows/test.yml +0 -90
  51. data/config/pre_commit.yml +0 -3
  52. data/docker/Dockerfile +0 -14
  53. data/examples/ipinfo_hosted_domains.rb +0 -45
  54. data/images/Tines-Full_Logo-Tines_Black.png +0 -0
  55. data/images/alert.png +0 -0
  56. data/images/logo.png +0 -0
  57. data/images/misp.png +0 -0
  58. data/images/overview.jpg +0 -0
  59. data/images/slack.png +0 -0
  60. data/images/tines.png +0 -0
  61. data/images/web_alerts.png +0 -0
  62. data/images/web_config.png +0 -0
  63. data/lib/mihari/commands/searcher.rb +0 -61
@@ -7,6 +7,20 @@ module Mihari
7
7
  attribute :country_code, Types::String.optional
8
8
  attribute :country_name, Types::String.optional
9
9
 
10
+ #
11
+ # @return [String, nil]
12
+ #
13
+ def country_code
14
+ attributes[:country_code]
15
+ end
16
+
17
+ #
18
+ # @return [String, nil]
19
+ #
20
+ def country_name
21
+ attributes[:country_name]
22
+ end
23
+
10
24
  #
11
25
  # @return [Mihari::Geolocation, nil]
12
26
  #
@@ -19,12 +33,19 @@ module Mihari
19
33
  )
20
34
  end
21
35
 
22
- def self.from_dynamic!(d)
23
- d = Types::Hash[d]
24
- new(
25
- country_code: d["country_code"],
26
- country_name: d["country_name"]
27
- )
36
+ class << self
37
+ #
38
+ # @param [Hash] d
39
+ #
40
+ # @return [Location]
41
+ #
42
+ def from_dynamic!(d)
43
+ d = Types::Hash[d]
44
+ new(
45
+ country_code: d["country_code"],
46
+ country_name: d["country_name"]
47
+ )
48
+ end
28
49
  end
29
50
  end
30
51
 
@@ -39,6 +60,48 @@ module Mihari
39
60
  attribute :port, Types::Integer
40
61
  attribute :metadata, Types::Hash
41
62
 
63
+ #
64
+ # @return [String, nil]
65
+ #
66
+ def asn
67
+ attributes[:asn]
68
+ end
69
+
70
+ #
71
+ # @return [Array<String>]
72
+ #
73
+ def hostnames
74
+ attributes[:hostnames]
75
+ end
76
+
77
+ #
78
+ # @return [Location]
79
+ #
80
+ def location
81
+ attributes[:location]
82
+ end
83
+
84
+ #
85
+ # @return [String]
86
+ #
87
+ def ip_str
88
+ attributes[:ip_str]
89
+ end
90
+
91
+ #
92
+ # @return [Integer]
93
+ #
94
+ def port
95
+ attributes[:port]
96
+ end
97
+
98
+ #
99
+ # @return [Hash]
100
+ #
101
+ def metadata
102
+ attributes[:metadata]
103
+ end
104
+
42
105
  #
43
106
  # @return [Mihari::AutonomousSystem, nil]
44
107
  #
@@ -48,25 +111,32 @@ module Mihari
48
111
  Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
49
112
  end
50
113
 
51
- def self.from_dynamic!(d)
52
- d = Types::Hash[d]
53
-
54
- # hostnames should be an array of string but sometimes Shodan returns a string
55
- # e.g. "hostnames": "set(['149.28.146.131.vultr.com', 'rebs.ga'])",
56
- # https://github.com/ninoseki/mihari/issues/424
57
- # so use an empty array if hostnames is a string
58
- hostnames = d.fetch("hostnames")
59
- hostnames = [] if hostnames.is_a?(String)
60
-
61
- new(
62
- asn: d["asn"],
63
- hostnames: hostnames,
64
- location: Location.from_dynamic!(d.fetch("location")),
65
- domains: d.fetch("domains"),
66
- ip_str: d.fetch("ip_str"),
67
- port: d.fetch("port"),
68
- metadata: d
69
- )
114
+ class << self
115
+ #
116
+ # @param [Hash] d
117
+ #
118
+ # @return [Match]
119
+ #
120
+ def from_dynamic!(d)
121
+ d = Types::Hash[d]
122
+
123
+ # hostnames should be an array of string but sometimes Shodan returns a string
124
+ # e.g. "hostnames": "set(['149.28.146.131.vultr.com', 'rebs.ga'])",
125
+ # https://github.com/ninoseki/mihari/issues/424
126
+ # so use an empty array if hostnames is a string
127
+ hostnames = d.fetch("hostnames")
128
+ hostnames = [] if hostnames.is_a?(String)
129
+
130
+ new(
131
+ asn: d["asn"],
132
+ hostnames: hostnames,
133
+ location: Location.from_dynamic!(d.fetch("location")),
134
+ domains: d.fetch("domains"),
135
+ ip_str: d.fetch("ip_str"),
136
+ port: d.fetch("port"),
137
+ metadata: d
138
+ )
139
+ end
70
140
  end
71
141
  end
72
142
 
@@ -74,6 +144,20 @@ module Mihari
74
144
  attribute :matches, Types.Array(Match)
75
145
  attribute :total, Types::Int
76
146
 
147
+ #
148
+ # @return [Array<Match>]
149
+ #
150
+ def matches
151
+ attributes[:matches]
152
+ end
153
+
154
+ #
155
+ # @return [Integer]
156
+ #
157
+ def total
158
+ attributes[:total]
159
+ end
160
+
77
161
  #
78
162
  # Collect metadata from matches
79
163
  #
@@ -107,12 +191,10 @@ module Mihari
107
191
  matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
108
192
  end
109
193
 
110
- #
111
- # @param [Source] source
112
194
  #
113
195
  # @return [Array<Mihari::Artifact>]
114
196
  #
115
- def to_artifacts(source = "Shodan")
197
+ def to_artifacts
116
198
  matches.map do |match|
117
199
  metadata = collect_metadata_by_ip(match.ip_str)
118
200
  ports = collect_ports_by_ip(match.ip_str).map do |port|
@@ -124,7 +206,6 @@ module Mihari
124
206
 
125
207
  Mihari::Artifact.new(
126
208
  data: match.ip_str,
127
- source: source,
128
209
  metadata: metadata,
129
210
  autonomous_system: match.to_asn,
130
211
  geolocation: match.location.to_geolocation,
@@ -134,12 +215,19 @@ module Mihari
134
215
  end
135
216
  end
136
217
 
137
- def self.from_dynamic!(d)
138
- d = Types::Hash[d]
139
- new(
140
- matches: d.fetch("matches", []).map { |x| Match.from_dynamic!(x) },
141
- total: d.fetch("total")
142
- )
218
+ class << self
219
+ #
220
+ # @param [Hash] d
221
+ #
222
+ # @return [Result]
223
+ #
224
+ def from_dynamic!(d)
225
+ d = Types::Hash[d]
226
+ new(
227
+ matches: d.fetch("matches", []).map { |x| Match.from_dynamic!(x) },
228
+ total: d.fetch("total")
229
+ )
230
+ end
143
231
  end
144
232
  end
145
233
 
@@ -151,20 +239,74 @@ module Mihari
151
239
  attribute :tags, Types.Array(Types::String)
152
240
  attribute :vulns, Types.Array(Types::String)
153
241
 
154
- def self.from_dynamic!(d)
155
- d = Types::Hash[d]
156
- new(
157
- ip: d.fetch("ip"),
158
- ports: d.fetch("ports"),
159
- cpes: d.fetch("cpes"),
160
- hostnames: d.fetch("hostnames"),
161
- tags: d.fetch("tags"),
162
- vulns: d.fetch("vulns")
163
- )
242
+ #
243
+ # @return [String]
244
+ #
245
+ def ip
246
+ attributes[:ip]
247
+ end
248
+
249
+ #
250
+ # @return [Array<Integer>]
251
+ #
252
+ def ports
253
+ attributes[:ports]
254
+ end
255
+
256
+ #
257
+ # @return [Array<String>]
258
+ #
259
+ def cpes
260
+ attributes[:cpes]
164
261
  end
165
262
 
166
- def self.from_json!(json)
167
- from_dynamic!(JSON.parse(json))
263
+ #
264
+ # @return [Array<String>]
265
+ #
266
+ def hostnames
267
+ attributes[:hostnames]
268
+ end
269
+
270
+ #
271
+ # @return [Array<String>]
272
+ #
273
+ def tags
274
+ attributes[:tags]
275
+ end
276
+
277
+ #
278
+ # @return [Array<String>]
279
+ #
280
+ def vulns
281
+ attributes[:vulns]
282
+ end
283
+
284
+ class << self
285
+ #
286
+ # @param [Hash] d
287
+ #
288
+ # @return [InternetDBResponse]
289
+ #
290
+ def from_dynamic!(d)
291
+ d = Types::Hash[d]
292
+ new(
293
+ ip: d.fetch("ip"),
294
+ ports: d.fetch("ports"),
295
+ cpes: d.fetch("cpes"),
296
+ hostnames: d.fetch("hostnames"),
297
+ tags: d.fetch("tags"),
298
+ vulns: d.fetch("vulns")
299
+ )
300
+ end
301
+
302
+ #
303
+ # @param [String] json
304
+ #
305
+ # @return [InternetDBResponse]
306
+ #
307
+ def from_json!(json)
308
+ from_dynamic!(JSON.parse(json))
309
+ end
168
310
  end
169
311
  end
170
312
  end
@@ -8,13 +8,41 @@ module Mihari
8
8
  attribute :ip, Types::String.optional
9
9
  attribute :url, Types::String
10
10
 
11
- def self.from_dynamic!(d)
12
- d = Types::Hash[d]
13
- new(
14
- domain: d["domain"],
15
- ip: d["ip"],
16
- url: d.fetch("url")
17
- )
11
+ #
12
+ # @return [String, nil]
13
+ #
14
+ def domain
15
+ attributes[:domain]
16
+ end
17
+
18
+ #
19
+ # @return [String, nil]
20
+ #
21
+ def ip
22
+ attributes[:ip]
23
+ end
24
+
25
+ #
26
+ # @return [String]
27
+ #
28
+ def url
29
+ attributes[:url]
30
+ end
31
+
32
+ class << self
33
+ #
34
+ # @param [Hash] d
35
+ #
36
+ # @return [Page]
37
+ #
38
+ def from_dynamic!(d)
39
+ d = Types::Hash[d]
40
+ new(
41
+ domain: d["domain"],
42
+ ip: d["ip"],
43
+ url: d.fetch("url")
44
+ )
45
+ end
18
46
  end
19
47
  end
20
48
 
@@ -22,14 +50,61 @@ module Mihari
22
50
  attribute :page, Page
23
51
  attribute :id, Types::String
24
52
  attribute :sort, Types.Array(Types::String | Types::Integer)
53
+ attribute :metadata, Types::Hash
54
+
55
+ #
56
+ # @return [Page]
57
+ #
58
+ def page
59
+ attributes[:page]
60
+ end
61
+
62
+ #
63
+ # @return [String]
64
+ #
65
+ def id
66
+ attributes[:id]
67
+ end
68
+
69
+ #
70
+ # @return [Array<String, Integer>]
71
+ #
72
+ def sort
73
+ attributes[:sort]
74
+ end
75
+
76
+ #
77
+ # @return [Array<String, Integer>]
78
+ #
79
+ def metadata
80
+ attributes[:metadata]
81
+ end
25
82
 
26
- def self.from_dynamic!(d)
27
- d = Types::Hash[d]
28
- new(
29
- page: Page.from_dynamic!(d.fetch("page")),
30
- id: d.fetch("_id"),
31
- sort: d.fetch("sort")
32
- )
83
+ #
84
+ # @return [Array<Mihari::Artifact>]
85
+ #
86
+ def to_artifacts
87
+ values = [page.url, page.domain, page.ip].compact
88
+ values.map do |value|
89
+ Mihari::Artifact.new(data: value, metadata: metadata)
90
+ end
91
+ end
92
+
93
+ class << self
94
+ #
95
+ # @param [Hash] d
96
+ #
97
+ # @return [Result]
98
+ #
99
+ def from_dynamic!(d)
100
+ d = Types::Hash[d]
101
+ new(
102
+ page: Page.from_dynamic!(d.fetch("page")),
103
+ id: d.fetch("_id"),
104
+ sort: d.fetch("sort"),
105
+ metadata: d
106
+ )
107
+ end
33
108
  end
34
109
  end
35
110
 
@@ -37,12 +112,40 @@ module Mihari
37
112
  attribute :results, Types.Array(Result)
38
113
  attribute :has_more, Types::Bool
39
114
 
40
- def self.from_dynamic!(d)
41
- d = Types::Hash[d]
42
- new(
43
- results: d.fetch("results").map { |x| Result.from_dynamic!(x) },
44
- has_more: d.fetch("has_more")
45
- )
115
+ #
116
+ # @return [Array<Result>]
117
+ #
118
+ def results
119
+ attributes[:results]
120
+ end
121
+
122
+ #
123
+ # @return [Boolean]
124
+ #
125
+ def has_more
126
+ attributes[:has_more]
127
+ end
128
+
129
+ #
130
+ # @return [Array<Mihari::Artifact>]
131
+ #
132
+ def to_artifacts
133
+ results.map(&:to_artifacts).flatten
134
+ end
135
+
136
+ class << self
137
+ #
138
+ # @param [Hash] d
139
+ #
140
+ # @return [Response]
141
+ #
142
+ def from_dynamic!(d)
143
+ d = Types::Hash[d]
144
+ new(
145
+ results: d.fetch("results").map { |x| Result.from_dynamic!(x) },
146
+ has_more: d.fetch("has_more")
147
+ )
148
+ end
46
149
  end
47
150
  end
48
151
  end
@@ -6,11 +6,25 @@ module Mihari
6
6
  class ContextAttributes < Dry::Struct
7
7
  attribute :url, Types::String.optional
8
8
 
9
- def self.from_dynamic!(d)
10
- d = Types::Hash[d]
11
- new(
12
- url: d["url"]
13
- )
9
+ #
10
+ # @return [String, nil]
11
+ #
12
+ def url
13
+ attributes[:url]
14
+ end
15
+
16
+ class << self
17
+ #
18
+ # @param [Hash] d
19
+ #
20
+ # @return [ContextAttributes]
21
+ #
22
+ def from_dynamic!(d)
23
+ d = Types::Hash[d]
24
+ new(
25
+ url: d["url"]
26
+ )
27
+ end
14
28
  end
15
29
  end
16
30
 
@@ -20,6 +34,37 @@ module Mihari
20
34
  attribute :context_attributes, ContextAttributes.optional
21
35
  attribute :metadata, Types::Hash
22
36
 
37
+ #
38
+ # @return [String]
39
+ #
40
+ def type
41
+ attributes[:type]
42
+ end
43
+
44
+ #
45
+ # @return [String]
46
+ #
47
+ def id
48
+ attributes[:id]
49
+ end
50
+
51
+ #
52
+ # @return [ContextAttributes, nil]
53
+ #
54
+ def context_attributes
55
+ attributes[:context_attributes]
56
+ end
57
+
58
+ #
59
+ # @return [Hash, nil]
60
+ #
61
+ def metadata
62
+ attributes[:metadata]
63
+ end
64
+
65
+ #
66
+ # @return [String, nil]
67
+ #
23
68
  def value
24
69
  case type
25
70
  when "file"
@@ -33,29 +78,59 @@ module Mihari
33
78
  end
34
79
  end
35
80
 
36
- def self.from_dynamic!(d)
37
- d = Types::Hash[d]
81
+ #
82
+ # @return [Mihari::Artifact]
83
+ #
84
+ def to_artifact
85
+ Artifact.new(data: value, metadata: metadata)
86
+ end
87
+
88
+ class << self
89
+ #
90
+ # @param [Hash] d
91
+ #
92
+ # @return [Datum]
93
+ #
94
+ def from_dynamic!(d)
95
+ d = Types::Hash[d]
38
96
 
39
- context_attributes = nil
40
- context_attributes = ContextAttributes.from_dynamic!(d.fetch("context_attributes")) if d.key?("context_attributes")
97
+ context_attributes = nil
98
+ if d.key?("context_attributes")
99
+ context_attributes = ContextAttributes.from_dynamic!(d.fetch("context_attributes"))
100
+ end
41
101
 
42
- new(
43
- type: d.fetch("type"),
44
- id: d.fetch("id"),
45
- context_attributes: context_attributes,
46
- metadata: d
47
- )
102
+ new(
103
+ type: d.fetch("type"),
104
+ id: d.fetch("id"),
105
+ context_attributes: context_attributes,
106
+ metadata: d
107
+ )
108
+ end
48
109
  end
49
110
  end
50
111
 
51
112
  class Meta < Dry::Struct
52
113
  attribute :cursor, Types::String.optional
53
114
 
54
- def self.from_dynamic!(d)
55
- d = Types::Hash[d]
56
- new(
57
- cursor: d["cursor"]
58
- )
115
+ #
116
+ # @return [String, nil]
117
+ #
118
+ def cursor
119
+ attributes[:cursor]
120
+ end
121
+
122
+ class << self
123
+ #
124
+ # @param [Hash] d
125
+ #
126
+ # @return [Meta]
127
+ #
128
+ def from_dynamic!(d)
129
+ d = Types::Hash[d]
130
+ new(
131
+ cursor: d["cursor"]
132
+ )
133
+ end
59
134
  end
60
135
  end
61
136
 
@@ -63,12 +138,40 @@ module Mihari
63
138
  attribute :meta, Meta
64
139
  attribute :data, Types.Array(Datum)
65
140
 
66
- def self.from_dynamic!(d)
67
- d = Types::Hash[d]
68
- new(
69
- meta: Meta.from_dynamic!(d.fetch("meta")),
70
- data: d.fetch("data").map { |x| Datum.from_dynamic!(x) }
71
- )
141
+ #
142
+ # @return [Meta]
143
+ #
144
+ def meta
145
+ attributes[:meta]
146
+ end
147
+
148
+ #
149
+ # @return [Array<Datum>]
150
+ #
151
+ def data
152
+ attributes[:data]
153
+ end
154
+
155
+ #
156
+ # @return [Array<Mihari::Artifact>]
157
+ #
158
+ def to_artifacts
159
+ data.map(&:to_artifact)
160
+ end
161
+
162
+ class << self
163
+ #
164
+ # @param [Hash] d
165
+ #
166
+ # @return [Response]
167
+ #
168
+ def from_dynamic!(d)
169
+ d = Types::Hash[d]
170
+ new(
171
+ meta: Meta.from_dynamic!(d.fetch("meta")),
172
+ data: d.fetch("data").map { |x| Datum.from_dynamic!(x) }
173
+ )
174
+ end
72
175
  end
73
176
  end
74
177
  end
@@ -67,14 +67,16 @@ module Mihari
67
67
  type
68
68
  end
69
69
 
70
- # @return [String, nil]
71
- def self.type(data)
72
- new(data).type
73
- end
74
-
75
- # @return [String, nil]
76
- def self.detailed_type(data)
77
- new(data).detailed_type
70
+ class << self
71
+ # @return [String, nil]
72
+ def type(data)
73
+ new(data).type
74
+ end
75
+
76
+ # @return [String, nil]
77
+ def detailed_type(data)
78
+ new(data).detailed_type
79
+ end
78
80
  end
79
81
 
80
82
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "5.2.1"
4
+ VERSION = "5.2.3"
5
5
  end
data/lib/mihari.rb CHANGED
@@ -254,6 +254,7 @@ require "mihari/analyzers/urlscan"
254
254
  require "mihari/analyzers/virustotal_intelligence"
255
255
  require "mihari/analyzers/virustotal"
256
256
  require "mihari/analyzers/zoomeye"
257
+
257
258
  require "mihari/analyzers/rule"
258
259
 
259
260
  # Entities