mihari 5.2.0 → 5.2.2

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 (51) 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/censys.rb +20 -2
  5. data/lib/mihari/analyzers/onyphe.rb +1 -1
  6. data/lib/mihari/analyzers/rule.rb +116 -60
  7. data/lib/mihari/analyzers/shodan.rb +1 -1
  8. data/lib/mihari/analyzers/urlscan.rb +6 -9
  9. data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -5
  10. data/lib/mihari/cli/main.rb +2 -2
  11. data/lib/mihari/commands/search.rb +69 -0
  12. data/lib/mihari/commands/web.rb +9 -2
  13. data/lib/mihari/mixins/error_notification.rb +0 -2
  14. data/lib/mihari/models/artifact.rb +1 -1
  15. data/lib/mihari/schemas/rule.rb +2 -17
  16. data/lib/mihari/structs/censys.rb +167 -11
  17. data/lib/mihari/structs/config.rb +28 -0
  18. data/lib/mihari/structs/google_public_dns.rb +39 -1
  19. data/lib/mihari/structs/greynoise.rb +93 -6
  20. data/lib/mihari/structs/ipinfo.rb +40 -0
  21. data/lib/mihari/structs/onyphe.rb +88 -6
  22. data/lib/mihari/structs/rule.rb +4 -2
  23. data/lib/mihari/structs/shodan.rb +138 -4
  24. data/lib/mihari/structs/urlscan.rb +98 -1
  25. data/lib/mihari/structs/virustotal_intelligence.rb +96 -1
  26. data/lib/mihari/version.rb +1 -1
  27. data/lib/mihari/web/app.rb +2 -2
  28. data/lib/mihari/web/public/assets/index-cbe1734c.js +50 -0
  29. data/lib/mihari/web/public/assets/index-eed1bcd8.css +5 -0
  30. data/lib/mihari/web/public/index.html +2 -2
  31. data/lib/mihari.rb +1 -0
  32. data/mihari.gemspec +13 -12
  33. metadata +41 -42
  34. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  35. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
  36. data/.github/workflows/test.yml +0 -90
  37. data/config/pre_commit.yml +0 -3
  38. data/docker/Dockerfile +0 -14
  39. data/examples/ipinfo_hosted_domains.rb +0 -45
  40. data/images/Tines-Full_Logo-Tines_Black.png +0 -0
  41. data/images/alert.png +0 -0
  42. data/images/logo.png +0 -0
  43. data/images/misp.png +0 -0
  44. data/images/overview.jpg +0 -0
  45. data/images/slack.png +0 -0
  46. data/images/tines.png +0 -0
  47. data/images/web_alerts.png +0 -0
  48. data/images/web_config.png +0 -0
  49. data/lib/mihari/commands/searcher.rb +0 -61
  50. data/lib/mihari/web/public/assets/index-9948ee35.js +0 -50
  51. data/lib/mihari/web/public/assets/index-d88cc3f1.css +0 -5
@@ -10,6 +10,8 @@ require "yaml"
10
10
  module Mihari
11
11
  module Structs
12
12
  class Rule
13
+ include Mixins::FalsePositive
14
+
13
15
  # @return [Hash]
14
16
  attr_reader :data
15
17
 
@@ -109,10 +111,10 @@ module Mihari
109
111
  end
110
112
 
111
113
  #
112
- # @return [Array<String>]
114
+ # @return [Array<String, RegExp>]
113
115
  #
114
116
  def falsepositives
115
- @falsepositives ||= data[:falsepositives]
117
+ @falsepositives ||= data[:falsepositives].map { |fp| normalize_falsepositive fp }
116
118
  end
117
119
 
118
120
  #
@@ -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,6 +33,11 @@ module Mihari
19
33
  )
20
34
  end
21
35
 
36
+ #
37
+ # @param [Hash] d
38
+ #
39
+ # @return [Location]
40
+ #
22
41
  def self.from_dynamic!(d)
23
42
  d = Types::Hash[d]
