brakeman-lib 4.5.1 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +158 -109
  3. data/README.md +1 -2
  4. data/lib/brakeman/call_index.rb +54 -15
  5. data/lib/brakeman/checks/base_check.rb +50 -47
  6. data/lib/brakeman/checks/check_cookie_serialization.rb +22 -0
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +4 -4
  8. data/lib/brakeman/checks/check_deserialize.rb +3 -6
  9. data/lib/brakeman/checks/check_execute.rb +26 -1
  10. data/lib/brakeman/checks/check_file_access.rb +7 -1
  11. data/lib/brakeman/checks/check_header_dos.rb +2 -2
  12. data/lib/brakeman/checks/check_i18n_xss.rb +2 -2
  13. data/lib/brakeman/checks/check_jruby_xml.rb +2 -2
  14. data/lib/brakeman/checks/check_json_parsing.rb +2 -2
  15. data/lib/brakeman/checks/check_mass_assignment.rb +1 -1
  16. data/lib/brakeman/checks/check_mime_type_dos.rb +2 -2
  17. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +1 -1
  18. data/lib/brakeman/checks/check_reverse_tabnabbing.rb +58 -0
  19. data/lib/brakeman/checks/check_sanitize_methods.rb +2 -2
  20. data/lib/brakeman/checks/check_session_settings.rb +5 -2
  21. data/lib/brakeman/checks/check_sql.rb +24 -22
  22. data/lib/brakeman/checks/check_xml_dos.rb +2 -2
  23. data/lib/brakeman/checks/check_yaml_parsing.rb +10 -18
  24. data/lib/brakeman/differ.rb +16 -28
  25. data/lib/brakeman/file_parser.rb +4 -8
  26. data/lib/brakeman/file_path.rb +14 -0
  27. data/lib/brakeman/parsers/haml_embedded.rb +1 -1
  28. data/lib/brakeman/parsers/template_parser.rb +3 -1
  29. data/lib/brakeman/processor.rb +2 -2
  30. data/lib/brakeman/processors/alias_processor.rb +15 -1
  31. data/lib/brakeman/processors/base_processor.rb +2 -0
  32. data/lib/brakeman/processors/controller_processor.rb +4 -4
  33. data/lib/brakeman/processors/gem_processor.rb +10 -2
  34. data/lib/brakeman/processors/haml_template_processor.rb +87 -123
  35. data/lib/brakeman/processors/lib/call_conversion_helper.rb +5 -4
  36. data/lib/brakeman/processors/lib/find_all_calls.rb +27 -4
  37. data/lib/brakeman/processors/lib/find_call.rb +3 -64
  38. data/lib/brakeman/processors/lib/rails2_config_processor.rb +1 -1
  39. data/lib/brakeman/processors/template_alias_processor.rb +28 -0
  40. data/lib/brakeman/processors/template_processor.rb +10 -6
  41. data/lib/brakeman/report/report_text.rb +4 -5
  42. data/lib/brakeman/rescanner.rb +4 -0
  43. data/lib/brakeman/tracker.rb +26 -2
  44. data/lib/brakeman/tracker/config.rb +38 -73
  45. data/lib/brakeman/tracker/constants.rb +2 -1
  46. data/lib/brakeman/util.rb +5 -3
  47. data/lib/brakeman/version.rb +1 -1
  48. data/lib/brakeman/warning.rb +4 -0
  49. data/lib/brakeman/warning_codes.rb +3 -0
  50. data/lib/ruby_parser/bm_sexp.rb +7 -2
  51. metadata +18 -17
@@ -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
@@ -75,7 +75,7 @@ class Brakeman::Rails2ConfigProcessor < Brakeman::BasicProcessor
75
75
  def process_cdecl exp
76
76
  #Set Rails version required
77
77
  if exp.lhs == :RAILS_GEM_VERSION
78
- @tracker.config.rails_version = exp.rhs.value
78
+ @tracker.config.set_rails_version exp.rhs.value
79
79
  end
80
80
 
81
81
  exp
@@ -32,6 +32,34 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
32
32
  end
33
33
  end
34
34
 
