brakeman-lib 4.5.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: 4335b21ba5c11b4a21f8f31f38f612f9153b7778d0c8c5fba9d1efbcfba6e16a
4
- data.tar.gz: 257d1b9f190b517d0b16859acc438870478d54a9ce71ed09f1e8ba01365095ac
3
+ metadata.gz: 9801d95660e71117c4d1578f6413c32063a5da4b3dbfa50ef351a4b6b1cc2b0a
4
+ data.tar.gz: d0fa01b7a2ae2cca03656cdd54197198f4284dec2ae1a38e12a3b0859aae249a
5
5
  SHA512:
6
- metadata.gz: 7055bf420077bac2ae9b4a5a86616d8aa2663efe81414b4688b22b1af6558b6f3aa563fe7292e5bf3b50a4159e770c5096ecc4d4b7e452953614809843baab58
7
- data.tar.gz: e5f3c8e3d5d66a652f1c6e0d6b1727db400b7db0d07a9f37a43df256da2cb71710a5e86f244cfe9994d309af6e77925623fa1496a930875693fa836c9a56a492
6
+ metadata.gz: 026dffce4f3530198f8dc2663455ff784d0155ff1b9a69daf2f60e9c2ca85839ea202c3f37d21f2942e70f129002d50073cb850ad023b687344b683c6256df89
7
+ data.tar.gz: 02f647f5fb135d6ff6dc446ac4dc2772fba090710fee36597329647e7d179f5ef407c7825105940063de8bb8dd07afcc73033bd90fc452f028d43cfff4daa773
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-lib
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
@@ -226,6 +226,7 @@ files:
226
226
  - lib/brakeman/checks/check_basic_auth.rb
227
227
  - lib/brakeman/checks/check_basic_auth_timing_attack.rb
228
228
  - lib/brakeman/checks/check_content_tag.rb
229
+ - lib/brakeman/checks/check_cookie_serialization.rb
229
230
  - lib/brakeman/checks/check_create_with.rb
230
231
  - lib/brakeman/checks/check_cross_site_scripting.rb
231
232
  - lib/brakeman/checks/check_default_routes.rb
@@ -266,6 +267,7 @@ files:
266
267
  - lib/brakeman/checks/check_render_dos.rb
267
268
  - lib/brakeman/checks/check_render_inline.rb
268
269
  - lib/brakeman/checks/check_response_splitting.rb
270
+ - lib/brakeman/checks/check_reverse_tabnabbing.rb
269
271
  - lib/brakeman/checks/check_route_dos.rb
270
272
  - lib/brakeman/checks/check_safe_buffer_manipulation.rb
271
273
  - lib/brakeman/checks/check_sanitize_methods.rb
@@ -403,8 +405,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
403
405
  - !ruby/object:Gem::Version
404
406
  version: '0'
405
407
  requirements: []
406
- rubyforge_project:
407
- rubygems_version: 2.7.8
408
+ rubygems_version: 3.0.3
408
409
  signing_key:
409
410
  specification_version: 4
410
411
  summary: Security vulnerability scanner for Ruby on Rails.