brakeman 1.6.0 → 1.6.1

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.
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ ![Brakeman Logo](http://brakemanscanner.org/images/logo_medium.png)
2
+
3
+ ![Travis CI Status](https://secure.travis-ci.org/presidentbeef/brakeman.png)
4
+
1
5
  # Brakeman
2
6
 
3
7
  Brakeman is a static analysis tool which checks Ruby on Rails applications for security vulnerabilities.
@@ -120,13 +120,28 @@ class Brakeman::BaseCheck < SexpProcessor
120
120
  end
121
121
 
122
122
  #Checks if the model inherits from parent,
123
- def parent? model, parent
123
+ def ancestor? model, parent
124
124
  if model == nil
125
125
  false
126
126
  elsif model[:parent] == parent
127
127
  true
128
128
  elsif model[:parent]
129
- parent? tracker.models[model[:parent]], parent
129
+ ancestor? tracker.models[model[:parent]], parent
130
+ else
131
+ false
132
+ end
133
+ end
134
+
135
+ def unprotected_model? model
136
+ model[:attr_accessible].nil? and !parent_classes_protected?(model) and ancestor?(model, :"ActiveRecord::Base")
137
+ end
138
+
139
+ # go up the chain of parent classes to see if any have attr_accessible
140
+ def parent_classes_protected? model
141
+ if model[:attr_accessible]
142
+ true
143
+ elsif parent = tracker.models[model[:parent]]
144
+ parent_classes_protected? parent
130
145
  else
131
146
  false
132
147
  end
@@ -23,7 +23,6 @@ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
23
23
  warn :controller => name,
24
24
  :warning_type => "Basic Auth",
25
25
  :message => "Basic authentication password stored in source code",
26
- :line => call.line,
27
26
  :code => call,
28
27
  :confidence => 0
29
28
 
@@ -100,7 +100,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
100
100
  warn :template => @current_template,
101
101
  :warning_type => "Cross Site Scripting",
102
102
  :message => message,
103
- :line => input.match.line,
104
103
  :code => input.match,
105
104
  :confidence => CONFIDENCE[:high]
106
105
 
@@ -120,7 +119,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
120
119
  warn :template => @current_template,
121
120
  :warning_type => "Cross Site Scripting",
122
121
  :message => "Unescaped model attribute",
123
- :line => code.line,
124
122
  :code => code,
125
123
  :confidence => confidence
126
124
  end
@@ -184,7 +182,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
184
182
  warn :template => @current_template,
185
183
  :warning_type => "Cross Site Scripting",
186
184
  :message => message,
187
- :line => exp.line,
188
185
  :code => exp,
189
186
  :user_input => @matched.match,
190
187
  :confidence => confidence
@@ -52,7 +52,6 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
52
52
  warn :result => result,
53
53
  :warning_type => "Command Injection",
54
54
  :message => "Possible command injection",
55
- :line => call.line,
56
55
  :code => call,
57
56
  :user_input => failure.match,
58
57
  :confidence => confidence
@@ -86,7 +85,6 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
86
85
 
87
86
  warning = { :warning_type => "Command Injection",
88
87
  :message => "Possible command injection",
89
- :line => exp.line,
90
88
  :code => exp,
91
89
  :user_input => user_input,
92
90
  :confidence => confidence }
@@ -47,7 +47,6 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
47
47
  :warning_type => "File Access",
48
48
  :message => message,
49
49
  :confidence => CONFIDENCE[:high],
50
- :line => call.line,
51
50
  :code => call,
52
51
  :user_input => input.match
53
52
  end
@@ -14,7 +14,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
14
14
 
15
15
  models = []
16
16
  tracker.models.each do |name, m|
17
- if parent?(m, :"ActiveRecord::Base") and m[:attr_accessible].nil?
17
+ if unprotected_model? m
18
18
  models << name
19
19
  end
20
20
  end
@@ -68,7 +68,6 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
68
68
  warn :result => res,
69
69
  :warning_type => "Mass Assignment",
70
70
  :message => "Unprotected mass assignment",
71
- :line => call.line,
72
71
  :code => call,
73
72
  :user_input => user_input,
74
73
  :confidence => confidence
@@ -62,7 +62,7 @@ class Brakeman::CheckModelAttributes < Brakeman::BaseCheck
62
62
 
63
63
  def check_models
64
64
  tracker.models.each do |name, model|
65
- if model[:attr_accessible].nil? and parent? model, :"ActiveRecord::Base"
65
+ if unprotected_model? model
66
66
  yield name, model
67
67
  end
68
68
  end
@@ -37,7 +37,6 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
37
37
  warn :result => result,
38
38
  :warning_type => "Redirect",
39
39
  :message => "Possible unprotected redirect",
40
- :line => call.line,
41
40
  :code => call,
42
41
  :user_input => res.match,
43
42
  :confidence => confidence
@@ -27,7 +27,6 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
27
27
  warn :class => controller[:name],
28
28
  :warning_type => "Cross-Site Request Forgery",
29
29
  :message => "Use whitelist (:only => [..]) when skipping CSRF check",
30
- :line => filter.line,
31
30
  :code => filter,
32
31
  :confidence => CONFIDENCE[:med]
33
32
  end
@@ -16,10 +16,11 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
16
16
  def run_check
17
17
  @rails_version = tracker.config[:rails_version]
18
18
 
19
+ @sql_targets = [:all, :average, :calculate, :count, :count_by_sql, :exists?,
20
+ :find, :find_by_sql, :first, :last, :maximum, :minimum, :sum]
21
+
19
22
  if tracker.options[:rails3]
20
- @sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql|where|order|group|having)$/
21
- else
22
- @sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/
23
+ @sql_targets.concat [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where]
23
24
  end
24
25
 
25
26
  Brakeman.debug "Finding possible SQL calls on models"
@@ -90,7 +91,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
90
91
  find_calls.process_source block, model_name, scope_name
91
92
 
92
93
  find_calls.calls.each do |call|
93
- if call[:method].to_s =~ @sql_targets
94
+ if @sql_targets.include? call[:method]
94
95
  process_result call
95
96
  end
96
97
  end
@@ -99,35 +100,82 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
99
100
  end
100
101
  end
101
102
 
102
- #Process result from Tracker#find_call.
103
+ #Process possible SQL injection sites:
104
+ #
105
+ # Model#find
106
+ #
107
+ # Model#(named_)scope
108
+ #
109
+ # Model#(find|count)_by_sql
110
+ #
111
+ # Model#all
112
+ #
113
+ ### Rails 3
114
+ #
115
+ # Model#(where|having)
116
+ # Model#(order|group)
117
+ #
118
+ ### Find Options Hash
119
+ #
120
+ # Dangerous keys that accept SQL:
121
+ #
122
+ # * conditions
123
+ # * order
124
+ # * having
125
+ # * joins
126
+ # * select
127
+ # * from
128
+ # * lock
129
+ #
103
130
  def process_result result
104
- #TODO: I don't like this method at all. It's a pain to figure out what
105
- #it is actually doing...
131
+ return if duplicate? result or result[:call].original_line
106
132
 
107
133
  call = result[:call]
108
-
134
+ method = call[2]
109
135
  args = call[3]
110
136
 
111
- if call[2] == :find_by_sql or call[2] == :count_by_sql
112
- failed = check_arguments args[1]
113
- elsif call[2].to_s =~ /^find/
114
- failed = (args.length > 2 and check_arguments args[-1])
115
- elsif tracker.options[:rails3] and result[:method] != :scope
116
- #This is for things like where("query = ?")
117
- failed = check_arguments args[1]
118
- else
119
- failed = (args.length > 1 and check_arguments args[-1])
120
- end
121
-
122
- if failed and not call.original_line and not duplicate? result
137
+ dangerous_value = case method
138
+ when :find
139
+ check_find_arguments args[2]
140
+ when :exists?
141
+ check_find_arguments args[1]
142
+ when :named_scope, :scope
143
+ check_scope_arguments args
144
+ when :find_by_sql, :count_by_sql
145
+ check_by_sql_arguments args[1]
146
+ when :calculate
147
+ check_find_arguments args[3]
148
+ when :last, :first, :all
149
+ check_find_arguments args[1]
150
+ when :average, :count, :maximum, :minimum, :sum
151
+ if args.length > 2
152
+ unsafe_sql?(args[1]) or check_find_arguments(args[-1])
153
+ else
154
+ check_find_arguments args[-1]
155
+ end
156
+ when :where, :having
157
+ check_query_arguments args
158
+ when :order, :group, :reorder
159
+ check_order_arguments args
160
+ when :joins
161
+ check_joins_arguments args[1]
162
+ when :from, :select
163
+ unsafe_sql? args[1]
164
+ when :lock
165
+ check_lock_arguments args[1]
166
+ else
167
+ Brakeman.debug "Unhandled SQL method: #{method}"
168
+ end
169
+
170
+ if dangerous_value
123
171
  add_result result
124
172
 
125
- if input = include_user_input?(args[-1])
173
+ if input = include_user_input?(dangerous_value)
126
174
  confidence = CONFIDENCE[:high]
127
175
  user_input = input.match
128
176
  else
129
177
  confidence = CONFIDENCE[:med]
130
- user_input = nil
178
+ user_input = dangerous_value
131
179
  end
132
180
 
133
181
  warn :result => result,
@@ -144,34 +192,137 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
144
192
  confidence = CONFIDENCE[:low]
145
193
  end
146
194
 
147
- warn :result => result,
148
- :warning_type => "SQL Injection",
195
+ warn :result => result,
196
+ :warning_type => "SQL Injection",
149
197
  :message => "Upgrade to Rails >= 2.1.2 to escape :limit and :offset. Possible SQL injection",
150
198
  :confidence => confidence
151
199
  end
152
200
  end
153
201
 
154
- private
202
+ #The 'find' methods accept a number of different types of parameters:
203
+ #
204
+ # * The first argument might be :all, :first, or :last
205
+ # * The first argument might be an integer ID or an array of IDs
206
+ # * The second argument might be a hash of options, some of which are
207
+ # dangerous and some of which are not
208
+ # * The second argument might contain SQL fragments as values
209
+ # * The second argument might contain properly parameterized SQL fragments in arrays
210
+ # * The second argument might contain improperly parameterized SQL fragments in arrays
211
+ #
212
+ #This method should only be passed the second argument.
213
+ def check_find_arguments arg
214
+ if not sexp? arg or node_type? arg, :lit, :string, :str, :true, :false, :nil
215
+ return nil
216
+ end
155
217
 
156
- #Check arguments for any string interpolation
157
- def check_arguments arg
158
- if sexp? arg
159
- case arg.node_type
160
- when :hash
161
- hash_iterate(arg) do |key, value|
162
- if check_arguments value
163
- return true
164
- end
165
- end
166
- when :array
167
- return check_arguments(arg[1])
168
- when :string_interp, :dstr
169
- return true if check_string_interp arg
170
- when :call
171
- return check_call(arg)
218
+ unsafe_sql? arg
219
+ end
220
+
221
+ def check_scope_arguments args
222
+ return unless node_type? args, :arglist
223
+
224
+ if node_type? args[2], :iter
225
+ unsafe_sql? args[2][-1]
226
+ else
227
+ unsafe_sql? args[2]
228
+ end
229
+ end
230
+
231
+ def check_query_arguments arg
232
+ return unless sexp? arg
233
+
234
+ if node_type? arg, :arglist
235
+ if arg.length > 2 and node_type? arg[1], :string_interp, :dstr
236
+ # Model.where("blah = ?", blah)
237
+ return string_interp arg[1]
172
238
  else
173
- return arg.any? do |a|
174
- check_arguments(a)
239
+ arg = arg[1]
240
+ end
241
+ end
242
+
243
+ if request_value? arg
244
+ # Model.where(params[:where])
245
+ arg
246
+ elsif hash? arg
247
+ #This is generally going to be a hash of column names and values, which
248
+ #would escape the values. But the keys _could_ be user input.
249
+ check_hash_keys arg
250
+ elsif node_type? arg, :lit, :str
251
+ nil
252
+ else
253
+ #Hashes are safe...but we check above for hash, so...?
254
+ unsafe_sql? arg, :ignore_hash
255
+ end
256
+ end
257
+
258
+ #Checks each argument to order/reorder/group for possible SQL.
259
+ #Anything used with these methods is passed in verbatim.
260
+ def check_order_arguments args
261
+ return unless sexp? args
262
+
263
+ if node_type? args, :arglist
264
+ args.each do |arg|
265
+ if unsafe_arg = unsafe_sql?(arg)
266
+ return unsafe_arg
267
+ end
268
+ end
269
+
270
+ nil
271
+ else
272
+ unsafe_sql? args
273
+ end
274
+ end
275
+
276
+ #find_by_sql and count_by_sql can take either a straight SQL string
277
+ #or an array with values to bind.
278
+ def check_by_sql_arguments arg
279
+ return unless sexp? arg
280
+
281
+ #This is kind of necessary, because unsafe_sql? will handle an array
282
+ #correctly, but might be better to be explicit.
283
+ if array? arg
284
+ unsafe_sql? arg[1]
285
+ else
286
+ unsafe_sql? arg
287
+ end
288
+ end
289
+
290
+ #joins can take a string, hash of associations, or an array of both(?)
291
+ #We only care about the possible string values.
292
+ def check_joins_arguments arg
293
+ return unless sexp? arg and not node_type? arg, :hash, :string, :str
294
+
295
+ if array? arg
296
+ arg.each do |a|
297
+ if unsafe = check_joins_arguments(a)
298
+ return unsafe
299
+ end
300
+ end
301
+
302
+ nil
303
+ else
304
+ unsafe_sql? arg
305
+ end
306
+ end
307
+
308
+ #Model#lock essentially only cares about strings. But those strings can be
309
+ #any SQL fragment. This does not apply to all databases. (For those who do not
310
+ #support it, the lock method does nothing).
311
+ def check_lock_arguments arg
312
+ return unless sexp? arg and not node_type? arg, :hash, :array, :string, :str
313
+
314
+ unsafe_sql? arg, :ignore_hash
315
+ end
316
+
317
+
318
+ #Check hash keys for user input.
319
+ #(Seems unlikely, but if a user can control the column names queried, that
320
+ #could be bad)
321
+ def check_hash_keys exp
322
+ hash_iterate(exp) do |key, value|
323
+ unless symbol? key
324
+ if unsafe_key = unsafe_sql?(value)
325
+ return unsafe_key
175
326
  end
176
327
  end
177
328
  end
@@ -179,35 +330,169 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
179
330
  false
180
331
  end
181
332
 
333
+ #Check an interpolated string for dangerous values.
334
+ #
335
+ #This method assumes values interpolated into strings are unsafe by default,
336
+ #unless safe_value? explicitly returns true.
182
337
  def check_string_interp arg
183
338
  arg.each do |exp|
184
- #For now, don't warn on interpolation of Model.table_name
185
- #but check for other 'safe' things in the future
186
- if node_type? exp, :string_eval, :evstr
187
- if call? exp[1] and (model_name?(exp[1][1]) or exp[1][1].nil?) and exp[1][2] == :table_name
188
- return false
339
+ if node_type?(exp, :string_eval, :evstr) and not safe_value? exp[1]
340
+ return exp[1]
341
+ end
342
+ end
343
+
344
+ nil
345
+ end
346
+
347
+ #Checks the given expression for unsafe SQL values. If an unsafe value is
348
+ #found, returns that value (may be the given _exp_ or a subexpression).
349
+ #
350
+ #Otherwise, returns false/nil.
351
+ def unsafe_sql? exp, ignore_hash = false
352
+ return unless sexp? exp
353
+
354
+ dangerous_value = find_dangerous_value exp, ignore_hash
355
+
356
+ if safe_value? dangerous_value
357
+ false
358
+ else
359
+ dangerous_value
360
+ end
361
+ end
362
+
363
+ #Check _exp_ for dangerous values. Used by unsafe_sql?
364
+ def find_dangerous_value exp, ignore_hash
365
+ case exp.node_type
366
+ when :lit, :str, :const, :colon2, :true, :false, :nil
367
+ nil
368
+ when :array
369
+ #Assume this is an array like
370
+ #
371
+ # ["blah = ? AND thing = ?", ...]
372
+ #
373
+ #and check first value
374
+
375
+ unsafe_sql? exp[1]
376
+ when :string_interp, :dstr
377
+ check_string_interp exp
378
+ when :hash
379
+ check_hash_values exp unless ignore_hash
380
+ when :if
381
+ unsafe_sql? exp[2] or unsafe_sql? exp[3]
382
+ when :call
383
+ unless IGNORE_METHODS_IN_SQL.include? exp[2]
384
+ if has_immediate_user_input? exp or has_immediate_model? exp
385
+ exp
386
+ else
387
+ check_call exp
189
388
  end
190
389
  end
390
+ when :or
391
+ if unsafe = (unsafe_sql?(exp[1]) || unsafe_sql?(exp[2]))
392
+ return unsafe
393
+ else
394
+ nil
395
+ end
396
+ when :block, :rlist
397
+ unsafe_sql? exp[-1]
398
+ else
399
+ if has_immediate_user_input? exp or has_immediate_model? exp
400
+ exp
401
+ else
402
+ nil
403
+ end
191
404
  end
192
405
  end
193
406
 
194
- #Check call for user input and string building
195
- def check_call exp
407
+ #Checks hash values associated with these keys:
408
+ #
409
+ # * conditions
410
+ # * order
411
+ # * having
412
+ # * joins
413
+ # * select
414
+ # * from
415
+ # * lock
416
+ def check_hash_values exp
417
+ hash_iterate(exp) do |key, value|
418
+ if symbol? key
419
+ unsafe = case key[1]
420
+ when :conditions, :having, :select
421
+ check_query_arguments value
422
+ when :order, :group
423
+ check_order_arguments value
424
+ when :joins
425
+ check_joins_arguments value
426
+ when :lock
427
+ check_lock_arguments value
428
+ when :from
429
+ unsafe_sql? value
430
+ else
431
+ nil
432
+ end
433
+
434
+ return unsafe if unsafe
435
+ end
436
+ end
437
+
438
+ false
439
+ end
440
+
441
+ STRING_METHODS = Set[:<<, :+, :concat, :prepend]
442
+
443
+ def check_for_string_building exp
444
+ return unless call? exp
445
+
196
446
  target = exp[1]
197
447
  method = exp[2]
198
448
  args = exp[3]
199
449
 
200
- if sexp? target and
201
- (method == :+ or method == :<< or method == :concat) and
202
- (string? target or include_user_input? exp)
450
+ if string? target or string? args[1]
451
+ if STRING_METHODS.include? method
452
+ return exp
453
+ end
454
+ elsif STRING_METHODS.include? method and call? target
455
+ unsafe_sql? target
456
+ end
457
+ end
203
458
 
459
+ IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :to_i, :to_f,
460
+ :sanitize_sql, :sanitize_sql_array, :sanitize_sql_for_assignment,
461
+ :sanitize_sql_for_conditions, :sanitize_sql_hash,
462
+ :sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions]
463
+
464
+ def safe_value? exp
465
+ return true unless sexp? exp
466
+
467
+ case exp.node_type
468
+ when :str, :lit, :const, :colon2, :nil, :true, :false
204
469
  true
