mihari 5.2.1 → 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
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