35
+ def process_lasgn exp
36
+ if exp.lhs == :haml_temp or haml_capture? exp.rhs
37
+ exp.rhs = process exp.rhs
38
+
39
+ # Avoid propagating contents of block
40
+ if node_type? exp.rhs, :iter
41
+ new_exp = exp.dup
42
+ new_exp.rhs = exp.rhs.block_call
43
+
44
+ super new_exp
45
+
46
+ exp # Still save the original, though
47
+ else
48
+ super exp
49
+ end
50
+ else
51
+ super exp
52
+ end
53
+ end
54
+
55
+ HAML_CAPTURE = [:capture, :capture_haml]
56
+
57
+ def haml_capture? exp
58
+ node_type? exp, :iter and
59
+ call? exp.block_call and
60
+ HAML_CAPTURE.include? exp.block_call.method
61
+ end
62
+
35
63
  #Determine template name
36
64
  def template_name name
37
65
  if !name.to_s.include?('/') && @template.name.to_s.include?('/')
@@ -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
@@ -19,7 +19,7 @@ class Brakeman::Report::Text < Brakeman::Report::Base
19
19
  add_chunk generate_controllers if tracker.options[:debug] or tracker.options[:report_routes]
20
20
  add_chunk generate_templates if tracker.options[:debug]
21
21
  add_chunk generate_obsolete
22
- add_chunk generate_errors
22
+ add_chunk generate_errors
23
23
  add_chunk generate_warnings
24
24
  end
25
25
 
@@ -51,7 +51,7 @@ class Brakeman::Report::Text < Brakeman::Report::Base
51
51
 
52
52
  def generate_header