470
+ when :call
471
+ IGNORE_METHODS_IN_SQL.include? exp[2]
472
+ when :if
473
+ safe_value? exp[2] and safe_value? exp[3]
474
+ when :block, :rlist
475
+ safe_value? exp[-1]
476
+ when :or
477
+ safe_value? exp[1] and safe_value? exp[2]
478
+ else
479
+ false
480
+ end
481
+ end
482
+
483
+ #Check call for string building
484
+ def check_call exp
485
+ return unless call? exp
486
+
487
+ target = exp[1]
488
+ args = exp[3]
489
+
490
+ if unsafe = check_for_string_building(exp)
491
+ unsafe
205
492
  elsif call? target
206
493
  check_call target
207
- elsif target == nil and tracker.options[:rails3] and method.to_s.match(/^first|last|all|where|order|group|having$/)
208
- check_arguments args
209
494
  else
210
- false
495
+ nil
211
496
  end
212
497
  end
213
498
 
@@ -218,7 +503,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
218
503
  def check_for_limit_or_offset_vulnerability options
219
504
  return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash? options
220
505
 
221
- if hash_access(options, :limit) or hash_access(options[:offset])
506
+ if hash_access(options, :limit) or hash_access(options, :offset)
222
507
  return true
223
508
  end
224
509
 
