brakeman 2.5.0 → 2.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 (37) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGES +14 -0
  5. data/README.md +6 -28
  6. data/lib/brakeman/checks/base_check.rb +5 -4
  7. data/lib/brakeman/checks/check_basic_auth.rb +1 -2
  8. data/lib/brakeman/checks/check_default_routes.rb +65 -15
  9. data/lib/brakeman/checks/check_detailed_exceptions.rb +5 -4
  10. data/lib/brakeman/checks/check_filter_skipping.rb +1 -1
  11. data/lib/brakeman/checks/check_forgery_setting.rb +9 -9
  12. data/lib/brakeman/checks/check_model_attr_accessible.rb +1 -1
  13. data/lib/brakeman/checks/check_model_attributes.rb +3 -3
  14. data/lib/brakeman/checks/check_model_serialize.rb +1 -1
  15. data/lib/brakeman/checks/check_redirect.rb +27 -6
  16. data/lib/brakeman/checks/check_render.rb +2 -2
  17. data/lib/brakeman/checks/check_skip_before_filter.rb +2 -2
  18. data/lib/brakeman/checks/check_sql.rb +2 -1
  19. data/lib/brakeman/file_parser.rb +49 -0
  20. data/lib/brakeman/options.rb +1 -1
  21. data/lib/brakeman/parsers/template_parser.rb +88 -0
  22. data/lib/brakeman/processors/alias_processor.rb +25 -2
  23. data/lib/brakeman/processors/controller_alias_processor.rb +3 -3
  24. data/lib/brakeman/processors/controller_processor.rb +106 -54
  25. data/lib/brakeman/processors/lib/rails3_route_processor.rb +27 -12
  26. data/lib/brakeman/processors/lib/route_helper.rb +1 -1
  27. data/lib/brakeman/processors/library_processor.rb +37 -28
  28. data/lib/brakeman/processors/model_processor.rb +117 -34
  29. data/lib/brakeman/report/report_base.rb +1 -1
  30. data/lib/brakeman/rescanner.rb +84 -35
  31. data/lib/brakeman/scanner.rb +84 -148
  32. data/lib/brakeman/tracker.rb +32 -12
  33. data/lib/brakeman/util.rb +13 -4
  34. data/lib/brakeman/version.rb +1 -1
  35. data/lib/brakeman/warning_codes.rb +2 -1
  36. metadata +6 -4
  37. metadata.gz.sig +0 -0
@@ -31,7 +31,7 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
31
31
  :message => "Use whitelist (:only => [..]) when skipping CSRF check",
32
32
  :code => filter,
33
33
  :confidence => CONFIDENCE[:med],
34
- :file => controller[:file]
34
+ :file => controller[:files].first
35
35
 
36
36
  when :login_required, :authenticate_user!, :require_user
37
37
  warn :controller => controller[:name],
@@ -41,7 +41,7 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
41
41
  :code => filter,
42
42
  :confidence => CONFIDENCE[:med],
43
43
  :link => "authentication_whitelist",
44
- :file => controller[:file]
44
+ :file => controller[:files].first
45
45
  end
46
46
  end
47
47
 
@@ -19,6 +19,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
19
19
  @sql_targets = [:all, :average, :calculate, :count, :count_by_sql, :exists?, :delete_all, :destroy_all,
20
20
  :find, :find_by_sql, :first, :last, :maximum, :minimum, :pluck, :sum, :update_all]
21
21
  @sql_targets.concat [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where] if tracker.options[:rails3]
22
+ @sql_targets << :find_by << :find_by! if version_between? "4.0.0", "9.9.9"
22
23
 
23
24
  @connection_calls = [:delete, :execute, :insert, :select_all, :select_one,
24
25
  :select_rows, :select_value, :select_values]
@@ -172,7 +173,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
172
173
  else
173
174
  check_find_arguments call.last_arg
174
175
  end
175
- when :where, :having
176
+ when :where, :having, :find_by, :find_by!
176
177
  check_query_arguments call.arglist
177
178
  when :order, :group, :reorder
178
179
  check_order_arguments call.arglist
