brakeman-min 3.1.4 → 3.1.5.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +30 -0
  3. data/README.md +3 -2
  4. data/lib/brakeman.rb +4 -4
  5. data/lib/brakeman/app_tree.rb +58 -5
  6. data/lib/brakeman/call_index.rb +22 -31
  7. data/lib/brakeman/checks.rb +59 -73
  8. data/lib/brakeman/checks/base_check.rb +13 -5
  9. data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
  10. data/lib/brakeman/checks/check_cross_site_scripting.rb +12 -6
  11. data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
  12. data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
  13. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
  14. data/lib/brakeman/checks/check_render.rb +33 -3
  15. data/lib/brakeman/checks/check_route_dos.rb +42 -0
  16. data/lib/brakeman/checks/check_sanitize_methods.rb +26 -4
  17. data/lib/brakeman/checks/check_sql.rb +8 -6
  18. data/lib/brakeman/checks/check_strip_tags.rb +27 -2
  19. data/lib/brakeman/options.rb +8 -2
  20. data/lib/brakeman/processors/alias_processor.rb +14 -1
  21. data/lib/brakeman/processors/base_processor.rb +8 -0
  22. data/lib/brakeman/processors/controller_processor.rb +2 -2
  23. data/lib/brakeman/processors/erb_template_processor.rb +1 -1
  24. data/lib/brakeman/processors/erubis_template_processor.rb +1 -1
  25. data/lib/brakeman/processors/haml_template_processor.rb +2 -1
  26. data/lib/brakeman/processors/lib/basic_processor.rb +16 -0
  27. data/lib/brakeman/processors/lib/find_all_calls.rb +4 -2
  28. data/lib/brakeman/processors/lib/find_call.rb +1 -1
  29. data/lib/brakeman/processors/lib/render_path.rb +2 -1
  30. data/lib/brakeman/processors/lib/route_helper.rb +4 -0
  31. data/lib/brakeman/processors/model_processor.rb +2 -2
  32. data/lib/brakeman/report/ignore/config.rb +3 -3
  33. data/lib/brakeman/report/report_csv.rb +1 -2
  34. data/lib/brakeman/report/report_json.rb +1 -4
  35. data/lib/brakeman/scanner.rb +7 -2
  36. data/lib/brakeman/tracker.rb +21 -0
  37. data/lib/brakeman/tracker/config.rb +6 -0
  38. data/lib/brakeman/util.rb +4 -3
  39. data/lib/brakeman/version.rb +1 -1
  40. data/lib/brakeman/warning.rb +2 -2
  41. data/lib/brakeman/warning_codes.rb +9 -0
  42. data/lib/ruby_parser/bm_sexp.rb +23 -23
  43. metadata +13 -30
  44. data/lib/brakeman/report/initializers/faster_csv.rb +0 -7
  45. data/lib/brakeman/report/initializers/multi_json.rb +0 -29
@@ -35,6 +35,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
35
35
  @mass_assign_disabled = nil
36
36
  @has_user_input = nil
37
37
  @safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id]
38
+ @comparison_ops = Set[:==, :!=, :>, :<, :>=, :<=]
38
39
  end
39
40
 
40
41
  #Add result to result list, which is used to check for duplicates
@@ -71,12 +72,14 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
71
72
 
72
73
  #Process calls and check if they include user input
73
74
  def process_call exp
74
- process exp.target if sexp? exp.target
75
- process_call_args exp
75
+ unless @comparison_ops.include? exp.method
76
+ process exp.target if sexp? exp.target
77
+ process_call_args exp
78
+ end
76
79
 
77
80
  target = exp.target
78
81
 
79
- unless @safe_input_attributes.include? exp.method
82
+ unless always_safe_method? exp.method
80
83
  if params? target
81
84
  @has_user_input = Match.new(:params, exp)
82
85
  elsif cookies? target
@@ -123,6 +126,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
123
126
 
124
127
  private
125
128
 
129
+ def always_safe_method? meth
130
+ @safe_input_attributes.include? meth or
131
+ @comparison_ops.include? meth
132
+ end
133
+
126
134
  #Report a warning
127
135
  def warn options
128
136
  extra_opts = { :check => self.class.to_s }
@@ -286,7 +294,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
286
294
  def has_immediate_user_input? exp
287
295
  if exp.nil?
288
296
  false
289
- elsif call? exp and not @safe_input_attributes.include? exp.method
297
+ elsif call? exp and not always_safe_method? exp.method
290
298
  if params? exp
291
299
  return Match.new(:params, exp)
292
300
  elsif cookies? exp
@@ -345,7 +353,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
345
353
  target = exp.target
346
354
  method = exp.method
347
355
 
348
- if @safe_input_attributes.include? method
356
+ if always_safe_method? method
349
357
  false
