brakeman 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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