@@ -0,0 +1,49 @@
1
+ module Brakeman
2
+ ASTFile = Struct.new(:path, :ast)
3
+
4
+ # This class handles reading and parsing files.
5
+ class FileParser
6
+ attr_reader :file_list
7
+
8
+ def initialize tracker, app_tree
9
+ @tracker = tracker
10
+ @app_tree = app_tree
11
+ @file_list = {}
12
+ end
13
+
14
+ def parse_files list, type
15
+ read_files list, type do |path, contents|
16
+ if ast = parse_ruby(contents, path)
17
+ ASTFile.new(path, ast)
18
+ end
19
+ end
20
+ end
21
+
22
+ def read_files list, type
23
+ @file_list[type] ||= []
24
+
25
+ list.each do |path|
26
+ result = yield path, read_path(path)
27
+ if result
28
+ @file_list[type] << result
29
+ end
30
+ end
31
+ end
32
+
33
+ def parse_ruby input, path
34
+ begin
35
+ RubyParser.new.parse input, path
36
+ rescue Racc::ParseError => e
37
+ @tracker.error e, "Could not parse #{path}"
38
+ nil
39
+ rescue => e
40
+ @tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
41
+ nil
42
+ end
43
+ end
44
+
45
+ def read_path path
46
+ @app_tree.read_path path
47
+ end
48
+ end
49
+ end
@@ -87,7 +87,7 @@ module Brakeman::Options
87
87
  options[:check_arguments] = !option
88
88
  end
89
89
 
90
- opts.on "-s", "--safe-methods meth1,meth2,etc", Array, "Consider the specified methods safe" do |methods|
90
+ opts.on "-s", "--safe-methods meth1,meth2,etc", Array, "Set methods as safe for unescaped output in views" do |methods|
91
91
  options[:safe_methods] ||= Set.new
92
92
  options[:safe_methods].merge methods.map {|e| e.to_sym }
93
93
  end
