brakeman 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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