brakeman-min 4.5.1 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +16 -0
  3. data/README.md +0 -1
  4. data/lib/brakeman/call_index.rb +54 -15
  5. data/lib/brakeman/checks/base_check.rb +27 -46
  6. data/lib/brakeman/checks/check_cookie_serialization.rb +22 -0
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +3 -3
  8. data/lib/brakeman/checks/check_deserialize.rb +3 -6
  9. data/lib/brakeman/checks/check_file_access.rb +7 -1
  10. data/lib/brakeman/checks/check_header_dos.rb +2 -2
  11. data/lib/brakeman/checks/check_i18n_xss.rb +2 -2
  12. data/lib/brakeman/checks/check_jruby_xml.rb +2 -2
  13. data/lib/brakeman/checks/check_json_parsing.rb +2 -2
  14. data/lib/brakeman/checks/check_mime_type_dos.rb +2 -2
  15. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +1 -1
  16. data/lib/brakeman/checks/check_reverse_tabnabbing.rb +54 -0
  17. data/lib/brakeman/checks/check_sanitize_methods.rb +2 -2
  18. data/lib/brakeman/checks/check_session_settings.rb +5 -2
  19. data/lib/brakeman/checks/check_xml_dos.rb +2 -2
  20. data/lib/brakeman/checks/check_yaml_parsing.rb +10 -18
  21. data/lib/brakeman/file_parser.rb +4 -8
  22. data/lib/brakeman/file_path.rb +14 -0
  23. data/lib/brakeman/processor.rb +1 -1
  24. data/lib/brakeman/processors/alias_processor.rb +5 -1
  25. data/lib/brakeman/processors/controller_processor.rb +4 -4
  26. data/lib/brakeman/processors/gem_processor.rb +10 -2
  27. data/lib/brakeman/processors/haml_template_processor.rb +1 -1
  28. data/lib/brakeman/processors/lib/find_all_calls.rb +27 -4
  29. data/lib/brakeman/processors/lib/find_call.rb +3 -64
  30. data/lib/brakeman/processors/template_processor.rb +10 -6
  31. data/lib/brakeman/rescanner.rb +4 -0
  32. data/lib/brakeman/tracker.rb +26 -2
  33. data/lib/brakeman/tracker/config.rb +39 -15
  34. data/lib/brakeman/tracker/constants.rb +2 -1
  35. data/lib/brakeman/version.rb +1 -1
  36. data/lib/brakeman/warning.rb +4 -0
  37. data/lib/brakeman/warning_codes.rb +3 -0
  38. data/lib/ruby_parser/bm_sexp.rb +1 -1
  39. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ccaea38ca4a79de01e6e3b0e0f680a47d975dfb0e1aeac3279747854f71a83e
4
- data.tar.gz: 531cb396d3485562e62209cb391d712f938b2c1de1674627373013fd45a6c399
3
+ metadata.gz: 0f2916d8c800c2d3da1efe112dd1a4dd7fafd68bfc9c0d7c765f495ccec46522
4
+ data.tar.gz: 3ba195217e6e07fcbcfb59192e1820aa2d4b4d9703d62d8a07205413ce3c904b
5
5
  SHA512:
6
- metadata.gz: b6177edcd4c59853afd48be4b2643af02c9a233f394b2f813c48cfdacf1614950e1147d9d77ea4921db807d6bab7300364e550ac6b0a8d12befa412f8940e679
7
- data.tar.gz: 0157dc06a6be674931c395bf26d72d81deeecdc3bfda2d1cee24518d24165a494d0fe8c82aa23a45ad3ee0f15afc2d1e7029b4ecbbe8b77ef09d029c0036c92d
6
+ metadata.gz: e1e2cb39f056346a9c9a00a3d3d1afa2291037023396cb64b49681d5039acb0cd1e0f1a7175f88bccebaa8337be3c942a2283c2dd81cac536b4366d73d904d0a
7
+ data.tar.gz: 8c320ac49aca344b30cdfaf8a68d3909cb181c15cc40a38d5e0bbfeae6ec8de7a4ada8cf7d6e8f70a085e58240d7874cdc4e06f7c73fb0191e96d98bab0b35a5
data/CHANGES.md CHANGED
@@ -1,3 +1,19 @@
1
+ # 4.6.0
2
+
3
+ * Skip calls to `dup`
4
+ * Add reverse tabnabbing check (Linos Giannopoulos)
5
+ * Better handling of gems with no version declared
6
+ * Warn people that Haml 5 is not fully supported (Jared Beck)
7
+ * Avoid warning about file access with `ActiveStorage::Filename#sanitized` (Tejas Bubane)
8
+ * Update loofah version for fixing CVE-2018-8048 (Markus Nölle)
9
+ * Restore `Warning#relative_path`
10
+ * Add check for cookie serialization with Marshal
11
+ * Index calls in initializers
12
+ * Improve template output handling in conditional branches
13
+ * Avoid assigning `nil` line numbers to `Sexp`s
14
+ * Add special warning code for custom checks
15
+ * Add call matching by regular expression
16
+
1
17
  # 4.5.1
2
18
 