@@ -231,7 +516,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
231
516
  #
232
517
  # s(:call,
233
518
  # s(:call,
234
- # s(:call,
519
+ # s(:call,
235
520
  # s(:call, nil, :params, s(:arglist)),
236
521
  # :[],
237
522
  # s(:arglist, s(:lit, :x))),
@@ -16,7 +16,7 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
16
16
 
17
17
  models = []
18
18
  tracker.models.each do |name, m|
19
- if parent? m, :"ActiveRecord::Base"
19
+ if ancestor? m, :"ActiveRecord::Base"
20
20
  models << name
21
21
  end
22
22
  end
@@ -59,7 +59,6 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
59
59
  warn :result => res,
60
60
  :warning_type => "Mass Assignment",
61
61
  :message => "Unprotected mass assignment",
62
- :line => call.line,
63
62
  :code => call,
64
63
  :user_input => user_input,
65
64
  :confidence => confidence
@@ -10,6 +10,8 @@ module Brakeman
10
10
  #The ControllerProcessor, TemplateProcessor, and ModelProcessor will
11
11
  #update the Tracker with information about what is parsed.
12
12
  class Processor
13
+ include Util
14
+
13
15
  def initialize options
14
16
  @tracker = Tracker.new self, options
15
17
  end
@@ -35,7 +37,11 @@ module Brakeman
35
37
 