53
53
  [
54
- header("Brakeman Report"),
54
+ header("Brakeman Report"),
55
55
  label("Application Path", tracker.app_path),
56
56
  label("Rails Version", rails_version),
57
57
  label("Brakeman Version", Brakeman::Version),
@@ -92,7 +92,7 @@ class Brakeman::Report::Text < Brakeman::Report::Base
92
92
  HighLine.color("No warnings found", :bold, :green)
93
93
  else
94
94
  warnings = tracker.filtered_warnings.sort_by do |w|
95
- [w.confidence, w.warning_type, w.fingerprint]
95
+ [w.confidence, w.warning_type, w.file, w.line, w.fingerprint]
96
96
  end.map do |w|
97
97
  output_warning w
98
98
  end
@@ -140,7 +140,7 @@ class Brakeman::Report::Text < Brakeman::Report::Base
140
140
  end
141
141
 
142
142
  double_space "Template Output", template_rows.sort_by { |name, value| name.to_s }.map { |template|
143
- [HighLine.new.color(template.first.to_s << "\n", :cyan)] + template[1]
143
+ [HighLine.new.color("#{template.first}\n", :cyan)] + template[1]
144
144
  }.compact
145
145
  end
146
146
 
@@ -211,4 +211,3 @@ class Brakeman::Report::Text < Brakeman::Report::Base
211
211
  double_space "Controller Overview", controllers
212
212
  end
213
213
  end
214
-
@@ -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
@@ -4,10 +4,8 @@ module Brakeman
4
4
  class Config
5
5
  include Util
6
6
 
7
- attr_reader :rails, :tracker
8
- attr_accessor :rails_version, :ruby_version
7
+ attr_reader :gems, :rails, :ruby_version, :tracker
9
8
  attr_writer :erubis, :escape_html
10
- attr_reader :gems
11
9
 
12
10
  def initialize tracker
13
11
  @tracker = tracker
@@ -19,16 +17,9 @@ module Brakeman
19
17
  @ruby_version = ""
20
18
  end
21
19
 
22
- def allow_forgery_protection?
23
- @rails[:action_controller] and
24
- @rails[:action_controller][:allow_forgery_protection] == Sexp.new(:false)
25
- end
26
-
27
20
  def default_protect_from_forgery?
28
- 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
-
21
+ if version_between? "5.2.0.beta1", "9.9.9"
22
+ if @rails.dig(:action_controller, :default_protect_from_forgery) == Sexp.new(:false)
32
23
  return false
33
24
  else
34
25
  return true
@@ -48,17 +39,21 @@ module Brakeman
48
39
 
49
40
  def escape_html_entities_in_json?
50
41
  #TODO add version-specific information here
51
- @rails[:active_support] and
52
- true? @rails[:active_support][:escape_html_entities_in_json]
42
+ true? @rails.dig(:active_support, :escape_html_entities_in_json)
43
+ end
44
+
45
+ def escape_filter_interpolations?
46
+ # TODO see if app is actually turning this off itself
47
+ has_gem?(:haml) and
48
+ version_between? "5.0.0", "5.99", gem_version(:haml)
53
49
  end
54
50
 
55
51
  def whitelist_attributes?
56
- @rails[:active_record] and
57
- @rails[:active_record][:whitelist_attributes] == Sexp.new(:true)
52
+ @rails.dig(:active_record, :whitelist_attributes) == Sexp.new(:true)
58
53
  end
59
54
 
60
55
  def gem_version name
61
- @gems[name] and @gems[name][:version]
56
+ extract_version @gems.dig(name, :version)
62
57
  end
63
58
 
64
59
  def add_gem name, version, file, line
@@ -78,11 +73,16 @@ module Brakeman
78
73
  @gems[name]
79
74
  end
80
75
 
81
- def set_rails_version
82
- # Ignore ~>, etc. when using values from Gemfile
83
- version = gem_version(:rails) || gem_version(:railties)
84
- if version and version.match(/(\d+\.\d+(\.\d+.*)?)/)
85
- @rails_version = $1
76
+ def set_rails_version version = nil
77
+ version = if version
78
+ # Only used by Rails2ConfigProcessor right now
79
+ extract_version(version)
80
+ else
81
+ gem_version(:rails) || gem_version(:railties)
82
+ end
83
+
84
+ if version
85
+ @rails_version = version
86
86
 
87
87
  if tracker.options[:rails3].nil? and tracker.options[:rails4].nil?
88
88
  if @rails_version.start_with? "3"
@@ -113,12 +113,20 @@ module Brakeman
113
113
  end
114
114
  end
115
115
 
116
+ def rails_version
117
+ # This needs to be here because Util#rails_version calls Tracker::Config#rails_version
118
+ # but Tracker::Config includes Util...
119
+ @rails_version
120
+ end
121
+
116
122
  def set_ruby_version version
123
+ @ruby_version = extract_version(version)
124
+ end
125
+
126
+ def extract_version version
117
127
  return unless version.is_a? String
118
128
 
119
- if version =~ /(\d+\.\d+\.\d+)/
120
- self.ruby_version = $1
121
- end
129
+ version[/\d+\.\d+(\.\d+.*)?/]
122
130
  end
123
131
 
124
132
  #Returns true if low_version <= RAILS_VERSION <= high_version
@@ -128,58 +136,15 @@ module Brakeman
128
136
  current_version ||= rails_version
129
137
  return false unless current_version
130
138
 
131
- version = current_version.split(".").map! { |v| convert_version_number v }
132
- low_version = low_version.split(".").map! { |v| convert_version_number v }
133
- high_version = high_version.split(".").map! { |v| convert_version_number v }
134
-
135
- version.each_with_index do |v, i|
136
- if lower? v, low_version.fetch(i, 0)
137
- return false
138
- elsif higher? v, low_version.fetch(i, 0)
139
- break
140
- end
141
- end
139
+ low = Gem::Version.new(low_version)
140
+ high = Gem::Version.new(high_version)
141
+ current = Gem::Version.new(current_version)
142
142
 
143
- version.each_with_index do |v, i|
144
- if higher? v, high_version.fetch(i, 0)
145
- return false
146
- elsif lower? v, high_version.fetch(i, 0)
147
- break
148
- end
149
- end
150
-
151
- true
143
+ current.between?(low, high)
152
144
  end
153
145
 
154
146
  def session_settings
155
- @rails[:action_controller] &&
156
- @rails[:action_controller][:session]
157
- end
158
-
159
- private
160
-
161
- def convert_version_number value
162
- if value.match(/\A\d+\z/)
163
- value.to_i
164
- else
165
- value
166
- end
167
- end
168
-
169
- def lower? lhs, rhs
170
- if lhs.class == rhs.class
171
- lhs < rhs
172
- else
173
- false
174
- end
175
- end
176
-
177
- def higher? lhs, rhs
178
- if lhs.class == rhs.class
179
- lhs > rhs
180
- else
181
- false
182
- end
147
+ @rails.dig(:action_controller, :session)
183
148
  end
184
149
  end
185
150
  end