350
358
  elsif call? target and not method.to_s[-1,1] == "?"
351
359
  if has_immediate_model?(target, out)
@@ -0,0 +1,33 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckBasicAuthTimingAttack < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check for timing attack in basic auth (CVE-2015-7576)"
7
+
8
+ def run_check
9
+ @upgrade = case
10
+ when version_between?("0.0.0", "3.2.22")
11
+ "3.2.22.1"
12
+ when version_between?("4.0.0", "4.1.14")
13
+ "4.1.14.1"
14
+ when version_between?("4.2.0", "4.2.5")
15
+ "4.2.5.1"
16
+ else
17
+ return
18
+ end
19
+
20
+ check_basic_auth_call
21
+ end
22
+
23
+ def check_basic_auth_call
24
+ tracker.find_call(target: nil, method: :http_basic_authenticate_with).each do |result|
25
+ warn :result => result,
26
+ :warning_type => "Timing Attack",
27
+ :warning_code => :CVE_2015_7576,
28
+ :message => "Basic authentication in Rails #{rails_version} is vulnerable to timing attacks. Upgrade to #@upgrade",
29
+ :confidence => CONFIDENCE[:high],
30
+ :link => "https://groups.google.com/d/msg/rubyonrails-security/ANv0HDHEC3k/mt7wNGxbFQAJ"
31
+ end
32
+ end
33
+ end
@@ -99,7 +99,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
99
99
  link_path = "cross_site_scripting"
100
100
  warning_code = :cross_site_scripting
101
101
 
102
- if node_type?(out, :call, :attrasgn) && out.method == :to_json
102
+ if node_type?(out, :call, :safe_call, :attrasgn, :safe_attrasgn) && out.method == :to_json
103
103
  message += " in JSON hash"
104
104
  link_path += "_to_json"
105
105
  warning_code = :xss_to_json
@@ -281,7 +281,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
281
281
  end
282
282
 
283
283
  def setup