36
38
  #Process controller source. +file_name+ is used for reporting
37
39
  def process_controller src, file_name
38
- ControllerProcessor.new(@tracker).process_controller src, file_name
40
+ if contains_class? src
41
+ ControllerProcessor.new(@tracker).process_controller src, file_name
42
+ else
43
+ LibraryProcessor.new(@tracker).process_library src, file_name
44
+ end
39
45
  end
40
46
 
41
47
  #Process variable aliasing in controller source and save it in the
@@ -251,7 +251,7 @@ class Brakeman::BaseProcessor < SexpProcessor
251
251
  value = args[1]
252
252
  end
253
253
 
254
- types_in_hash = Set[:action, :file, :inline, :js, :json, :nothing, :partial, :text, :update, :xml]
254
+ types_in_hash = Set[:action, :file, :inline, :js, :json, :nothing, :partial, :template, :text, :update, :xml]
255
255
 
256
256
  #render :layout => "blah" means something else when in a template
257
257
  if in_view
@@ -24,7 +24,41 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
24
24
  return
25
25
  else
26
26
  @current_class = name
27
+
27
28
  process_default src
29
+
30
+ process_mixins
31
+ end
32
+ end
33
+
34
+ #Process modules mixed into the controller, in case they contain actions.
35
+ def process_mixins
36
+ controller = @tracker.controllers[@current_class]
37
+
38
+ controller[:includes].each do |i|
39
+ mixin = @tracker.libs[i]
40
+
41
+ next unless mixin
42
+
43
+ #Process methods in alphabetical order for consistency
44
+ methods = mixin[:public].keys.map { |n| n.to_s }.sort.map { |n| n.to_sym }
45
+
46
+ methods.each do |name|
47
+ #Need to process the method like it was in a controller in order
48
+ #to get the renders set
49
+ processor = Brakeman::ControllerProcessor.new(@tracker)
50
+ method = mixin[:public][name]
51
+
52
+ if node_type? method, :methdef
53
+ method = processor.process_defn method
54
+ else
55
+ #Should be a methdef, but this will catch other cases
56
+ method = processor.process method
57
+ end
58
+
59
+ #Then process it like any other method in the controller
60
+ process method
61
+ end
28
62
  end
