brakeman 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGES +20 -0
  2. data/README.md +6 -1
  3. data/bin/brakeman +13 -3
  4. data/lib/brakeman.rb +64 -7
  5. data/lib/brakeman/call_index.rb +6 -4
  6. data/lib/brakeman/checks/check_basic_auth.rb +47 -2
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +50 -12
  8. data/lib/brakeman/checks/check_execute.rb +4 -1
  9. data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
  10. data/lib/brakeman/checks/check_sql.rb +101 -154
  11. data/lib/brakeman/options.rb +16 -0
  12. data/lib/brakeman/parsers/rails2_erubis.rb +2 -0
  13. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +2 -0
  14. data/lib/brakeman/parsers/rails3_erubis.rb +2 -0
  15. data/lib/brakeman/processors/alias_processor.rb +19 -4
  16. data/lib/brakeman/processors/controller_alias_processor.rb +2 -3
  17. data/lib/brakeman/processors/gem_processor.rb +5 -4
  18. data/lib/brakeman/processors/lib/find_all_calls.rb +43 -16
  19. data/lib/brakeman/report.rb +39 -640
  20. data/lib/brakeman/report/ignore/config.rb +130 -0
  21. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  22. data/lib/brakeman/report/renderer.rb +2 -0
  23. data/lib/brakeman/report/report_base.rb +279 -0
  24. data/lib/brakeman/report/report_csv.rb +56 -0
  25. data/lib/brakeman/report/report_hash.rb +22 -0
  26. data/lib/brakeman/report/report_html.rb +203 -0
  27. data/lib/brakeman/report/report_json.rb +46 -0
  28. data/lib/brakeman/report/report_table.rb +109 -0
  29. data/lib/brakeman/report/report_tabs.rb +17 -0
  30. data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
  31. data/lib/brakeman/report/templates/overview.html.erb +6 -0
  32. data/lib/brakeman/report/templates/security_warnings.html.erb +1 -1
  33. data/lib/brakeman/scanner.rb +14 -12
  34. data/lib/brakeman/tracker.rb +5 -1
  35. data/lib/brakeman/util.rb +2 -0
  36. data/lib/brakeman/version.rb +1 -1
  37. data/lib/ruby_parser/bm_sexp.rb +12 -1
  38. metadata +179 -90
  39. checksums.yaml +0 -7
@@ -18,10 +18,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
18
18
 
19
19
  @sql_targets = [:all, :average, :calculate, :count, :count_by_sql, :exists?,
20
20
  :find, :find_by_sql, :first, :last, :maximum, :minimum, :pluck, :sum, :update_all]
21
-
22
- if tracker.options[:rails3]
23
- @sql_targets.concat [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where]
24
- end
21
+ @sql_targets.concat [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where] if tracker.options[:rails3]
25
22
 
26
23
  Brakeman.debug "Finding possible SQL calls on models"
27
24
  calls = tracker.find_call :targets => active_record_models.keys,
@@ -37,25 +34,11 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
37
34
  Brakeman.debug "Finding calls to named_scope or scope"
38
35
  calls.concat find_scope_calls
39
36
 
40
- Brakeman.debug "Checking version of Rails for CVE-2012-2660"
41
- check_rails_version_for_cve_2012_2660
42
-
43
- Brakeman.debug "Checking version of Rails for CVE-2012-2661"
44
- check_rails_version_for_cve_2012_2661
45
-
46
- Brakeman.debug "Checking version of Rails for CVE-2012-2695"
47
- check_rails_version_for_cve_2012_2695
48
-
49
- Brakeman.debug "Checking version of Rails for CVE-2012-5664"
50
- check_rails_version_for_cve_2012_5664
51
-
52
- Brakeman.debug "Checking version of Rails for CVE-2013-0155"
53
- check_rails_version_for_cve_2013_0155
37
+ Brakeman.debug "Checking version of Rails for CVE issues"
38
+ check_rails_versions_against_cve_issues
54
39
 
55
40
  Brakeman.debug "Processing possible SQL calls"
56
- calls.each do |c|
57
- process_result c
58
- end
41
+ calls.each { |call| process_result call }
59
42
  end
60
43
 
61
44
  #Find calls to named_scope() or scope() in models
@@ -63,33 +46,24 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
63
46
  def find_scope_calls
64
47
  scope_calls = []
65
48
 
