brakeman 1.6.0 → 1.6.1

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