29
63
  end
30
64
 
@@ -8,7 +8,7 @@ module Brakeman::RenderHelper
8
8
  process_default exp
9
9
  @rendered = true
10
10
  case exp[1]
11
- when :action
11
+ when :action, :template
12
12
  process_action exp[2][1], exp[3]
13
13
  when :default
14
14
  begin
@@ -505,7 +505,7 @@ class Brakeman::Report
505
505
  message = CGI.escapeHTML(message)
506
506
 
507
507
  if @highlight_user_input and warning.user_input
508
- user_input = warning.format_user_input
508
+ user_input = CGI.escapeHTML(warning.format_user_input)
509
509
 
510
510
  message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
511
511
  end
@@ -127,7 +127,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
127
127
  end
128
128
 
129
129
  def rescan_template path
130
- return unless path.match KNOWN_TEMPLATE_EXTENSIONS
130
+ return unless path.match KNOWN_TEMPLATE_EXTENSIONS and File.exist? path
131
131
 
132
132
  template_name = template_path_to_name(path)
133
133
 
data/lib/brakeman/util.rb CHANGED
@@ -219,6 +219,25 @@ module Brakeman::Util
219
219
  exp.is_a? Sexp and types.include? exp.node_type
220
220
  end
221
221
 
222
+ #Returns true if the given _exp_ contains a :class node.
223
+ #
224
+ #Useful for checking if a module is just a module or if it is a namespace.
225
+ def contains_class? exp
226
+ todo = [exp]
227
+
228
+ until todo.empty?
229
+ current = todo.shift
230
+
231
+ if node_type? current, :class
232
+ return true
233
+ elsif sexp? current
234
+ todo = current[1..-1].concat todo
235
+ end
236
+ end
237
+
238
+ false
239
+ end
240
+
222
241
  #Return file name related to given warning. Uses +warning.file+ if it exists