66
- if version_between? "2.1.0", "3.0.9"
67
- active_record_models.each do |name, model|
68
- if model[:options][:named_scope]
69
- model[:options][:named_scope].each do |args|
70
- call = make_call(nil, :named_scope, args).line(args.line)
71
- scope_calls << { :call => call, :location => { :type => :class, :class => name }, :method => :named_scope }
72
- end
73
- end
74
- end
75
- elsif version_between? "3.1.0", "3.9.9"
76
- active_record_models.each do |name, model|
77
- if model[:options][:scope]
78
- model[:options][:scope].each do |args|
79
- second_arg = args[2]
80
-
81
- next unless sexp? second_arg
82
-
83
- if second_arg.node_type == :iter and node_type? second_arg.block, :block, :call
84
- process_scope_with_block name, args
85
- elsif second_arg.node_type == :call
86
- call = second_arg
87
- scope_calls << { :call => call, :location => { :type => :class, :class => name }, :method => call.method }
88
- else
89
- call = make_call(nil, :scope, args).line(args.line)
90
- scope_calls << { :call => call, :location => { :type => :class, :class => name }, :method => :scope }
91
- end
92
- end
49
+ if version_between?("2.1.0", "3.0.9")
50
+ ar_scope_calls(:named_scope) do |name, args|
51
+ call = make_call(nil, :named_scope, args).line(args.line)
52
+ scope_calls << scope_call_hash(call, name, :named_scope)
53
+ end
54
+ elsif version_between?("3.1.0", "3.9.9")
55
+ ar_scope_calls(:scope) do |name, args|
56
+ second_arg = args[2]
57
+ next unless sexp? second_arg
58
+
59
+ if second_arg.node_type == :iter and node_type? second_arg.block, :block, :call
60
+ process_scope_with_block(name, args)
61
+ elsif second_arg.node_type == :call
62
+ call = second_arg
63
+ scope_calls << scope_call_hash(call, name, call.method)
64
+ else
65
+ call = make_call(nil, :scope, args).line(args.line)
66
+ scope_calls << scope_call_hash(call, name, :scope)
93
67
  end
94
68
  end
95
69
  end
@@ -97,66 +71,34 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
97
71
  scope_calls
98
72
  end
99
73
 
100
- def check_rails_version_for_cve_2012_2660
101
- versions = [%w[2.0.0 2.3.14 2.3.17],
102
- %w[3.0.0 3.0.12 3.0.13],
103
- %w[3.1.0 3.1.4 3.1.5],
104
- %w[3.2.0 3.2.3 3.2.4]]
105
-
106
- cve_warning_for versions, "CVE-2012-2660", "https://groups.google.com/d/topic/rubyonrails-security/8SA-M3as7A8/discussion"
107
- end
108
-
109
- def check_rails_version_for_cve_2012_2661
110
- versions = [%w[3.0.0 3.0.12 3.0.13],
111
- %w[3.1.0 3.1.4 3.1.5],
112
- %w[3.2.0 3.2.3 3.2.5]]
113
-
114
- cve_warning_for versions, "CVE-2012-2661", "https://groups.google.com/d/topic/rubyonrails-security/dUaiOOGWL1k/discussion"
115
- end
116
-
117
- def check_rails_version_for_cve_2012_2695
118
- versions = [%w[2.0.0 2.3.14 2.3.15],
119
- %w[3.0.0 3.0.13 3.0.14],
120
- %w[3.1.0 3.1.5 3.1.6],
121
- %w[3.2.0 3.2.5 3.2.6]]
122
-
123
- cve_warning_for versions, "CVE-2012-2695", "https://groups.google.com/d/topic/rubyonrails-security/l4L0TEVAz1k/discussion"
74
+ def ar_scope_calls(symbol_name = :named_scope, &block)
75
+ return_array = []
76
+ active_record_models.each do |name, model|
77
+ model_args = model[:options][symbol_name]
78
+ if model_args
79
+ model_args.each do |args|
80
+ yield name, args
81
+ return_array << [name, args]
82
+ end
83
+ end
84
+ end
85
+ return_array
124
86
  end
125
87
 
126
- def check_rails_version_for_cve_2012_5664
127
- versions = [%w[2.0.0 2.3.14 2.3.15],
128
- %w[3.0.0 3.0.17 3.0.18],
129
- %w[3.1.0 3.1.8 3.1.9],
130
- %w[3.2.0 3.2.9 3.2.18]]
131
-
132
- cve_warning_for versions, "CVE-2012-5664", "https://groups.google.com/d/topic/rubyonrails-security/DCNTNp_qjFM/discussion"
88
+ def scope_call_hash(call, name, method)
89
+ { :call => call, :location => { :type => :class, :class => name }, :method => :named_scope }
133
90
  end