3
19
  * Add `Brakeman::FilePath` to represent file paths
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  [![Brakeman Logo](http://brakemanscanner.org/images/logo_medium.png)](http://brakemanscanner.org/)
2
2
 
3
3
  [![Build Status](https://circleci.com/gh/presidentbeef/brakeman.svg?style=svg)](https://circleci.com/gh/presidentbeef/brakeman)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/1b08a5c74695cb0d11ec/maintainability)](https://codeclimate.com/github/presidentbeef/brakeman/maintainability)
5
4
  [![Test Coverage](https://api.codeclimate.com/v1/badges/1b08a5c74695cb0d11ec/test_coverage)](https://codeclimate.com/github/presidentbeef/brakeman/test_coverage)
6
5
  [![Gitter](https://badges.gitter.im/presidentbeef/brakeman.svg)](https://gitter.im/presidentbeef/brakeman)
7
6
 
@@ -27,7 +27,7 @@ class Brakeman::CallIndex
27
27
  if options[:chained]
28
28
  return find_chain options
29
29
  #Find by narrowest category
30
- elsif target and method and target.is_a? Array and method.is_a? Array
30
+ elsif target.is_a? Array and method.is_a? Array
31
31
  if target.length > method.length
32
32
  calls = filter_by_target calls_by_methods(method), target
33
33
  else
@@ -35,6 +35,12 @@ class Brakeman::CallIndex
35
35
  calls = filter_by_method calls, method
36
36
  end
37
37
 
38
+ elsif target.is_a? Regexp and method
39
+ calls = filter_by_target(calls_by_method(method), target)
40
+
41
+ elsif method.is_a? Regexp and target
42
+ calls = filter_by_method(calls_by_target(target), method)
43
+
38
44
  #Find by target, then by methods, if provided
39
45
  elsif target
40
46
  calls = calls_by_target target
@@ -85,6 +91,16 @@ class Brakeman::CallIndex
85
91
  end
86
92
  end
87
93
 
94
+ def remove_indexes_by_file file
95
+ [@calls_by_method, @calls_by_target].each do |calls_by|
96
+ calls_by.each do |_name, calls|
97
+ calls.delete_if do |call|
98
+ call[:location][:file] == file
99
+ end
100
+ end
101
+ end
102
+ end
103
+
88
104
  def index_calls calls
89
105
  calls.each do |call|
90
106
  @calls_by_method[call[:method]] ||= []
@@ -116,8 +132,11 @@ class Brakeman::CallIndex
116
132
  end
117
133
 
118
134
  def calls_by_target target
119
- if target.is_a? Array
135
+ case target
136
+ when Array
120
137
  calls_by_targets target
138
+ when Regexp
139
+ calls_by_targets_regex target
121
140
  else
122
141
  @calls_by_target[target] || []
123
142
  end
@@ -133,10 +152,24 @@ class Brakeman::CallIndex
133
152
  calls
134
153
  end
135
154
 
155
+ def calls_by_targets_regex targets_regex
156
+ calls = []
157
+
158
+ @calls_by_target.each do |key, value|
159
+ case key
160
+ when String, Symbol
161
+ calls.concat value if key.match targets_regex
162
+ end
163
+ end
164
+
165
+ calls
166
+ end
167
+
136
168
  def calls_by_method method
137
- if method.is_a? Array
169
+ case method
170
+ when Array
138
171
  calls_by_methods method
139
- elsif method.is_a? Regexp
172
+ when Regexp
140
173
  calls_by_methods_regex method
141
174
  else
142
175
  @calls_by_method[method.to_sym] || []
@@ -156,26 +189,28 @@ class Brakeman::CallIndex
156
189
 
157
190
  def calls_by_methods_regex methods_regex
158
191
  calls = []
192
+
159
193
  @calls_by_method.each do |key, value|
160
- calls.concat value if key.to_s.match methods_regex
194
+ calls.concat value if key.match methods_regex
161
195
  end
162
- calls
163
- end
164
196
 
165
- def calls_with_no_target
166
- @calls_by_target[nil]
197
+ calls
167
198
  end
168
199
 
169
200
  def filter calls, key, value
170
- if value.is_a? Array
201
+ case value
202
+ when Array
171
203
  values = Set.new value
172
204
 
173
205
  calls.select do |call|
174
206
  values.include? call[key]
175
207
  end
176
- elsif value.is_a? Regexp
208
+ when Regexp
177
209
  calls.select do |call|
178
- call[key].to_s.match value
210
+ case call[key]
211
+ when String, Symbol
212
+ call[key].match value
213
+ end
179
214
  end
180
215
  else
181
216
  calls.select do |call|
@@ -197,15 +232,19 @@ class Brakeman::CallIndex
197
232
  end
198
233
 
199
234
  def filter_by_chain calls, target
200
- if target.is_a? Array
235
+ case target
236
+ when Array
201
237
  targets = Set.new target
202
238
 
203
239
  calls.select do |call|
204
240
  targets.include? call[:chain].first
205
241
  end
206
- elsif target.is_a? Regexp
242
+ when Regexp
207
243
  calls.select do |call|
208
- call[:chain].first.to_s.match target
244
+ case call[:chain].first
245
+ when String, Symbol
246
+ call[:chain].first.match target
247
+ end
209
248
  end
210
249
  else
211
250
  calls.select do |call|
@@ -44,19 +44,9 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
44
44
  end
45
45
 
46
46
  #Add result to result list, which is used to check for duplicates
47
- def add_result result, location = nil
48
- location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
49
- location = location[:name] if location.is_a? Hash
50
- location = location.name if location.is_a? Brakeman::Collection
51
- location = location.to_sym
52
-
53
- if result.is_a? Hash
54
- line = result[:call].original_line || result[:call].line
55
- elsif sexp? result
56
- line = result.original_line || result.line
57
- else
58
- raise ArgumentError
59
- end
47
+ def add_result result
48
+ location = get_location result
49
+ location, line = get_location result
60
50
 
61
51
  @results << [line, location, result]
62
52
  end
@@ -170,8 +160,9 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
170
160
  @mass_assign_disabled = true
171
161
  else
172
162
  #Check for ActiveRecord::Base.send(:attr_accessible, nil)
173
- tracker.check_initializers(:"ActiveRecord::Base", :attr_accessible).each do |result|
174
- call = result.call
163
+ tracker.find_call(target: :"ActiveRecord::Base", method: :attr_accessible).each do |result|
164
+ call = result[:call]
165
+
175
166
  if call? call
176
167
  if call.first_arg == Sexp.new(:nil)
177
168
  @mass_assign_disabled = true
@@ -180,26 +171,12 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
180
171
  end
181
172
  end
182
173
 
183
- unless @mass_assign_disabled
184
- tracker.check_initializers(:"ActiveRecord::Base", :send).each do |result|
185
- call = result.call
186
- if call? call
187
- if call.first_arg == Sexp.new(:lit, :attr_accessible) and call.second_arg == Sexp.new(:nil)
188
- @mass_assign_disabled = true
189
- break
190
- end
191
- end
192
- end
193
- end
194
-
195
174
  unless @mass_assign_disabled
196
175
  #Check for
197
176
  # class ActiveRecord::Base
198
177
  # attr_accessible nil
199
178
  # end
200
- matches = tracker.check_initializers([], :attr_accessible)
201
-
202
- matches.each do |result|
179
+ tracker.check_initializers([], :attr_accessible).each do |result|
203
180
  if result.module == "ActiveRecord" and result.result_class == :Base
204
181
  arg = result.call.first_arg
205
182
 
@@ -227,10 +204,8 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
227
204
  end
228
205
 
229
206
  unless @mass_assign_disabled
230
- matches = tracker.check_initializers(:"ActiveRecord::Base", [:send, :include])
231
-
232
- matches.each do |result|
233
- call = result.call
207
+ tracker.find_call(target: :"ActiveRecord::Base", method: [:send, :include]).each do |result|
208
+ call = result[:call]
234
209
  if call? call and (call.first_arg == forbidden_protection or call.second_arg == forbidden_protection)
235
210
  @mass_assign_disabled = true
236
211
  end
@@ -250,6 +225,22 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
250
225
  #This is to avoid reporting duplicates. Checks if the result has been
251
226
  #reported already from the same line number.
252
227
  def duplicate? result, location = nil
228
+ location, line = get_location result
229
+
230
+ @results.each do |r|
231
+ if r[0] == line and r[1] == location
232
+ if tracker.options[:combine_locations]
233
+ return true
234
+ elsif r[2] == result
235
+ return true
236
+ end
237
+ end
238
+ end
239
+
240
+ false
241
+ end
242
+
243
+ def get_location result
253
244
  if result.is_a? Hash
254
245
  line = result[:call].original_line || result[:call].line
255
246
  elsif sexp? result
@@ -258,23 +249,13 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
258
249
  raise ArgumentError
259
250
  end
260
251
 
261
- location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
252
+ location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template] || result[:location][:file].to_s
262
253
 
263
254
  location = location[:name] if location.is_a? Hash
264
255
  location = location.name if location.is_a? Brakeman::Collection
265
256
  location = location.to_sym
266
257
 
267
- @results.each do |r|
268
- if r[0] == line and r[1] == location
269
- if tracker.options[:combine_locations]
270
- return true
271
- elsif r[2] == result
272
- return true
273
- end
274
- end
275
- end
276
-
277
- false
258
+ return location, line
278
259
  end
279
260
 
280
261
  #Checks if an expression contains string interpolation.
@@ -0,0 +1,22 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckCookieSerialization < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check for use of Marshal for cookie serialization"
7
+
8
+ def run_check
9
+ tracker.find_call(target: :'Rails.application.config.action_dispatch', method: :cookies_serializer=).each do |result|
10
+ setting = result[:call].first_arg
11
+
12
+ if symbol? setting and setting.value == :marshal or setting.value == :hybrid
13
+ warn :result => result,
14
+ :warning_type => "Remote Code Execution",
15
+ :warning_code => :unsafe_cookie_serialization,
16
+ :message => msg("Use of unsafe cookie serialization strategy ", msg_code(setting.value.inspect), " might lead to remote code execution"),
17
+ :confidence => :medium,
18
+ :link_path => "unsafe_deserialization"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -316,11 +316,11 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
316
316
  end
317
317
 
318
318
  json_escape_on = false
319
- initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
320
- initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
319
+ initializers = tracker.find_call(target: :ActiveSupport, method: :escape_html_entities_in_json=)
320
+ initializers.each {|result| json_escape_on = true?(result[:call].first_arg) }
321
321
 
322
322
  if tracker.config.escape_html_entities_in_json?
323
- json_escape_on = true
323
+ json_escape_on = true
324
324
  elsif version_between? "4.0.0", "9.9.9"
325
325
  json_escape_on = true
326
326
  end
@@ -80,13 +80,10 @@ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
80
80
  def oj_safe_default?
81
81
  safe_default = false
82
82
 
83
- # TODO: Can we just index initializers already??
84
- if tracker.check_initializers(:Oj, :mimic_JSON).any?
83
+ if tracker.find_call(target: :Oj, method: :mimic_JSON).any?
85
84
  safe_default = true
86
- end
87
-
88
- if result = tracker.check_initializers(:Oj, :default_options=).first
89
- options = result.call.first_arg
85
+ elsif result = tracker.find_call(target: :Oj, method: :default_options=).first
86
+ options = result[:call].first_arg
90
87
 
91
88
  if oj_safe_mode? options
92
89
  safe_default = true
@@ -32,7 +32,7 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
32
32
 
33
33
  file_name = call.first_arg
34
34
 
35
- return if called_on_tempfile?(file_name)
35
+ return if called_on_tempfile?(file_name) || sanitized?(file_name)
36
36
 
37
37
  if match = has_immediate_user_input?(file_name)
38
38
  confidence = :high
@@ -71,6 +71,12 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
71
71
  call?(file_name) && file_name.target == s(:const, :Tempfile)
72
72
  end
73
73
 
74
+ def sanitized? file
75
+ call?(file) &&
76
+ call?(file.target) &&
77
+ class_name(file.target.target) == :"ActiveStorage::Filename"
78
+ end
79
+
74
80
  def temp_file_method? exp
75
81
  if call? exp
76
82
  return true if exp.call_chain.include? :tempfile
@@ -25,7 +25,7 @@ class Brakeman::CheckHeaderDoS < Brakeman::BaseCheck
25
25
  end
26
26
 
27
27
  def has_workaround?
28
- tracker.check_initializers(:ActiveSupport, :on_load).any? and
29
- tracker.check_initializers(:"ActionView::LookupContext::DetailsKey", :class_eval).any?
28
+ tracker.find_call(target: :ActiveSupport, method: :on_load).any? and
29
+ tracker.find_call(target: :"ActionView::LookupContext::DetailsKey", method: :class_eval).any?
30
30
  end
31
31
  end
@@ -41,8 +41,8 @@ class Brakeman::CheckI18nXSS < Brakeman::BaseCheck
41
41
  end
42
42
 
43
43
  def has_workaround?
44
- tracker.check_initializers(:I18n, :const_defined?).any? do |match|
45
- match.last.first_arg == s(:lit, :MissingTranslation)
44
+ tracker.find_call(target: :I18n, method: :const_defined?, chained: true).any? do |match|
45
+ match[:call].first_arg == s(:lit, :MissingTranslation)
46
46
  end
47
47
  end
48
48
  end
@@ -20,8 +20,8 @@ class Brakeman::CheckJRubyXML < Brakeman::BaseCheck
20
20
  end
21
21
 
22
22
  #Check for workaround
23
- tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).each do |result|
24
- arg = result.call.first_arg
23
+ tracker.find_call(target: :"ActiveSupport::XmlMini", method: :backend=, chained: true).each do |result|
24
+ arg = result[:call].first_arg
25
25
 
26
26
  return if string? arg and arg.value == "REXML"
27
27
  end
@@ -44,13 +44,13 @@ class Brakeman::CheckJSONParsing < Brakeman::BaseCheck
44
44
 
45
45
  #Check for `ActiveSupport::JSON.backend = "JSONGem"`
46
46
  def uses_gem_backend?
47
- matches = tracker.check_initializers(:'ActiveSupport::JSON', :backend=)
47
+ matches = tracker.find_call(target: :'ActiveSupport::JSON', method: :backend=, chained: true)
48
48
 
49
49
  unless matches.empty?
50
50
  json_gem = s(:str, "JSONGem")
51
51
 
52
52
  matches.each do |result|
53
- if result.call.first_arg == json_gem
53
+ if result[:call].first_arg == json_gem
54
54
  return true
55
55
  end
56
56
  end
@@ -30,8 +30,8 @@ class Brakeman::CheckMimeTypeDoS < Brakeman::BaseCheck
30
30
  end
31
31
 
32
32
  def has_workaround?
33
- tracker.check_initializers(:Mime, :const_set).any? do |match|
34
- arg = match.call.first_arg
33
+ tracker.find_call(target: :Mime, method: :const_set).any? do |match|
34
+ arg = match[:call].first_arg
35
35
 
36
36
  symbol? arg and arg.value == :LOOKUP
37
37
  end
@@ -53,6 +53,6 @@ class Brakeman::CheckNestedAttributesBypass < Brakeman::BaseCheck
53
53
  end
54
54
 
55
55
  def workaround?
56
- tracker.check_initializers([], :will_be_destroyed?).any?
56
+ tracker.find_call(method: :will_be_destroyed?).any?
57
57
  end
58
58
  end
@@ -0,0 +1,54 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckReverseTabnabbing < Brakeman::BaseCheck
4
+ Brakeman::Checks.add_optional self
5
+
6
+ @description = "Checks for reverse tabnabbing cases on 'link_to' calls"
7
+
8
+ def run_check
9
+ calls = tracker.find_call :methods => :link_to
10
+ calls.each do |call|
11
+ process_result call
12
+ end
13
+ end
14
+
15
+ def process_result result
16
+ return unless original? result and result[:call].last_arg
17
+
18
+ html_opts = result[:call].last_arg
19
+ return unless hash? html_opts
20
+
21
+ target = hash_access html_opts, :target
22
+ return unless target && string?(target) && target.value == "_blank"
23
+
24
+ target_url = result[:block] ? result[:call].first_arg : result[:call].second_arg
25
+
26
+ # `url_for` and `_path` calls lead to urls on to the same origin.
27
+ # That means that an adversary would need to run javascript on
28
+ # the victim application's domain. If that is the case, the adversary
29
+ # already has the ability to redirect the victim user anywhere.
30
+ # Also statically provided URLs (interpolated or otherwise) are also
31
+ # ignored as they produce many false positives.
32
+ return if !call?(target_url) || target_url.method.match(/^url_for$|_path$/)
33
+
34
+ rel = hash_access html_opts, :rel
35
+ confidence = :medium
36
+
37
+ if rel && string?(rel) then
38
+ rel_opt = rel.value
39
+ return if rel_opt.include?("noopener") && rel_opt.include?("noreferrer")
40
+
41
+ if rel_opt.include?("noopener") ^ rel_opt.include?("noreferrer") then
42
+ confidence = :weak
43
+ end
44
+ end
45
+
46
+ warn :result => result,
47
+ :warning_type => "Reverse Tabnabbing",
48
+ :warning_code => :reverse_tabnabbing,
49
+ :message => msg("When opening a link in a new tab without setting ", msg_code('rel: "noopener noreferr"'),
50
+ ", the new tab can control the parent tab's location. For example, an attacker could redirect to a phishing page."),
51
+ :confidence => confidence,
52
+ :user_input => rel
53
+ end
54
+ end
@@ -70,7 +70,7 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
70
70
 
71
71
  def check_cve_2018_8048
72
72
  if loofah_vulnerable_cve_2018_8048?
73
- message = msg(msg_version(tracker.config.gem_version(:loofah), "loofah gem"), " is vulnerable (CVE-2018-8048). Upgrade to 2.1.2")
73
+ message = msg(msg_version(tracker.config.gem_version(:loofah), "loofah gem"), " is vulnerable (CVE-2018-8048). Upgrade to 2.2.1")
74
74
 
75
75
  if tracker.find_call(:target => false, :method => :sanitize).any?
76
76
  confidence = :high
@@ -90,7 +90,7 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
90
90
  def loofah_vulnerable_cve_2018_8048?
91
91
  loofah_version = tracker.config.gem_version(:loofah)
92
92
 
93
- loofah_version and loofah_version < "2.1.2"
93
+ loofah_version and loofah_version < "2.2.1"
94
94
  end
95
95
 
96
96
  def warn_sanitizer_cve cve, link, upgrade_version
@@ -21,8 +21,11 @@ class Brakeman::CheckSessionSettings < Brakeman::BaseCheck
21
21
 
22
22
  check_for_issues settings, @app_tree.file_path("config/environment.rb")
23
23
 
24
- ["session_store.rb", "secret_token.rb"].each do |file|
25
- if tracker.initializers[file] and not ignored? file
24
+ session_store = @app_tree.file_path("config/initializers/session_store.rb")
25
+ secret_token = @app_tree.file_path("config/initializers/secret_token.rb")
26
+
27
+ [session_store, secret_token].each do |file|
28
+ if tracker.initializers[file] and not ignored? file.basename
26
29
  process tracker.initializers[file]
27
30
  end
28
31
  end
@@ -34,8 +34,8 @@ class Brakeman::CheckXMLDoS < Brakeman::BaseCheck
34
34
  end
35
35
 
36
36
  def has_workaround?
37
- tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).any? do |match|
38
- arg = match.call.first_arg
37
+ tracker.find_call(target: :"ActiveSupport::XmlMini", method: :backend=).any? do |match|
38
+ arg = match[:call].first_arg
39
39
  if string? arg
40
40
  value = arg.value
41
41
  value == 'Nokogiri' or value == 'LibXML'
@@ -48,21 +48,17 @@ class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
48
48
  def disabled_xml_parser?
49
49
  if version_between? "0.0.0", "2.3.14"
50
50
  #Look for ActionController::Base.param_parsers.delete(Mime::XML)
51
- params_parser = s(:call,
52
- s(:colon2, s(:const, :ActionController), :Base),
53
- :param_parsers)
54
-
55
- matches = tracker.check_initializers(params_parser, :delete)
51
+ matches = tracker.find_call(target: :"ActionController::Base.param_parsers", method: :delete)
56
52
  else
57
53
  #Look for ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML)
58
- matches = tracker.check_initializers(:"ActionDispatch::ParamsParser::DEFAULT_PARSERS", :delete)
54
+ matches = tracker.find_call(target: :"ActionDispatch::ParamsParser::DEFAULT_PARSERS", method: :delete)
59
55
  end
60
56
 
61
57
  unless matches.empty?
62
58
  mime_xml = s(:colon2, s(:const, :Mime), :XML)
63
59
 
64
60
  matches.each do |result|
65
- if result.call.first_arg == mime_xml
61
+ if result[:call].first_arg == mime_xml
66
62
  return true
67
63
  end
68
64
  end
@@ -74,18 +70,14 @@ class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
74
70
  #Look for ActionController::Base.param_parsers[Mime::YAML] = :yaml
75
71
  #in Rails 2.x apps
76
72
  def enabled_yaml_parser?
77
- param_parsers = s(:call,
78
- s(:colon2, s(:const, :ActionController), :Base),
79
- :param_parsers)
80
-
81
- matches = tracker.check_initializers(param_parsers, :[]=)
73
+ matches = tracker.find_call(target: :'ActionController::Base.param_parsers', method: :[]=)
82
74
 
83
75
  mime_yaml = s(:colon2, s(:const, :Mime), :YAML)
84
76
 
85
77
  matches.each do |result|
86
- if result.call.first_arg == mime_yaml and
87
- symbol? result.call.second_arg and
88
- result.call.second_arg.value == :yaml
78
+ if result[:call].first_arg == mime_yaml and
79
+ symbol? result[:call].second_arg and
80
+ result[:call].second_arg.value == :yaml
89
81
 
90
82
  return true
91
83
  end
@@ -96,16 +88,16 @@ class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
96
88
 
97
89
  def disabled_xml_dangerous_types?
98
90
  if version_between? "0.0.0", "2.3.14"
99
- matches = tracker.check_initializers(:"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", :delete)
91
+ matches = tracker.find_call(target: :"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", method: :delete)
100
92
  else
101
- matches = tracker.check_initializers(:"ActiveSupport::XmlMini::PARSING", :delete)
93
+ matches = tracker.find_call(target: :"ActiveSupport::XmlMini::PARSING", method: :delete)
102
94
  end
103
95
 
104
96
  symbols_off = false
105
97
  yaml_off = false
106
98
 
107
99
  matches.each do |result|
108
- arg = result.call.first_arg
100
+ arg = result[:call].first_arg
109
101
 
110
102
  if string? arg
111
103
  if arg.value == "yaml"
@@ -33,17 +33,13 @@ module Brakeman
33
33
  end
34
34
  end
35
35
 
36
- def parse_ruby input, path, parser = RubyParser.new
36
+ def parse_ruby input, path
37
37
  begin
38
38
  Brakeman.debug "Parsing #{path}"
39
- parser.parse input, path, @timeout
39
+ RubyParser.new.parse input, path, @timeout
40
40
  rescue Racc::ParseError => e
41
- if parser.class == RubyParser
42
- return parse_ruby(input, path, RubyParser.latest)
43
- else
44
- @tracker.error e, "Could not parse #{path}"
45
- nil
46
- end
41
+ @tracker.error e, "Could not parse #{path}"
42
+ nil
47
43
  rescue Timeout::Error => e
48
44
  @tracker.error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout"), caller
49
45
  nil
@@ -35,6 +35,11 @@ module Brakeman
35
35
  @relative = relative_path
36
36
  end
37
37
 
38
+ # Just the file name, no path
39
+ def basename
40
+ @basename ||= File.basename(self.relative)
41
+ end
42
+
38
43
  # Read file from absolute path.
39
44
  def read
40
45
  File.read self.absolute
@@ -67,5 +72,14 @@ module Brakeman
67
72
  def to_s
68
73
  self.to_str
69
74
  end
75
+
76
+ def hash
77
+ @hash ||= [@absolute, @relative].hash
78
+ end
79
+
80
+ def eql? rhs
81
+ @absolute == rhs.absolute and
82
+ @relative == rhs.relative
83
+ end
70
84
  end
71
85
  end
@@ -90,7 +90,7 @@ module Brakeman
90
90
  def process_initializer file_name, src
91
91
  res = BaseProcessor.new(@tracker).process_file src, file_name
92
92
  res = AliasProcessor.new(@tracker).process_safely res, nil, file_name
93
- @tracker.initializers[Pathname.new(file_name).basename.to_s] = res
93
+ @tracker.initializers[file_name] = res
94
94
  end
95
95
 
96
96
  #Process source for a library file
@@ -265,6 +265,10 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
265
265
  unless target.nil?
266
266
  exp = target
267
267
  end
268
+ when :dup
269
+ unless target.nil?
270
+ exp = target
271
+ end
268
272
  when :join
269
273
  if array? target and target.length > 2 and (string? first_arg or first_arg.nil?)
270
274
  exp = process_array_join(target, first_arg)
@@ -602,7 +606,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
602
606
  if node_type? exp, :hash
603
607
  if exp.any? { |e| node_type? e, :kwsplat and node_type? e.value, :hash }
604
608
  kwsplats, rest = exp.partition { |e| node_type? e, :kwsplat and node_type? e.value, :hash }
605
- exp = Sexp.new.concat(rest).line(exp)
609
+ exp = Sexp.new.concat(rest).line(exp.line)
606
610
 
607
611
  kwsplats.each do |e|
608
612
  exp = process_hash_merge! exp, e.value
@@ -192,8 +192,8 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
192
192
 
193
193
  filter_name = ("fake_filter" + rand.to_s[/\d+$/]).to_sym
194
194
  args = exp.block_call.arglist
195
- args.insert(1, Sexp.new(:lit, filter_name))
196
- before_filter_call = make_call(nil, :before_filter, args)
195
+ args.insert(1, Sexp.new(:lit, filter_name).line(exp.line))
196
+ before_filter_call = make_call(nil, :before_filter, args).line(exp.line)
197
197
 
198
198
  if exp.block_args.length > 1
199
199
  block_variable = exp.block_args[1]
@@ -210,9 +210,9 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
210
210
  #Build Sexp for filter method
211
211
  body = Sexp.new(:lasgn,
212
212
  block_variable,
213
- Sexp.new(:call, Sexp.new(:const, @current_class.name), :new))
213
+ Sexp.new(:call, Sexp.new(:const, @current_class.name).line(exp.line), :new).line(exp.line)).line(exp.line)
214
214
 
215
- filter_method = Sexp.new(:defn, filter_name, Sexp.new(:args), body).concat(block_inner).line(exp.line)
215
+ filter_method = Sexp.new(:defn, filter_name, Sexp.new(:args).line(exp.line), body).concat(block_inner).line(exp.line)
216
216
 
217
217
  vis = @visibility
218
218
  @visibility = :private
@@ -29,6 +29,14 @@ class Brakeman::GemProcessor < Brakeman::BasicProcessor
29
29
  @tracker.config.set_rails_version
30
30
  end
31
31
 
32
+ # Known issue: Brakeman does not yet support `gem` calls with multiple
33
+ # "version requirements". Consider the following example from the ruby docs:
34
+ #
35
+ # gem 'rake', '>= 1.1.a', '< 2'
36
+ #
37
+ # We are assuming that `second_arg` (eg. '>= 1.1.a') is the only requirement.
38
+ # Perhaps we should instantiate an array of `::Gem::Requirement`s or even a
39
+ # `::Gem::Dependency` and pass that to `Tracker::Config#add_gem`?
32
40
  def process_call exp
33
41
  if exp.target == nil
34
42
  if exp.method == :gem
@@ -51,8 +59,8 @@ class Brakeman::GemProcessor < Brakeman::BasicProcessor
51
59
  end
52
60
  end
53
61
  elsif @inside_gemspec and exp.method == :add_dependency
54
- if string? exp.first_arg and string? exp.last_arg
55
- @tracker.config.add_gem exp.first_arg.value, exp.last_arg.value, @gemspec, exp.line
62
+ if string? exp.first_arg and string? exp.second_arg
63
+ @tracker.config.add_gem exp.first_arg.value, exp.second_arg.value, @gemspec, exp.line
56
64
  end
57
65
  end
58
66
 
@@ -179,7 +179,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
179
179
  clauses = [get_pushed_value(exp.then_clause), get_pushed_value(exp.else_clause)].compact
180
180
 
181
181
  if clauses.length > 1
182
- s(:or, *clauses)
182
+ s(:or, *clauses).line(exp.line)
183
183
  else
184
184
  clauses.first
185
185
  end
@@ -5,9 +5,9 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
5
5
 
6
6
  def initialize tracker
7
7
  super
8
- @current_class = nil
9
- @current_method = nil
8
+
10
9
  @in_target = false
10
+ @processing_class = false
11
11
  @calls = []
12
12
  @cache = {}
13
13
  end
@@ -23,10 +23,33 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
23
23
  process exp
24
24
  end
25
25
 
26
+ #For whatever reason, originally the indexing of calls
27
+ #was performed on individual method bodies (see process_defn).
28
+ #This method explicitly indexes all calls everywhere given any
29
+ #source.
30
+ def process_all_source exp, opts
31
+ @processing_class = true
32
+ process_source exp, opts
33
+ ensure
34
+ @processing_class = false
35
+ end
36
+
26
37
  #Process body of method
27
38
  def process_defn exp
28
- return exp unless @current_method
29
- process_all exp.body
39
+ return exp unless @current_method or @processing_class
40
+
41
+ # 'Normal' processing assumes the method name was given
42
+ # as an option to `process_source` but for `process_all_source`
43
+ # we don't want to do that.
44
+ if @current_method.nil?
45
+ @current_method = exp.method_name
46
+ process_all exp.body
47
+ @current_method = nil
48
+ else
49
+ process_all exp.body
50
+ end
51
+
52
+ exp
30
53
  end
31
54
 
32
55
  alias process_defs process_defn
@@ -33,14 +33,13 @@ require 'brakeman/processors/lib/basic_processor'
33
33
  # FindCall.new nil, /^g?sub!?$/
34
34
  class Brakeman::FindCall < Brakeman::BasicProcessor
35
35
 
36
- def initialize targets, methods, tracker, in_depth = false
36
+ def initialize targets, methods, tracker
37
37
  super tracker
38
38
  @calls = []
39
39
  @find_targets = targets
40
40
  @find_methods = methods
41
41
  @current_class = nil
42
42
  @current_method = nil
43
- @in_depth = in_depth
44
43
  end
45
44
 
46
45
  #Returns a list of results.
@@ -48,10 +47,6 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
48
47
  #A result looks like:
49
48
  #
50
49
  # s(:result, :ClassName, :method_name, s(:call, ...))
51
- #
52
- #or
53
- #
54
- # s(:result, :template_name, s(:call, ...))
55
50
  def matches
56
51
  @calls
57
52
  end
@@ -60,10 +55,7 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
60
55
  #or the template. These names are used when reporting results.
61
56
  #
62
57
  #Use FindCall#matches to retrieve results.
63
- def process_source exp, klass = nil, method = nil, template = nil
64
- @current_class = klass
65
- @current_method = method
66
- @current_template = template
58
+ def process_source exp
67
59
  process exp
68
60
  end
69
61
 
@@ -74,11 +66,6 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
74
66
 
75
67
  alias :process_defs :process_defn
76
68
 
77
- #Process body of block
78
- def process_rlist exp
79
- process_all exp
80
- end
81
-
82
69
  #Look for matching calls and add them to results
83
70
  def process_call exp
84
71
  target = get_target exp.target
@@ -87,25 +74,9 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
87
74
  process_call_args exp
88
75
 
89
76
  if match(@find_targets, target) and match(@find_methods, method)
90
-
91
- if @current_template
92
- @calls << Sexp.new(:result, @current_template, exp).line(exp.line)
93
- else
94
- @calls << Sexp.new(:result, @current_module, @current_class, @current_method, exp).line(exp.line)
95
- end
96
-
77
+ @calls << Sexp.new(:result, @current_module, @current_class, @current_method, exp).line(exp.line)
97
78
  end
98
79
 
99
- #Normally FindCall won't match a method invocation that is the target of
100
- #another call, such as:
101
- #
102
- # User.find(:first, :conditions => "user = '#{params['user']}').name
103
- #
104
- #A search for User.find will not match this unless @in_depth is true.
105
- if @in_depth and call? exp.target
106
- process exp.target
107
- end
108
-
109
80
  exp
110
81
  end
111
82
 
@@ -123,8 +94,6 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
123
94
  case exp.node_type
124
95
  when :ivar, :lvar, :const, :lit
125
96
  exp.value
126
- when :true, :false
127
- exp.node_type
128
97
  when :colon2
129
98
  class_name exp
130
99
  else
@@ -141,43 +110,13 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
141
110
  when Symbol
142
111
  if search_terms == item
143
112
  true
144
- elsif sexp? item
145
- is_instance_of? item, search_terms
146
113
  else
147
114
  false
148
115
  end
149
- when Sexp
150
- search_terms == item
151
116
  when Enumerable
152
117
  if search_terms.empty?
153
118
  item == nil
154
- else
155
- search_terms.each do|term|
156
- if match(term, item)
157
- return true
158
- end
159
- end
160
- false
161
119
  end
162
- when Regexp
163
- search_terms.match item.to_s
164
- when nil
165
- true
166
- else
167
- raise "Cannot match #{search_terms} and #{item}"
168
- end
169
- end
170
-
171
- #Checks if +item+ is an instance of +klass+ by looking for Klass.new
172
- def is_instance_of? item, klass
173
- if call? item
174
- if sexp? item.target
175
- item.method == :new and item.target.node_type == :const and item.target.value == klass
176
- else
177
- item.method == :new and item.target == klass
178
- end
179
- else
180
- false
181
120
  end
182
121
  end
183
122
  end
@@ -61,9 +61,9 @@ class Brakeman::TemplateProcessor < Brakeman::BaseProcessor
61
61
  branches = [arg.then_clause, arg.else_clause].compact
62
62
 
63
63
  if branches.empty?
64
- s(:nil)
64
+ s(:nil).line(arg.line)
65
65
  elsif branches.length == 2
66
- Sexp.new(:or, *branches)
66
+ Sexp.new(:or, *branches).line(arg.line)
67
67
  else
68
68
  branches.first
69
69
  end
@@ -77,9 +77,13 @@ class Brakeman::TemplateProcessor < Brakeman::BaseProcessor
77
77
  end
78
78
 
79
79
  def add_output output, type = :output
80
- s = Sexp.new(type, output)
81
- s.line(output.line)
82
- @current_template.add_output s
83
- s
80
+ if node_type? output, :or
81
+ Sexp.new(:or, add_output(output.lhs, type), add_output(output.rhs, type)).line(output.line)
82
+ else
83
+ s = Sexp.new(type, output)
84
+ s.line(output.line)
85
+ @current_template.add_output s
86
+ s
87
+ end
84
88
  end
85
89
  end
@@ -226,9 +226,13 @@ class Brakeman::Rescanner < Brakeman::Scanner
226
226
  end
227
227
 
228
228
  def rescan_initializer path
229
+ tracker.reset_initializer path
230
+
229
231
  parse_ruby_files([path]).each do |astfile|
230
232
  process_initializer astfile
231
233
  end
234
+
235
+ @reindex << :initializers
232
236
  end
233
237
 
234
238
  #Handle rescanning when a file is deleted
@@ -227,6 +227,10 @@ class Brakeman::Tracker
227
227
  finder.process_source template.src, :template => template, :file => template.file
228
228
  end
229
229
 
230
+ self.initializers.each do |file_name, src|
231
+ finder.process_all_source src, :file => file_name
232
+ end
233
+
230
234
  @call_index = Brakeman::CallIndex.new finder.calls
231
235
  end
232
236
 
@@ -237,8 +241,8 @@ class Brakeman::Tracker
237
241
  #
238
242
  #This will limit reindexing to the given sets
239
243
  def reindex_call_sites locations
240
- #If reindexing templates, models, and controllers, just redo
241
- #everything
244
+ #If reindexing templates, models, controllers,
245
+ #just redo everything.
242
246
  if locations.length == 3
243
247
  return index_call_sites
244
248
  end
@@ -260,6 +264,12 @@ class Brakeman::Tracker
260
264
  method_sets << self.controllers
261
265
  end
262
266
 
267
+ if locations.include? :initializers
268
+ self.initializers.each do |file_name, src|
269
+ @call_index.remove_indexes_by_file file_name
270
+ end
271
+ end
272
+
263
273
  @call_index.remove_indexes_by_class classes_to_reindex
264
274
 
265
275
  finder = Brakeman::FindAllCalls.new self
@@ -279,6 +289,12 @@ class Brakeman::Tracker
279
289
  end
280
290
  end
281
291
 
292
+ if locations.include? :initializers
293
+ self.initializers.each do |file_name, src|
294
+ finder.process_all_source src, :file => file_name
295
+ end
296
+ end
297
+
282
298
  @call_index.index_calls finder.calls
283
299
  end
284
300
 
@@ -363,4 +379,12 @@ class Brakeman::Tracker
363
379
  def reset_routes
364
380
  @routes = {}
365
381
  end
382
+
383
+ def reset_initializer path
384
+ @initializers.delete_if do |file, src|
385
+ path.relative.include? file
386
+ end
387
+
388
+ @call_index.remove_indexes_by_file path
389
+ end
366
390
  end
@@ -19,16 +19,9 @@ module Brakeman
19
19
  @ruby_version = ""
20
20
  end
21
21
 
22
- def allow_forgery_protection?
23
- @rails[:action_controller] and
24
- @rails[:action_controller][:allow_forgery_protection] == Sexp.new(:false)
25
- end
26
-
27
22
  def default_protect_from_forgery?
28
23
  if version_between? "5.2.0", "9.9.9"
29
- if @rails[:action_controller] and
30
- @rails[:action_controller][:default_protect_from_forgery] == Sexp.new(:false)
31
-
24
+ if @rails.dig(:action_controller, :default_protect_from_forgery) == Sexp.new(:false)
32
25
  return false
33
26
  else
34
27
  return true
@@ -48,17 +41,15 @@ module Brakeman
48
41
 
49
42
  def escape_html_entities_in_json?
50
43
  #TODO add version-specific information here
51
- @rails[:active_support] and
52
- true? @rails[:active_support][:escape_html_entities_in_json]
44
+ true? @rails.dig(:active_support, :escape_html_entities_in_json)
53
45
  end
54
46
 
55
47
  def whitelist_attributes?
56
- @rails[:active_record] and
57
- @rails[:active_record][:whitelist_attributes] == Sexp.new(:true)
48
+ @rails.dig(:active_record, :whitelist_attributes) == Sexp.new(:true)
58
49
  end
59
50
 
60
51
  def gem_version name
61
- @gems[name] and @gems[name][:version]
52
+ @gems.dig(name, :version)
62
53
  end
63
54
 
64
55
  def add_gem name, version, file, line
@@ -111,6 +102,8 @@ module Brakeman
111
102
  @escape_html = true
112
103
  Brakeman.notify "[Notice] Escaping HTML by default"
113
104
  end
105
+
106
+ check_haml_version
114
107
  end
115
108
 
116
109
  def set_ruby_version version
@@ -152,12 +145,26 @@ module Brakeman
152
145
  end
153
146
 
154
147
  def session_settings
155
- @rails[:action_controller] &&
156
- @rails[:action_controller][:session]
148
+ @rails.dig(:action_controller, :session)
157
149
  end
158
150
 
159
151
  private
160
152
 
153
+ # Brakeman does not support Haml 5 yet.
154
+ # (https://github.com/presidentbeef/brakeman/issues/1370) Fortunately, Haml
155
+ # 5 doesn't add much new syntax, and brakeman (which uses the haml 4 parser)
156
+ # will parse most Haml 5 files without errors. Therefore, we only print a
157
+ # warning here, instead of raising an error.
158
+ def check_haml_version
159
+ return if supported_haml_version?
160
+ Brakeman.notify <<~EOS
161
+ Brakeman does not fully support Haml 5 yet. Your Gemfile (or gemspec)
162
+ allows Haml 5. Brakeman uses the Haml 4 parser and thus may produce
163
+ errors when parsing Haml 5 syntax. If Brakeman encounters such an error,
164
+ it will be unable to scan that file.
165
+ EOS
166
+ end
167
+
161
168
  def convert_version_number value
162
169
  if value.match(/\A\d+\z/)
163
170
  value.to_i
@@ -174,6 +181,23 @@ module Brakeman
174
181
  end
175
182
  end
176
183
 
184
+ # Brakeman does not support Haml 5 yet. See `check_haml_version`.
185
+ #
186
+ # This method is messy because all we have is a version `Requirement` (like
187
+ # `~> 5.0.3`, or `> 4.99`) The only way I could think of was to loop over
188
+ # known Haml 5 versions and test whether the `Requirement` is satisfied. I
189
+ # included '5.99.99' in the list so that this method might stand a chance of
190
+ # working in the future.
191
+ def supported_haml_version?
192
+ haml_version = gem_version(:haml)
193
+ return true unless haml_version
194
+
195
+ requirement = Gem::Requirement.new(haml_version)
196
+ ['5.99.99', '5.1.1', '5.1.0', '5.0.4', '5.0.3', '5.0.2', '5.0.1', '5.0.0'].none? do |v|
197
+ requirement.satisfied_by?(Gem::Version.new(v))
198
+ end
199
+ end
200
+
177
201
  def higher? lhs, rhs
178
202
  if lhs.class == rhs.class
179
203
  lhs > rhs
@@ -49,7 +49,7 @@ module Brakeman
49
49
  include Brakeman::Util
50
50
 
51
51
  def initialize
52
- @constants = Hash.new { |h, k| h[k] = [] }
52
+ @constants = {}
53
53
  end
54
54
 
55
55
  def size
@@ -103,6 +103,7 @@ module Brakeman
103
103
  end
104
104
 
105
105
  base_name = Constants.get_constant_base_name(name)
106
+ @constants[base_name] ||= []
106
107
  @constants[base_name] << Constant.new(name, value, context)
107
108
  end
108
109
 
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "4.5.1"
2
+ Version = "4.6.0"
3
3
  end
@@ -271,6 +271,10 @@ class Brakeman::Warning
271
271
  end
272
272
  end
273
273
 
274
+ def relative_path
275
+ self.file.relative
276
+ end
277
+
274
278
  def to_hash absolute_paths: true
275
279
  if self.called_from and not absolute_paths
276
280
  render_path = self.called_from.with_relative_paths
@@ -111,6 +111,9 @@ module Brakeman::WarningCodes
111
111
  :CVE_2018_3741 => 107,
112
112
  :CVE_2018_3760 => 108,
113
113
  :force_ssl_disabled => 109,
114
+ :unsafe_cookie_serialization => 110,
115
+ :reverse_tabnabbing => 111,
116
+ :custom_check => 9090,
114
117
  }
115
118
 
116
119
  def self.code name
@@ -40,7 +40,7 @@ class Sexp
40
40
  s.line(line)
41
41
  else
42
42
  s.original_line = self.original_line
43
- s.line(self.line)
43
+ s.line(self.line) if self.line
44
44
  end
45
45
 
46
46
  s
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman-min
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.1
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
11
  - brakeman-public_cert.pem
12
- date: 2019-05-11 00:00:00.000000000 Z
12
+ date: 2019-07-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -144,6 +144,7 @@ files:
144
144
  - lib/brakeman/checks/check_basic_auth.rb
145
145
  - lib/brakeman/checks/check_basic_auth_timing_attack.rb
146
146
  - lib/brakeman/checks/check_content_tag.rb
147
+ - lib/brakeman/checks/check_cookie_serialization.rb
147
148
  - lib/brakeman/checks/check_create_with.rb
148
149
  - lib/brakeman/checks/check_cross_site_scripting.rb
149
150
  - lib/brakeman/checks/check_default_routes.rb
@@ -184,6 +185,7 @@ files:
184
185
  - lib/brakeman/checks/check_render_dos.rb
185
186
  - lib/brakeman/checks/check_render_inline.rb
186
187
  - lib/brakeman/checks/check_response_splitting.rb
188
+ - lib/brakeman/checks/check_reverse_tabnabbing.rb
187
189
  - lib/brakeman/checks/check_route_dos.rb
188
190
  - lib/brakeman/checks/check_safe_buffer_manipulation.rb
189
191
  - lib/brakeman/checks/check_sanitize_methods.rb
@@ -321,8 +323,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
321
323
  - !ruby/object:Gem::Version
322
324
  version: '0'
323
325
  requirements: []
324
- rubyforge_project:
325
- rubygems_version: 2.7.8
326
+ rubygems_version: 3.0.3
326
327
  signing_key:
327
328
  specification_version: 4
328
329
  summary: Security vulnerability scanner for Ruby on Rails.