223
242
  def file_for warning, tracker = nil
224
243
  if tracker.nil?
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "1.6.0"
2
+ Version = "1.6.1"
3
3
  end
@@ -29,8 +29,12 @@ class Brakeman::Warning
29
29
  end
30
30
  end
31
31
 
32
- if @code and not @line and @code.respond_to? :line
33
- @line = @code.line
32
+ if not @line
33
+ if @user_input and @user_input.respond_to? :line
34
+ @line = @user_input.line
35
+ elsif @code and @code.respond_to? :line
36
+ @line = @code.line
37
+ end
34
38
  end
35
39
 
36
40
  unless @warning_set
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 13
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 6
9
- - 0
10
- version: 1.6.0
9
+ - 1
10
+ version: 1.6.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Justin Collins
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-04-20 00:00:00 Z
18
+ date: 2012-05-23 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activesupport
@@ -166,6 +166,20 @@ dependencies:
166
166
  version: "3.0"
167
167
  type: :runtime
168
168
  version_requirements: *id010
169
+ - !ruby/object:Gem::Dependency
170
+ name: json_pure
171
+ prerelease: false
172
+ requirement: &id011 !ruby/object:Gem::Requirement
173
+ none: false
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ hash: 3
178
+ segments:
179
+ - 0
180
+ version: "0"
181
+ type: :runtime
182
+ version_requirements: *id011
169
183
  description: Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis.
170
184
  email:
171
185
  executables:
@@ -298,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
312
  requirements: []
299
313
 
300
314
  rubyforge_project:
301
- rubygems_version: 1.8.15
315
+ rubygems_version: 1.8.23
302
316
  signing_key:
303
317
  specification_version: 3
304
318
  summary: Security vulnerability scanner for Ruby on Rails.