24
43
  new(
@@ -39,6 +58,48 @@ module Mihari
39
58
  attribute :port, Types::Integer
40
59
  attribute :metadata, Types::Hash
41
60
 
61
+ #
62
+ # @return [String, nil]
63
+ #
64
+ def asn
65
+ attributes[:asn]
66
+ end
67
+
68
+ #
69
+ # @return [Array<String>]
70
+ #
71
+ def hostnames
72
+ attributes[:hostnames]
73
+ end
74
+
75
+ #
76
+ # @return [Location]
77
+ #
78
+ def location
79
+ attributes[:location]
80
+ end
81
+
82
+ #
83
+ # @return [String]
84
+ #
85
+ def ip_str
86
+ attributes[:ip_str]
87
+ end
88
+
89
+ #
90
+ # @return [Integer]
91
+ #
92
+ def port
93
+ attributes[:port]
94
+ end
95
+
96
+ #
97
+ # @return [Hash]
98
+ #
99
+ def metadata
100
+ attributes[:metadata]
101
+ end
102
+
42
103
  #
43
104
  # @return [Mihari::AutonomousSystem, nil]
44
105
  #
@@ -48,6 +109,11 @@ module Mihari
48
109
  Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
49
110
  end
50
111
 
112
+ #
113
+ # @param [Hash] d
114
+ #
115
+ # @return [Match]
116
+ #
51
117
  def self.from_dynamic!(d)
52
118
  d = Types::Hash[d]
53
119
 
@@ -74,6 +140,20 @@ module Mihari
74
140
  attribute :matches, Types.Array(Match)
75
141
  attribute :total, Types::Int
76
142
 
143
+ #
144
+ # @return [Array<Match>]
145
+ #
146
+ def matches
147
+ attributes[:matches]
148
+ end
149
+
150
+ #
151
+ # @return [Integer]
152
+ #
153
+ def total
154
+ attributes[:total]
155
+ end
156
+
77
157
  #
78
158
  # Collect metadata from matches
79
159
  #
@@ -107,12 +187,10 @@ module Mihari
107
187
  matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
108
188
  end
109
189
 
110
- #
111
- # @param [Source] source
112
190
  #
113
191
  # @return [Array<Mihari::Artifact>]
114
192
  #
115
- def to_artifacts(source = "Shodan")
193
+ def to_artifacts
116
194
  matches.map do |match|
117
195
  metadata = collect_metadata_by_ip(match.ip_str)
118
196
  ports = collect_ports_by_ip(match.ip_str).map do |port|
@@ -124,7 +202,6 @@ module Mihari
124
202
 
125
203
  Mihari::Artifact.new(
126
204
  data: match.ip_str,
127
- source: source,
128
205
  metadata: metadata,
129
206
  autonomous_system: match.to_asn,
130
207
  geolocation: match.location.to_geolocation,
@@ -134,6 +211,11 @@ module Mihari
134
211
  end
135
212
  end
136
213
 
214
+ #
215
+ # @param [Hash] d
216
+ #
217
+ # @return [Result]
218
+ #
137
219
  def self.from_dynamic!(d)
138
220
  d = Types::Hash[d]
139
221
  new(
@@ -151,6 +233,53 @@ module Mihari
151
233
  attribute :tags, Types.Array(Types::String)
152
234
  attribute :vulns, Types.Array(Types::String)
153
235
 
236
+ #
237
+ # @return [String]
238
+ #
239
+ def ip
240
+ attributes[:ip]
241
+ end
242
+
243
+ #
244
+ # @return [Array<Integer>]
245
+ #
246
+ def ports
247
+ attributes[:ports]
248
+ end
249
+
250
+ #
251
+ # @return [Array<String>]
252
+ #
253
+ def cpes
254
+ attributes[:cpes]
255
+ end
256
+
257
+ #
258
+ # @return [Array<String>]
259
+ #
260
+ def hostnames
261
+ attributes[:hostnames]
262
+ end
263
+
264
+ #
265
+ # @return [Array<String>]
266
+ #
267
+ def tags
268
+ attributes[:tags]
269
+ end
270
+
271
+ #
272
+ # @return [Array<String>]
273
+ #
274
+ def vulns
275
+ attributes[:vulns]
276
+ end
277
+
278
+ #
279
+ # @param [Hash] d
280
+ #
281
+ # @return [InternetDBResponse]
282
+ #
154
283
  def self.from_dynamic!(d)
155
284
  d = Types::Hash[d]
156
285
  new(
@@ -163,6 +292,11 @@ module Mihari
163
292
  )
164
293
  end
165
294
 
295
+ #
296
+ # @param [String] json
297
+ #
298
+ # @return [InternetDBResponse]
299
+ #
166
300
  def self.from_json!(json)
167
301
  from_dynamic!(JSON.parse(json))
168
302
  end
@@ -8,6 +8,32 @@ module Mihari
8
8
  attribute :ip, Types::String.optional
9
9
  attribute :url, Types::String
10
10
 
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
+ #
33
+ # @param [Hash] d
34
+ #
35
+ # @return [Page]
36
+ #
11
37
  def self.from_dynamic!(d)
12
38
  d = Types::Hash[d]
13
39
  new(
@@ -22,13 +48,58 @@ module Mihari
22
48
  attribute :page, Page
23
49
  attribute :id, Types::String
24
50
  attribute :sort, Types.Array(Types::String | Types::Integer)
51
+ attribute :metadata, Types::Hash
52
+
53
+ #
54
+ # @return [Page]
55
+ #
56
+ def page
57
+ attributes[:page]
58
+ end
59
+
60
+ #
61
+ # @return [String]
62
+ #
63
+ def id
64
+ attributes[:id]
65
+ end
66
+
67
+ #
68
+ # @return [Array<String, Integer>]
69
+ #
70
+ def sort
71
+ attributes[:sort]
72
+ end
73
+
74
+ #
75
+ # @return [Array<String, Integer>]
76
+ #
77
+ def metadata
78
+ attributes[:metadata]
79
+ end
25
80
 
81
+ #
82
+ # @return [Array<Mihari::Artifact>]
83
+ #
84
+ def to_artifacts
85
+ values = [page.url, page.domain, page.ip].compact
86
+ values.map do |value|
87
+ Mihari::Artifact.new(data: value, metadata: metadata)
88
+ end
89
+ end
90
+
91
+ #
92
+ # @param [Hash] d
93
+ #
94
+ # @return [Result]
95
+ #
26
96
  def self.from_dynamic!(d)
27
97
  d = Types::Hash[d]
28
98
  new(
29
99
  page: Page.from_dynamic!(d.fetch("page")),
30
100
  id: d.fetch("_id"),
31
- sort: d.fetch("sort")
101
+ sort: d.fetch("sort"),
102
+ metadata: d
32
103
  )
33
104
  end
34
105
  end
@@ -37,6 +108,32 @@ module Mihari
37
108
  attribute :results, Types.Array(Result)
38
109
  attribute :has_more, Types::Bool
39
110
 
111
+ #
112
+ # @return [Array<Result>]
113
+ #
114
+ def results
115
+ attributes[:results]
116
+ end
117
+
118
+ #
119
+ # @return [Boolean]
120
+ #
121
+ def has_more
122
+ attributes[:has_more]
123
+ end
124
+
125
+ #
126
+ # @return [Array<Mihari::Artifact>]
127
+ #
128
+ def to_artifacts
129
+ results.map(&:to_artifacts).flatten
130
+ end
131
+
132
+ #
133
+ # @param [Hash] d
134
+ #
135
+ # @return [Response]
136
+ #
40
137
  def self.from_dynamic!(d)
41
138
  d = Types::Hash[d]
42
139
  new(
@@ -6,6 +6,18 @@ module Mihari
6
6
  class ContextAttributes < Dry::Struct
7
7
  attribute :url, Types::String.optional
8
8
 
9
+ #
10
+ # @return [String, nil]
11
+ #
12
+ def url
13
+ attributes[:url]
14
+ end
15
+
16
+ #
17
+ # @param [Hash] d
18
+ #
19
+ # @return [ContextAttributes]
20
+ #
9
21
  def self.from_dynamic!(d)
10
22
  d = Types::Hash[d]
11
23
  new(
@@ -20,6 +32,37 @@ module Mihari
20
32
  attribute :context_attributes, ContextAttributes.optional
21
33
  attribute :metadata, Types::Hash
22
34
 
35
+ #
36
+ # @return [String]
37
+ #
38
+ def type
39
+ attributes[:type]
40
+ end
41
+
42
+ #
43
+ # @return [String]
44
+ #
45
+ def id
46
+ attributes[:id]
47
+ end
48
+
49
+ #
50
+ # @return [ContextAttributes, nil]
51
+ #
52
+ def context_attributes
53
+ attributes[:context_attributes]
54
+ end
55
+
56
+ #
57
+ # @return [Hash, nil]
58
+ #
59
+ def metadata
60
+ attributes[:metadata]
61
+ end
62
+
63
+ #
64
+ # @return [String, nil]
65
+ #
23
66
  def value
24
67
  case type
25
68
  when "file"
@@ -33,11 +76,25 @@ module Mihari
33
76
  end
34
77
  end
35
78
 
79
+ #
80
+ # @return [Mihari::Artifact]
81
+ #
82
+ def to_artifact
83
+ Artifact.new(data: value, metadata: metadata)
84
+ end
85
+
86
+ #
87
+ # @param [Hash] d
88
+ #
89
+ # @return [Datum]
90
+ #
36
91
  def self.from_dynamic!(d)
37
92
  d = Types::Hash[d]
38
93
 
39
94
  context_attributes = nil
40
- context_attributes = ContextAttributes.from_dynamic!(d.fetch("context_attributes")) if d.key?("context_attributes")
95
+ if d.key?("context_attributes")
96
+ context_attributes = ContextAttributes.from_dynamic!(d.fetch("context_attributes"))
97
+ end
41
98
 
42
99
  new(
43
100
  type: d.fetch("type"),
@@ -51,6 +108,18 @@ module Mihari
51
108
  class Meta < Dry::Struct
52
109
  attribute :cursor, Types::String.optional
53
110
 
111
+ #
112
+ # @return [String, nil]
113
+ #
114
+ def cursor
115
+ attributes[:cursor]
116
+ end
117
+
118
+ #
119
+ # @param [Hash] d
120
+ #
121
+ # @return [Meta]
122
+ #
54
123
  def self.from_dynamic!(d)
55
124
  d = Types::Hash[d]
56
125
  new(
@@ -63,6 +132,32 @@ module Mihari
63
132
  attribute :meta, Meta
64
133
  attribute :data, Types.Array(Datum)
65
134
 
135
+ #
136
+ # @return [Meta]
137
+ #
138
+ def meta
139
+ attributes[:meta]
140
+ end
141
+
142
+ #
143
+ # @return [Array<Datum>]
144
+ #
145
+ def data
146
+ attributes[:data]
147
+ end
148
+
149
+ #
150
+ # @return [Array<Mihari::Artifact>]
151
+ #
152
+ def to_artifacts
153
+ data.map(&:to_artifact)
154
+ end
155
+
156
+ #
157
+ # @param [Hash] d
158
+ #
159
+ # @return [Response]
160
+ #
66
161
  def self.from_dynamic!(d)
67
162
  d = Types::Hash[d]
68
163
  new(
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "5.2.0"
4
+ VERSION = "5.2.2"
5
5
  end
@@ -44,7 +44,7 @@ module Mihari
44
44
  end.to_app
45
45
  end
46
46
 
47
- def run!(port: 9292, host: "localhost", threads: "0:5", verbose: false, worker_timeout: 60)
47
+ def run!(port: 9292, host: "localhost", threads: "0:5", verbose: false, worker_timeout: 60, open: true)
48
48
  url = "http://#{host}:#{port}"
49
49
 
50
50
  # set maximum number of threads to use as PARALLEL_PROCESSOR_COUNT (if it is not set)
@@ -60,7 +60,7 @@ module Mihari
60
60
  Verbose: verbose,
61
61
  worker_timeout: worker_timeout
62
62
  ) do |_|
63
- Launchy.open(url) if ENV["RACK_ENV"] != "development"
63
+ Launchy.open(url) if ENV["RACK_ENV"] != "development" && open
64
64
  rescue Launchy::CommandNotFoundError
65
65
  # ref. https://github.com/ninoseki/mihari/issues/477
66
66
  # do nothing