284
- @ignore_methods = Set[:button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
284
+ @ignore_methods = Set[:==, :!=, :button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
285
285
  :field_field, :fields_for, :h, :hidden_field,
286
286
  :hidden_field, :hidden_field_tag, :image_tag, :label,
287
287
  :link_to, :mail_to, :radio_button, :select,
@@ -300,17 +300,23 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
300
300
  @ignore_methods << :auto_link
301
301
  end
302
302
 
303
- if version_between? "2.0.0", "2.3.14"
303
+ if version_between? "2.0.0", "2.3.14" or tracker.config.gem_version(:'rails-html-sanitizer') == '1.0.2'
304
304
  @known_dangerous << :strip_tags
305
305
  end
306
306
 
307
+ if tracker.config.has_gem? :'rails-html-sanitizer' and
308
+ version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer')
309
+
310
+ @known_dangerous << :sanitize
311
+ end
312
+
307
313
  json_escape_on = false
308
314
  initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
309
315
  initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
310
316
 
311
317
  if tracker.config.escape_html_entities_in_json?
312
318
  json_escape_on = true
313
- elsif version_between? "4.0.0", "5.0.0"
319
+ elsif version_between? "4.0.0", "9.9.9"
314
320
  json_escape_on = true
315
321
  end
316
322
 
@@ -328,7 +334,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
328
334
  end
329
335
 
330
336
  def html_safe_call? exp
331
- exp.value.node_type == :call and exp.value.method == :html_safe
337
+ call? exp.value and exp.value.method == :html_safe
332
338
  end
333
339
 
334
340
  def ignore_call? target, method
@@ -370,7 +376,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
370
376
  end
371
377
 
372
378
  def safe_input_attribute? target, method
373
- target and @safe_input_attributes.include? method
379
+ target and always_safe_method? method
374
380
  end
375
381
 
376
382
  def boolean_method? method
@@ -0,0 +1,49 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #This check looks for regexes that include user input.
4
+ class Brakeman::CheckDynamicFinders < Brakeman::BaseCheck
5
+ Brakeman::Checks.add self
6
+
7
+ @description = "Check unsafe usage of find_by_*"
8
+
9
+ def run_check
10
+ if tracker.config.has_gem? :mysql and version_between? '2.0.0', '4.1.99'
11
+ tracker.find_call(:method => /^find_by_/).each do |result|
12
+ process_result result
13
+ end
14
+ end
15
+ end
16
+
17
+ def process_result result
18
+ return if duplicate? result or result[:call].original_line
19
+ add_result result
20
+
21
+ call = result[:call]
22
+
23
+ if potentially_dangerous? call.method
24
+ call.each_arg do |arg|
25
+ if params? arg and not safe_call? arg
26
+ warn :result => result,
27
+ :warning_type => "SQL Injection",
28
+ :warning_code => :sql_injection_dynamic_finder,
29
+ :message => "MySQL integer conversion may cause 0 to match any string",
30
+ :confidence => CONFIDENCE[:med],
31
+ :user_input => arg
32
+
33
+ break
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def safe_call? arg
40
+ return false unless call? arg
41
+
42
+ meth = arg.method
43
+ meth == :to_s or meth == :to_i
44
+ end
45
+
46
+ def potentially_dangerous? method_name
47
+ method_name.match /^find_by_.*(token|guid|password|api_key|activation|code|private|reset)/
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckMimeTypeDoS < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for mime type denial of service (CVE-2016-0751)"
7
+
8
+ def run_check
9
+ fix_version = case
10
+ when version_between?("3.0.0", "3.2.22")
11
+ "3.2.22.1"
12
+ when version_between?("4.0.0", "4.1.14")
13
+ "4.1.14.1"
14
+ when version_between?("4.2.0", "4.2.5")
15
+ "4.2.5.1"
16
+ else
17
+ return
18
+ end
19
+
20
+ return if has_workaround?
21
+
22
+ message = "Rails #{rails_version} is vulnerable to denial of service via mime type caching (CVE-2016-0751). Upgrade to Rails version #{fix_version}"
23
+
24
+ warn :warning_type => "Denial of Service",
25
+ :warning_code => :CVE_2016_0751,
26
+ :message => message,
27
+ :confidence => CONFIDENCE[:med],
28
+ :gem_info => gemfile_or_environment,
29
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/9oLY_FCzvoc/w9oI9XxbFQAJ"
30
+ end
31
+
32
+ def has_workaround?
33
+ tracker.check_initializers(:Mime, :const_set).any? do |match|
34
+ arg = match.call.first_arg
35
+
36
+ symbol? arg and arg.value == :LOOKUP
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #https://groups.google.com/d/msg/rubyonrails-security/cawsWcQ6c8g/tegZtYdbFQAJ
4
+ class Brakeman::CheckNestedAttributesBypass < Brakeman::BaseCheck
5
+ Brakeman::Checks.add self
6
+
7
+ @description = "Checks for nested attributes vulnerability (CVE-2015-7577)"
8
+
9
+ def run_check
10
+ if version_between? "3.1.0", "3.2.22" or
11
+ version_between? "4.0.0", "4.1.14" or
12
+ version_between? "4.2.0", "4.2.5"
13
+
14
+ unless workaround?
15
+ check_nested_attributes
16
+ end
17
+ end
18
+ end
19
+
20
+ def check_nested_attributes
21
+ active_record_models.each do |name, model|
22
+ if opts = model.options[:accepts_nested_attributes_for]
23
+ opts.each do |args|
24
+ if args.any? { |a| allow_destroy? a } and args.any? { |a| reject_if? a }
25
+ warn_about_nested_attributes name, model, args
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def warn_about_nested_attributes name, model, args
33
+ message = "Rails #{rails_version} does not call :reject_if option when :allow_destroy is false (CVE-2015-7577)"
34
+
35
+ warn :model => name,
36
+ :warning_type => "Nested Attributes",
37
+ :warning_code => :CVE_2015_7577,
38
+ :message => message,
39
+ :file => model.file,
40
+ :line => args.line,
41
+ :confidence => CONFIDENCE[:med],
42
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/cawsWcQ6c8g/tegZtYdbFQAJ"
43
+ end
44
+
45
+ def allow_destroy? arg
46
+ hash? arg and
47
+ false? hash_access(arg, :allow_destroy)
48
+ end
49
+
50
+ def reject_if? arg
51
+ hash? arg and
52
+ hash_access(arg, :reject_if)
53
+ end
54
+
55
+ def workaround?
56
+ tracker.check_initializers([], :will_be_destroyed?).any?
57
+ end
58
+ end
@@ -4,7 +4,7 @@ require 'brakeman/checks/base_check'
4
4
  class Brakeman::CheckRender < Brakeman::BaseCheck
5
5
  Brakeman::Checks.add self
6
6
 
7
- @description = "Finds calls to render that might allow file access"
7
+ @description = "Finds calls to render that might allow file access or code execution"
8
8
 
9
9
  def run_check
10
10
  tracker.find_call(:target => nil, :method => :render).each do |result|
@@ -17,7 +17,8 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
17
17
 
18
18
  case result[:call].render_type
19
19
  when :partial, :template, :action, :file
20
- check_for_dynamic_path result
20
+ check_for_rce(result) or
21
+ check_for_dynamic_path(result)
21
22
  when :inline
22
23
  when :js
23
24
  when :json
@@ -34,7 +35,6 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
34
35
  if sexp? view and not duplicate? result
35
36
  add_result result
36
37
 
37
-
38
38
  if input = has_immediate_user_input?(view)
39
39
  if string_interp? view
40
40
  confidence = CONFIDENCE[:med]
@@ -48,6 +48,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
48
48
  end
49
49
 
50
50
  return if input.type == :model #skip models
51
+ return if safe_param? input.match
51
52
 
52
53
  message = "Render path contains #{friendly_type_of input}"
53
54
 
@@ -59,4 +60,33 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
59
60
  :confidence => confidence
60
61
  end
61
62
  end
63
+
64
+ def check_for_rce result
65
+ return unless version_between? "0.0.0", "3.2.22" or
66
+ version_between? "4.0.0", "4.1.14" or
67
+ version_between? "4.2.0", "4.2.5"
68
+
69
+
70
+ view = result[:call][2]
71
+ if sexp? view and not duplicate? result
72
+ if params? view
73
+ add_result result
74
+ return if safe_param? view
75
+
76
+ warn :result => result,
77
+ :warning_type => "Remote Code Execution",
78
+ :warning_code => :dynamic_render_path_rce,
79
+ :message => "Passing query parameters to render() is vulnerable in Rails #{rails_version} (CVE-2016-0752)",
80
+ :user_input => view,
81
+ :confidence => CONFIDENCE[:high]
82
+ end
83
+ end
84
+ end
85
+
86
+ def safe_param? exp
87
+ if params? exp and call? exp and exp.method == :[]
88
+ arg = exp.first_arg
89
+ symbol? arg and [:controller, :action].include? arg.value
90
+ end
91
+ end
62
92
  end
@@ -0,0 +1,42 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckRouteDoS < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for route DoS (CVE-2015-7581)"
7
+
8
+ def run_check
9
+ fix_version = case
10
+ when version_between?("4.0.0", "4.1.14")
11
+ "4.1.14.1"
12
+ when version_between?("4.2.0", "4.2.5")
13
+ "4.2.5.1"
14
+ else
15
+ return
16
+ end
17
+
18
+ if controller_wildcards?
19
+ message = "Rails #{rails_version} has a denial of service vulnerability with :controller routes (CVE-2015-7581). Upgrade to Rails #{fix_version}"
20
+
21
+ warn :warning_type => "Denial of Service",
22
+ :warning_code => :CVE_2015_7581,
23
+ :message => message,
24
+ :confidence => CONFIDENCE[:med],
25
+ :gem_info => gemfile_or_environment,
26
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/dthJ5wL69JE/YzPnFelbFQAJ"
27
+ end
28
+ end
29
+
30
+ def controller_wildcards?
31
+ tracker.routes.each do |name, actions|
32
+ if name == :':controllerController'
33
+ # awful hack for routes with :controller in them
34
+ return true
35
+ elsif string? actions and actions.value.include? ":controller"
36
+ return true
37
+ end
38
+ end
39
+
40
+ false
41
+ end
42
+ end
@@ -17,12 +17,17 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
17
17
  '3.1.12'
18
18
  when version_between?('3.2.0', '3.2.12')
19
19
  '3.2.13'
20
- else
21
- return
22
20
  end
23
21
 
24
- check_cve_2013_1855
25
- check_cve_2013_1857
22
+ if @fix_version
23
+ check_cve_2013_1855
24
+ check_cve_2013_1857
25
+ elsif tracker.config.has_gem? :'rails-html-sanitizer' and
26
+ version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer')
27
+
28
+ warn_sanitizer_cve "CVE-2015-7578", "https://groups.google.com/d/msg/rubyonrails-security/uh--W4TDwmI/JbvSRpdbFQAJ"
29
+ warn_sanitizer_cve "CVE-2015-7580", "https://groups.google.com/d/msg/rubyonrails-security/uh--W4TDwmI/m_CVZtdbFQAJ"
30
+ end
26
31
  end
27
32
 
28
33
  def check_cve_2013_1855
@@ -54,4 +59,21 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
54
59
  :link_path => link
55
60
  end
56
61
  end
62
+
63
+ def warn_sanitizer_cve cve, link
64
+ message = "rails-html-sanitizer #{tracker.config.gem_version(:'rails-html-sanitizer')} is vulnerable (#{cve}). Upgrade to 1.0.3"
65
+
66
+ if tracker.find_call(:target => false, :method => :sanitize).any?
67
+ confidence = CONFIDENCE[:high]
68
+ else
69
+ confidence = CONFIDENCE[:med]
70
+ end
71
+
72
+ warn :warning_type => "Cross Site Scripting",
73
+ :warning_code => cve.tr('-', '_').to_sym,
74
+ :message => message,
75
+ :gem_info => gemfile_or_environment,
76
+ :confidence => confidence,
77
+ :link_path => link
78
+ end
57
79
  end