134
91
 
135
- def check_rails_version_for_cve_2013_0155
136
- versions = [%w[2.0.0 2.3.15 2.3.16],
137
- %w[3.0.0 3.0.18 3.0.19],
138
- %w[3.1.0 3.1.9 3.1.10],
139
- %w[3.2.0 3.2.10 3.2.11]]
140
-
141
-
142
- cve_warning_for versions, "CVE-2013-0155", "https://groups.google.com/d/topic/rubyonrails-security/c7jT-EeN9eI/discussion"
143
- end
144
92
 
145
93
  def process_scope_with_block model_name, args
146
94
  scope_name = args[1][1]
147
95
  block = args[-1][-1]
148
96
 
149
- #Search lambda for calls to query methods
97
+ # Search lambda for calls to query methods
150
98
  if block.node_type == :block
151
- find_calls = Brakeman::FindAllCalls.new tracker
152
-
153
- find_calls.process_source block, :class => model_name, :method => scope_name
154
-
155
- find_calls.calls.each do |call|
156
- if @sql_targets.include? call[:method]
157
- process_result call
158
- end
159
- end
99
+ find_calls = Brakeman::FindAllCalls.new(tracker)
100
+ find_calls.process_source(block, :class => model_name, :method => scope_name)
101
+ find_calls.calls.each { |call| process_result(call) if @sql_targets.include?(call[:method]) }
160
102
  elsif block.node_type == :call
161
103
  process_result :target => block.target, :method => block.method, :call => block,
162
104
  :location => { :type => :class, :class => model_name, :method => scope_name }
@@ -191,7 +133,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
191
133
  # * lock
192
134
  #
193
135
  def process_result result
194
- return if duplicate? result or result[:call].original_line
136
+ return if duplicate?(result) or result[:call].original_line
195
137
 
196
138
  call = result[:call]
197
139
  method = call.method
@@ -236,7 +178,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
236
178
  if dangerous_value
237
179
  add_result result
238
180
 
239
- if input = include_user_input?(dangerous_value)
181
+ input = include_user_input? dangerous_value
182
+ if input
240
183
  confidence = CONFIDENCE[:high]
241
184
  user_input = input.match
242
185
  else
@@ -267,6 +210,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
267
210
  end
268
211
  end
269
212
 
213
+
270
214
  #The 'find' methods accept a number of different types of parameters:
271
215
  #
272
216
  # * The first argument might be :all, :first, or :last
@@ -279,9 +223,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
279
223
  #
280
224
  #This method should only be passed the second argument.
281
225
  def check_find_arguments arg
282
- if not sexp? arg or node_type? arg, :lit, :string, :str, :true, :false, :nil
283
- return nil
284
- end
226
+ return nil if not sexp? arg or node_type? arg, :lit, :string, :str, :true, :false, :nil
285
227
 
286
228
  unsafe_sql? arg
287
229
  end
@@ -289,11 +231,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
289
231
  def check_scope_arguments call
290
232
  scope_arg = call.second_arg #first arg is name of scope
291
233
 
292
- if node_type? scope_arg, :iter
293
- unsafe_sql? scope_arg.block
294
- else
295
- unsafe_sql? scope_arg
296
- end
234
+ node_type?(scope_arg, :iter) ? unsafe_sql?(scope_arg.block) : unsafe_sql?(scope_arg)
297
235
  end
298
236
 
299
237
  def check_query_arguments arg
@@ -330,13 +268,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
330
268
  return unless sexp? args
331
269
 
332
270
  if node_type? args, :arglist
333
- args.each do |arg|
334
- if unsafe_arg = unsafe_sql?(arg)
335
- return unsafe_arg
336
- end
337
- end
338
-
339
- nil
271
+ check_update_all_arguments(args)
340
272
  else
341
273
  unsafe_sql? args
342
274
  end
@@ -349,11 +281,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
349
281
 
350
282
  #This is kind of unnecessary, because unsafe_sql? will handle an array
351
283
  #correctly, but might be better to be explicit.
352
- if array? arg
353
- unsafe_sql? arg[1]
354
- else
355
- unsafe_sql? arg
356
- end
284
+ array?(arg) ? unsafe_sql?(arg[1]) : unsafe_sql?(arg)
357
285
  end
358
286
 
359
287
  #joins can take a string, hash of associations, or an array of both(?)
@@ -363,9 +291,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
363
291
 