@@ -0,0 +1,88 @@
1
+ module Brakeman
2
+ class TemplateParser
3
+ include Brakeman::Util
4
+ attr_reader :tracker
5
+ KNOWN_TEMPLATE_EXTENSIONS = /.*\.(erb|haml|rhtml|slim)$/
6
+
7
+ TemplateFile = Struct.new(:path, :ast, :name, :type)
8
+
9
+ def initialize tracker, file_parser
10
+ @tracker = tracker
11
+ @file_parser = file_parser
12
+ @file_parser.file_list[:templates] ||= []
13
+ end
14
+
15
+ def parse_template path, text
16
+ type = path.match(KNOWN_TEMPLATE_EXTENSIONS)[1].to_sym
17
+ type = :erb if type == :rhtml
18
+ name = template_path_to_name path
19
+
20
+ begin
21
+ src = case type
22
+ when :erb
23
+ type = :erubis if erubis?
24
+ parse_erb text
25
+ when :haml
26
+ parse_haml text
27
+ when :slim
28
+ parse_slim text
29
+ else
30
+ tracker.error "Unkown template type in #{path}"
31
+ nil
32
+ end
33
+
34
+ if src and ast = @file_parser.parse_ruby(src, path)
35
+ @file_parser.file_list[:templates] << TemplateFile.new(path, ast, name, type)
36
+ end
37
+ rescue Racc::ParseError => e
38
+ tracker.error e, "could not parse #{path}"
39
+ rescue Haml::Error => e
40
+ tracker.error e, ["While compiling HAML in #{path}"] << e.backtrace
41
+ rescue StandardError, LoadError => e
42
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ def parse_erb text
49
+ if tracker.config[:escape_html]
50
+ if tracker.options[:rails3]
51
+ require 'brakeman/parsers/rails3_erubis'
52
+ Brakeman::Rails3Erubis.new(text).src
53
+ else
54
+ require 'brakeman/parsers/rails2_xss_plugin_erubis'
55
+ Brakeman::Rails2XSSPluginErubis.new(text).src
56
+ end
57
+ elsif tracker.config[:erubis]
58
+ require 'brakeman/parsers/rails2_erubis'
59
+ Brakeman::ScannerErubis.new(text).src
60
+ else
61
+ require 'erb'
62
+ src = ERB.new(text, nil, "-").src
63
+ src.sub!(/^#.*\n/, '') if Brakeman::Scanner::RUBY_1_9
64
+ src
65
+ end
66
+ end
67
+
68
+ def erubis?
69
+ tracker.config[:escape_html] or
70
+ tracker.config[:erubis]
71
+ end
72
+
73
+ def parse_haml text
74
+ Brakeman.load_brakeman_dependency 'haml'
75
+ Brakeman.load_brakeman_dependency 'sass'
76
+
77
+ Haml::Engine.new(text,
78
+ :escape_html => !!tracker.config[:escape_html]).precompiled
79
+ end
80
+
81
+ def parse_slim text
82
+ Brakeman.load_brakeman_dependency 'slim'
83
+
84
+ Slim::Template.new(:disable_capture => true,
85
+ :generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
86
+ end
87
+ end
88
+ end
@@ -257,12 +257,18 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
257
257
  #Local assignment
258
258
  # x = 1
259
259
  def process_lasgn exp
260
+ self_assign = self_assign?(exp.lhs, exp.rhs)
260
261
  exp.rhs = process exp.rhs if sexp? exp.rhs
261
262
  return exp if exp.rhs.nil?
262
263
 
263
264
  local = Sexp.new(:lvar, exp.lhs).line(exp.line || -2)
264
265
 
265
- set_value local, exp.rhs
266
+ if self_assign
267
+ # Skip branching
268
+ env[local] = exp.rhs
269
+ else
270
+ set_value local, exp.rhs
271
+ end
266
272
 
267
273
  exp
268
274
  end
@@ -270,10 +276,19 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
270
276
  #Instance variable assignment
271
277
  # @x = 1
272
278
  def process_iasgn exp
279
+ self_assign = self_assign?(exp.lhs, exp.rhs)
273
280
  exp.rhs = process exp.rhs
274
281
  ivar = Sexp.new(:ivar, exp.lhs).line(exp.line)
275
282
 
276
- set_value ivar, exp.rhs
283
+ if self_assign
284
+ if env[ivar].nil? and @meth_env
285
+ @meth_env[ivar] = exp.rhs
286
+ else
287
+ env[ivar] = exp.rhs
288
+ end
289
+ else
290
+ set_value ivar, exp.rhs
291
+ end
277
292
 
278
293
  exp
279
294
  end
@@ -727,6 +742,14 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
727
742
  end
728
743
  end
729
744
 
745
+ #Return true if for x += blah or @x += blah
746
+ def self_assign? var, value
747
+ call? value and
748
+ value.method == :+ and
749
+ node_type? value.target, :lvar, :ivar and
750
+ value.target.value == var
751
+ end
752
+
730
753
  def value_from_if exp
731
754
  if block? exp.else_clause or block? exp.then_clause
732
755
  #If either clause is more than a single expression, just use entire
@@ -48,7 +48,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
48
48
  #Need to process the method like it was in a controller in order
49
49
  #to get the renders set
50
50
  processor = Brakeman::ControllerProcessor.new(@app_tree, @tracker)
51
- method = mixin[:public][name].deep_clone
51
+ method = mixin[:public][name][:src].deep_clone
52
52
 
53
53
  if node_type? method, :methdef
54
54
  method = processor.process_defn method
@@ -206,7 +206,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
206
206
  true
207
207
  else
208
208
  routes = @tracker.routes[@current_class]
209
- routes and (routes == :allow_all_actions or routes.include? method)
209
+ routes and (routes.include? :allow_all_actions or routes.include? method)
210
210
  end
211
211
  end
212
212
 
@@ -323,7 +323,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
323
323
 
324
324
  @method_cache[method_name] = find_method method_name, controller[:parent]
325
325
  else
326
- @method_cache[method_name] = { :controller => controller[:name], :method => method }
326
+ @method_cache[method_name] = { :controller => controller[:name], :method => method[:src] }
327
327
  end
328
328
  else
329
329
  nil
@@ -7,7 +7,7 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
7
7
  def initialize app_tree, tracker
8
8
  super(tracker)
9
9
  @app_tree = app_tree
10
- @controller = nil
10
+ @current_class = nil
11
11
  @current_method = nil
12
12
  @current_module = nil
13
13
  @visibility = :public
@@ -28,7 +28,7 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
28
28
  #If inside a real controller, treat any other classes as libraries.
29
29
  #But if not inside a controller already, then the class may include
30
30
  #a real controller, so we can't take this shortcut.
31
- if @controller and @controller[:name].to_s.end_with? "Controller"
31
+ if @current_class and @current_class[:name].to_s.end_with? "Controller"
32
32
  Brakeman.debug "[Notice] Treating inner class as library: #{name}"
33
33
  Brakeman::LibraryProcessor.new(@tracker).process_library exp, @file_name
34
34
  return exp
@@ -36,57 +36,98 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
36
36
 
37
37
  if not name.to_s.end_with? "Controller"
38
38
  Brakeman.debug "[Notice] Adding noncontroller as library: #{name}"
39
-
40
- current_controller = @controller
41
-
42
39
  #Set the class to be a module in order to get the right namespacing.
43
40
  #Add class to libraries, in case it is needed later (e.g. it's used
44
41
  #as a parent class for a controller.)
45
42
  #However, still want to process it in this class, so have to set
46
- #@controller to this not-really-a-controller thing.
47
- process_module exp do
48
- name = @current_module
49
-
50
- if @tracker.libs[name.to_sym]
51
- @controller = @tracker.libs[name]
52
- else
53
- set_controller name, parent, exp
54
- @tracker.libs[name.to_sym] = @controller
55
- end
56
-
57
- process_all exp.body
58
- end
59
-
60
- @controller = current_controller
43
+ #@current_class to this not-really-a-controller thing.
44
+ process_module exp, parent
61
45
 
62
46
  return exp
63
47
  end
64
48
 
65
- if @current_module
66
- name = (@current_module.to_s + "::" + name.to_s).to_sym
49
+ if @current_class
50
+ outer_class = @current_class
51
+ name = (outer_class[:name].to_s + "::" + name.to_s).to_sym
67
52
  end
68
53
 
69
- set_controller name, parent, exp
54
+ if @current_module
55
+ name = (@current_module[:name].to_s + "::" + name.to_s).to_sym
56
+ end
70
57
 
71
- @tracker.controllers[@controller[:name]] = @controller
58
+ if @tracker.controllers[name]
59
+ @current_class = @tracker.controllers[name]
60
+ @current_class[:files] << @file_name unless @current_class[:files].include? @file_name
61
+ @current_class[:src][@file_name] = exp
62
+ else
63
+ @current_class = {
64
+ :name => name,
65
+ :parent => parent,
66
+ :includes => [],
67
+ :public => {},
68
+ :private => {},
69
+ :protected => {},
70
+ :options => {:before_filters => []},
71
+ :src => { @file_name => exp },
72
+ :files => [ @file_name ]
73
+ }
74
+
75
+ @tracker.controllers[name] = @current_class
76
+ end
72
77
 
73
78
  exp.body = process_all! exp.body
74
79
  set_layout_name
75
80
 
76
- @controller = nil
81
+ if outer_class
82
+ @current_class = outer_class
83
+ else
84
+ @current_class = nil
85
+ end
86
+
77
87
  exp
78
88
  end
79
89
 
80
- def set_controller name, parent, exp
81
- @controller = { :name => name,
82
- :parent => parent,
83
- :includes => [],
84
- :public => {},
85
- :private => {},
86
- :protected => {},
87
- :options => {:before_filters => []},
88
- :src => exp,
89
- :file => @file_name }
90
+ def process_module exp, parent = nil
91
+ name = class_name(exp.module_name)
92
+
93
+ if @current_module
94
+ outer_module = @current_module
95
+ name = (outer_module[:name].to_s + "::" + name.to_s).to_sym
96
+ end
97
+
98
+ if @current_class
99
+ name = (@current_class[:name].to_s + "::" + name.to_s).to_sym
100
+ end
101
+
102
+ if @tracker.libs[name]
103
+ @current_module = @tracker.libs[name]
104
+ @current_module[:files] << @file_name unless @current_module[:files].include? @file_name
105
+ @current_module[:src][@file_name] = exp
106
+ else
107
+ @current_module = {
108
+ :name => name,
109
+ :parent => parent,
110
+ :includes => [],
111
+ :public => {},
112
+ :private => {},
113
+ :protected => {},
114
+ :options => {:before_filters => []},
115
+ :src => { @file_name => exp },
116
+ :files => [ @file_name ]
117
+ }
118
+
119
+ @tracker.libs[name] = @current_module
120
+ end
121
+
122
+ exp.body = process_all! exp.body
123
+
124
+ if outer_module
125
+ @current_module = outer_module
126
+ else
127
+ @current_module = nil
128
+ end
129
+
130
+ exp
90
131
  end
91
132
 
92
133
  #Look for specific calls inside the controller
@@ -102,41 +143,41 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
102
143
 
103
144
  #Methods called inside class definition
104
145
  #like attr_* and other settings
105
- if @current_method.nil? and target.nil? and @controller
146
+ if @current_method.nil? and target.nil? and @current_class
106
147
  if first_arg.nil? #No args
107
148
  case method
108
149
  when :private, :protected, :public
109
150
  @visibility = method
110
151
  when :protect_from_forgery
111
- @controller[:options][:protect_from_forgery] = true
152
+ @current_class[:options][:protect_from_forgery] = true
112
153
  else
113
154
  #??
114
155
  end
115
156
  else
116
157
  case method
117
158
  when :include
118
- @controller[:includes] << class_name(first_arg) if @controller
159
+ @current_class[:includes] << class_name(first_arg) if @current_class
119
160
  when :before_filter, :append_before_filter, :before_action, :append_before_action
120
- @controller[:options][:before_filters] << exp.args
161
+ @current_class[:options][:before_filters] << exp.args
121
162
  when :prepend_before_filter, :prepend_before_action
122
- @controller[:options][:before_filters].unshift exp.args
163
+ @current_class[:options][:before_filters].unshift exp.args
123
164
  when :layout
124
165
  if string? last_arg
125
166
  #layout "some_layout"
126
167
 
127
168
  name = last_arg.value.to_s
128
169
  if @app_tree.layout_exists?(name)
129
- @controller[:layout] = "layouts/#{name}"
170
+ @current_class[:layout] = "layouts/#{name}"
130
171
  else
131
172
  Brakeman.debug "[Notice] Layout not found: #{name}"
132
173
  end
133
174
  elsif node_type? last_arg, :nil, :false
134
175
  #layout :false or layout nil
135
- @controller[:layout] = false
176
+ @current_class[:layout] = false
136
177
  end
137
178
  else
138
- @controller[:options][method] ||= []
139
- @controller[:options][method] << exp
179
+ @current_class[:options][method] ||= []
180
+ @current_class[:options][method] << exp
140
181
  end
141
182
  end
142
183
 
@@ -165,7 +206,13 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
165
206
  res = Sexp.new :methdef, name, exp.formal_args, *process_all!(exp.body)
166
207
  res.line(exp.line)
167
208
  @current_method = nil
168
- @controller[@visibility][name] = res unless @controller.nil?
209
+
210
+ if @current_class
211
+ @current_class[@visibility][name] = { :src => res, :file => @file_name }
212
+ elsif @current_module
213
+ @current_module[@visibility][name] = { :src => res, :file => @file_name }
214
+ end
215
+
169
216
  res
170
217
  end
171
218
 
@@ -174,8 +221,8 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
174
221
  name = exp.method_name
175
222
 
176
223
  if exp[1].node_type == :self
177
- if @controller
178
- target = @controller[:name]
224
+ if @current_class
225
+ target = @current_class[:name]
179
226
  elsif @current_module
180
227
  target = @current_module
181
228
  else
@@ -189,7 +236,12 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
189
236
  res = Sexp.new :selfdef, target, name, exp.formal_args, *process_all!(exp.body)
190
237
  res.line(exp.line)
191
238
  @current_method = nil
192
- @controller[@visibility][name] = res unless @controller.nil?
239
+
240
+ if @current_class
241
+ @current_class[@visibility][name] = { :src => res, :file => @file_name }
242
+ elsif @current_module
243
+ @current_module[@visibility][name] = { :src => res, :file => @file_name }
244
+ end
193
245
 
194
246
  res
195
247
  end
@@ -206,13 +258,13 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
206
258
 
207
259
  #Sets default layout for renders inside Controller
208
260
  def set_layout_name
209
- return if @controller[:layout]
261
+ return if @current_class[:layout]
210
262
 
211
- name = underscore(@controller[:name].to_s.split("::")[-1].gsub("Controller", ''))
263
+ name = underscore(@current_class[:name].to_s.split("::")[-1].gsub("Controller", ''))
212
264
 
213
265
  #There is a layout for this Controller
214
266
  if @app_tree.layout_exists?(name)
215
- @controller[:layout] = "layouts/#{name}"
267
+ @current_class[:layout] = "layouts/#{name}"
216
268
  end
217
269
  end
218
270
 
@@ -221,7 +273,7 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
221
273
  #We build a new method and process that the same way as usual
222
274
  #methods and filters.
223
275
  def add_fake_filter exp
224
- unless @controller
276
+ unless @current_class
225
277
  Brakeman.debug "Skipping before_filter outside controller: #{exp}"
226
278
  return exp
227
279
  end
@@ -245,8 +297,8 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
245
297
 
246
298
  #Build Sexp for filter method
247
299
  body = Sexp.new(:lasgn,
248
- block_variable,
249
- Sexp.new(:call, Sexp.new(:const, @controller[:name]), :new))
300
+ block_variable,
301
+ Sexp.new(:call, Sexp.new(:const, @current_class[:name]), :new))
250
302
 
251
303
  filter_method = Sexp.new(:defn, filter_name, Sexp.new(:args), body).concat(block_inner).line(exp.line)
252
304