364
292
  if array? arg
365
293
  arg.each do |a|
366
- if unsafe = check_joins_arguments(a)
367
- return unsafe
368
- end
294
+ unsafe_arg = check_joins_arguments a
295
+ return unsafe_arg if unsafe_arg
369
296
  end
370
297
 
371
298
  nil
@@ -376,8 +303,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
376
303
 
377
304
  def check_update_all_arguments args
378
305
  args.each do |arg|
379
- res = unsafe_sql? arg
380
- return res if res
306
+ unsafe_arg = unsafe_sql? arg
307
+ return unsafe_arg if unsafe_arg
381
308
  end
382
309
 
383
310
  nil
@@ -389,7 +316,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
389
316
  def check_lock_arguments arg
390
317
  return unless sexp? arg and not node_type? arg, :hash, :array, :string, :str
391
318
 
392
- unsafe_sql? arg, :ignore_hash
319
+ unsafe_sql?(arg, :ignore_hash)
393
320
  end
394
321
 
395
322
 
@@ -398,10 +325,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
398
325
  #could be bad)
399
326
  def check_hash_keys exp
400
327
  hash_iterate(exp) do |key, value|
401
- unless symbol? key
402
- if unsafe_key = unsafe_sql?(value)
403
- return unsafe_key
404
- end
328
+ unless symbol?(key)
329
+ unsafe_key = unsafe_sql? value
330
+ return unsafe_key if unsafe_key
405
331
  end
406
332
  end
407
333
 
@@ -414,9 +340,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
414
340
  #unless safe_value? explicitly returns true.
415
341
  def check_string_interp arg
416
342
  arg.each do |exp|
417
- if node_type?(exp, :string_eval, :evstr) and not safe_value? exp.value
418
- return exp.value
419
- end
343
+ return exp.value if node_type?(exp, :string_eval, :evstr) and not safe_value?(exp.value)
420
344
  end
421
345
 
422
346
  nil
@@ -427,15 +351,10 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
427
351
  #
428
352
  #Otherwise, returns false/nil.
429
353
  def unsafe_sql? exp, ignore_hash = false
430
- return unless sexp? exp
354
+ return unless sexp?(exp)
431
355
 
432
356
  dangerous_value = find_dangerous_value exp, ignore_hash
433
-
434
- if safe_value? dangerous_value
435
- false
436
- else
437
- dangerous_value
438
- end
357
+ safe_value?(dangerous_value) ? false : dangerous_value
439
358
  end
440
359
 
441
360
  #Check _exp_ for dangerous values. Used by unsafe_sql?
@@ -449,7 +368,6 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
449
368
  # ["blah = ? AND thing = ?", ...]
450
369
  #
451
370
  #and check first value
452
-
453
371
  unsafe_sql? exp[1]
454
372
  when :string_interp, :dstr
455
373
  check_string_interp exp
@@ -467,7 +385,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
467
385
  end
468
386
  when :or
469
387
  if unsafe = (unsafe_sql?(exp.lhs) || unsafe_sql?(exp.rhs))
470
- return unsafe
388
+ unsafe
471
389
  else
472
390
  nil
473
391
  end
@@ -525,12 +443,12 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
525
443
  method = exp.method
526
444
 
527
445
  if string? target or string? exp.first_arg
528
- if STRING_METHODS.include? method
529
- return exp
530
- end
446
+ return exp if STRING_METHODS.include? method
531
447
  elsif STRING_METHODS.include? method and call? target
532
- unsafe_sql? target
448
+ return unsafe_sql? target
533
449
  end
450
+
451
+ nil
534
452
  end
535
453
 
536
454
  IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :to_i, :to_f,
@@ -561,8 +479,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
561
479
  #Check call for string building
562
480
  def check_call exp
563
481
  return unless call? exp
482
+ unsafe = check_for_string_building exp
564
483
 
565
- if unsafe = check_for_string_building(exp)
484
+ if unsafe
566
485
  unsafe
567
486
  elsif call? exp.target
568
487
  check_call exp.target
@@ -576,11 +495,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
576
495
  #
577
496
  #http://www.rorsecurity.info/2008/09/08/sql-injection-issue-in-limit-and-offset-parameter/
578
497
  def check_for_limit_or_offset_vulnerability options
579
- return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash? options
498
+ return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash?(options)
580
499
 
581
- if hash_access(options, :limit) or hash_access(options, :offset)
582
- return true
583
- end
500
+ return true if hash_access(options, :limit) or hash_access(options, :offset)
584
501
 
585
502
  false
586
503
  end
@@ -606,14 +523,44 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
606
523
 
607
524
  def upgrade_version? versions
608
525
  versions.each do |low, high, upgrade|
609
- if version_between? low, high
610
- return upgrade
611
- end
526
+ return upgrade if version_between? low, high
612
527
  end
613
528
 
614
529
  false
615
530
  end
616
531
 
532
+ def check_rails_versions_against_cve_issues
533
+ [
534
+ {
535
+ :cve => "CVE-2012-2660",
536
+ :versions => [%w[2.0.0 2.3.14 2.3.17], %w[3.0.0 3.0.12 3.0.13], %w[3.1.0 3.1.4 3.1.5], %w[3.2.0 3.2.3 3.2.4]],
537
+ :url => "https://groups.google.com/d/topic/rubyonrails-security/8SA-M3as7A8/discussion"
538
+ },
539
+ {
540
+ :cve => "CVE-2012-2661",
541
+ :versions => [%w[3.0.0 3.0.12 3.0.13], %w[3.1.0 3.1.4 3.1.5], %w[3.2.0 3.2.3 3.2.5]],
542
+ :url => "https://groups.google.com/d/topic/rubyonrails-security/dUaiOOGWL1k/discussion"
543
+ },
544
+ {
545
+ :cve => "CVE-2012-2695",
546
+ :versions => [%w[2.0.0 2.3.14 2.3.15], %w[3.0.0 3.0.13 3.0.14], %w[3.1.0 3.1.5 3.1.6], %w[3.2.0 3.2.5 3.2.6]],
547
+ :url => "https://groups.google.com/d/topic/rubyonrails-security/l4L0TEVAz1k/discussion"
548
+ },
549
+ {
550
+ :cve => "CVE-2012-5664",
551
+ :versions => [%w[2.0.0 2.3.14 2.3.15], %w[3.0.0 3.0.17 3.0.18], %w[3.1.0 3.1.8 3.1.9], %w[3.2.0 3.2.9 3.2.18]],
552
+ :url => "https://groups.google.com/d/topic/rubyonrails-security/DCNTNp_qjFM/discussion"
553
+ },
554
+ {
555
+ :cve => "CVE-2013-0155",
556
+ :versions => [%w[2.0.0 2.3.15 2.3.16], %w[3.0.0 3.0.18 3.0.19], %w[3.1.0 3.1.9 3.1.10], %w[3.2.0 3.2.10 3.2.11]],
557
+ :url => "https://groups.google.com/d/topic/rubyonrails-security/c7jT-EeN9eI/discussion"
558
+ },
559
+ ].each do |cve_issue|
560
+ cve_warning_for cve_issue[:versions], cve_issue[:cve], cve_issue[:url]
561
+ end
562
+ end
563
+
617
564
  def cve_warning_for versions, cve, link
618
565
  upgrade_version = upgrade_version? versions
619
566
  return unless upgrade_version
@@ -79,6 +79,10 @@ module Brakeman::Options
79
79
  options[:ignore_ifs] = true
80
80
  end
81
81
 
82
+ opts.on "--branch-limit LIMIT", Integer, "Limit depth of values in branches (-1 for no limit)" do |limit|
83
+ options[:branch_limit] = limit
84
+ end
85
+
82
86
  opts.on "-r", "--report-direct", "Only report direct use of untrusted data" do |option|
83
87
  options[:check_arguments] = !option
84
88
  end
@@ -149,6 +153,14 @@ module Brakeman::Options
149
153
  options[:html_style] = File.expand_path file
150
154
  end
151
155
 
156
+ opts.on "-i IGNOREFILE", "--ignore-config IGNOREFILE", "Use configuration to ignore warnings" do |file|
157
+ options[:ignore_file] = file
158
+ end
159
+
160
+ opts.on "-I", "--interactive-ignore", "Interactively ignore warnings" do
161
+ options[:interactive_ignore] = true
162
+ end
163
+
152
164
  opts.on "-l", "--[no-]combine-locations", "Combine warning locations (Default)" do |combine|
153
165
  options[:combine_locations] = combine
154
166
  end
@@ -234,6 +246,10 @@ module Brakeman::Options
234
246
  parser.parse args
235
247
  end
236
248
 
249
+ if options[:previous_results_json] and options[:output_files]
250
+ options[:comparison_output_file] = options[:output_files].shift
251
+ end
252
+
237
253
  return options, parser
238
254
  end
239
255
  end