brakeman 1.5.3 → 1.6.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/bin/brakeman +20 -6
  2. data/lib/brakeman.rb +21 -1
  3. data/lib/brakeman/checks.rb +2 -7
  4. data/lib/brakeman/checks/base_check.rb +31 -26
  5. data/lib/brakeman/checks/check_basic_auth.rb +1 -7
  6. data/lib/brakeman/checks/check_cross_site_scripting.rb +38 -33
  7. data/lib/brakeman/checks/check_evaluation.rb +2 -1
  8. data/lib/brakeman/checks/check_execute.rb +5 -1
  9. data/lib/brakeman/checks/check_file_access.rb +6 -4
  10. data/lib/brakeman/checks/check_link_to.rb +8 -5
  11. data/lib/brakeman/checks/check_link_to_href.rb +6 -5
  12. data/lib/brakeman/checks/check_mail_to.rb +2 -4
  13. data/lib/brakeman/checks/check_mass_assignment.rb +12 -6
  14. data/lib/brakeman/checks/check_redirect.rb +17 -14
  15. data/lib/brakeman/checks/check_render.rb +4 -4
  16. data/lib/brakeman/checks/check_send.rb +4 -2
  17. data/lib/brakeman/checks/check_session_settings.rb +16 -21
  18. data/lib/brakeman/checks/check_skip_before_filter.rb +2 -4
  19. data/lib/brakeman/checks/check_sql.rb +8 -7
  20. data/lib/brakeman/checks/check_validation_regex.rb +2 -4
  21. data/lib/brakeman/checks/check_without_protection.rb +8 -9
  22. data/lib/brakeman/differ.rb +61 -0
  23. data/lib/brakeman/format/style.css +4 -0
  24. data/lib/brakeman/options.rb +8 -0
  25. data/lib/brakeman/processors/alias_processor.rb +5 -7
  26. data/lib/brakeman/processors/controller_alias_processor.rb +10 -3
  27. data/lib/brakeman/processors/erb_template_processor.rb +2 -0
  28. data/lib/brakeman/processors/erubis_template_processor.rb +2 -0
  29. data/lib/brakeman/processors/gem_processor.rb +8 -0
  30. data/lib/brakeman/processors/haml_template_processor.rb +2 -0
  31. data/lib/brakeman/processors/lib/rails2_route_processor.rb +2 -6
  32. data/lib/brakeman/processors/lib/rails3_route_processor.rb +2 -4
  33. data/lib/brakeman/processors/lib/render_helper.rb +3 -1
  34. data/lib/brakeman/processors/library_processor.rb +4 -6
  35. data/lib/brakeman/processors/template_alias_processor.rb +1 -1
  36. data/lib/brakeman/report.rb +257 -198
  37. data/lib/brakeman/rescanner.rb +112 -10
  38. data/lib/brakeman/scanner.rb +3 -4
  39. data/lib/brakeman/templates/controller_overview.html.erb +18 -0
  40. data/lib/brakeman/templates/controller_warnings.html.erb +17 -0
  41. data/lib/brakeman/templates/error_overview.html.erb +14 -0
  42. data/lib/brakeman/templates/header.html.erb +38 -0
  43. data/lib/brakeman/templates/model_warnings.html.erb +17 -0
  44. data/lib/brakeman/templates/overview.html.erb +28 -0
  45. data/lib/brakeman/templates/security_warnings.html.erb +28 -0
  46. data/lib/brakeman/templates/template_overview.html.erb +17 -0
  47. data/lib/brakeman/templates/view_warnings.html.erb +17 -0
  48. data/lib/brakeman/templates/warning_overview.html.erb +13 -0
  49. data/lib/brakeman/tracker.rb +1 -1
  50. data/lib/brakeman/util.rb +24 -4
  51. data/lib/brakeman/version.rb +1 -1
  52. data/lib/brakeman/warning.rb +11 -3
  53. data/lib/ruby_parser/bm_sexp.rb +5 -11
  54. metadata +84 -23
@@ -40,10 +40,9 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
40
40
  #with something before the user input
41
41
  return if node_type?(url_arg, :string_interp) && !url_arg[1].chomp.empty?
42
42
 
43
- type, match = has_immediate_user_input? url_arg
44
43
 
45
- if type
46
- case type
44
+ if input = has_immediate_user_input?(url_arg)
45
+ case input.type
47
46
  when :params
48
47
  message = "Unsafe parameter value in link_to href"
49
48
  when :cookies
@@ -57,6 +56,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
57
56
  warn :result => result,
58
57
  :warning_type => "Cross Site Scripting",
59
58
  :message => message,
59
+ :user_input => input.match,
60
60
  :confidence => CONFIDENCE[:high]
61
61
  end
62
62
  elsif has_immediate_model? url_arg
@@ -72,9 +72,9 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
72
72
  # attack.
73
73
 
74
74
  elsif @matched
75
- if @matched == :model and not tracker.options[:ignore_model_output]
75
+ if @matched.type == :model and not tracker.options[:ignore_model_output]
76
76
  message = "Unsafe model attribute in link_to href"
77
- elsif @matched == :params
77
+ elsif @matched.type == :params
78
78
  message = "Unsafe parameter value in link_to href"
79
79
  end
80
80
 
@@ -83,6 +83,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
83
83
  warn :result => result,
84
84
  :warning_type => "Cross Site Scripting",
85
85
  :message => message,
86
+ :user_input => @matched.match,
86
87
  :confidence => CONFIDENCE[:med]
87
88
  end
88
89
  end
@@ -38,10 +38,8 @@ class Brakeman::CheckMailTo < Brakeman::BaseCheck
38
38
 
39
39
  args.each do |arg|
40
40
  if hash? arg
41
- hash_iterate arg do |k, v|
42
- if symbol? v and v[-1] == :javascript
43
- return result
44
- end
41
+ if hash_access(arg, :javascript)
42
+ return result
45
43
  end
46
44
  end
47
45
  end
@@ -21,12 +21,10 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
21
21
 
22
22
  return if models.empty?
23
23
 
24
- @results = Set.new
25
24
 
26
25
  Brakeman.debug "Finding possible mass assignment calls on #{models.length} models"
27
26
  calls = tracker.find_call :chained => true, :targets => models, :methods => [:new,
28
27
  :attributes=,
29
- :update_attribute,
30
28
  :update_attributes,
31
29
  :update_attributes!,
32
30
  :create,
@@ -45,8 +43,8 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
45
43
 
46
44
  check = check_call call
47
45
 
48
- if check and not @results.include? call
49
- @results << call
46
+ if check and not call.original_line and not duplicate? res
47
+ add_result res
50
48
 
51
49
  model = tracker.models[res[:chain].first]
52
50
 
@@ -54,10 +52,17 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
54
52
 
55
53
  if attr_protected and tracker.options[:ignore_attr_protected]
56
54
  return
57
- elsif include_user_input? call[3] and not hash? call[3][1] and not attr_protected
58
- confidence = CONFIDENCE[:high]
55
+ elsif input = include_user_input?(call[3])
56
+ if not hash? call[3][1] and not attr_protected
57
+ confidence = CONFIDENCE[:high]
58
+ user_input = input.match
59
+ else
60
+ confidence = CONFIDENCE[:low]
61
+ user_input = input.match
62
+ end
59
63
  else
60
64
  confidence = CONFIDENCE[:low]
65
+ user_input = nil
61
66
  end
62
67
 
63
68
  warn :result => res,
@@ -65,6 +70,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
65
70
  :message => "Unprotected mass assignment",
66
71
  :line => call.line,
67
72
  :code => call,
73
+ :user_input => user_input,
68
74
  :confidence => confidence
69
75
  end
70
76
 
@@ -19,12 +19,16 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
19
19
  end
20
20
 
21
21
  def process_result result
22
+ return if duplicate? result
23
+
22
24
  call = result[:call]
23
25
 
24
26
  method = call[2]
25
27
 
26
28
  if method == :redirect_to and not only_path?(call) and res = include_user_input?(call)
27
- if res == :immediate
29
+ add_result result
30
+
31
+ if res.type == :immediate
28
32
  confidence = CONFIDENCE[:high]
29
33
  else
30
34
  confidence = CONFIDENCE[:low]
@@ -35,6 +39,7 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
35
39
  :message => "Possible unprotected redirect",
36
40
  :line => call.line,
37
41
  :code => call,
42
+ :user_input => res.match,
38
43
  :confidence => confidence
39
44
  end
40
45
  end
@@ -60,16 +65,18 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
60
65
 
61
66
  call[3].each do |arg|
62
67
  if call? arg
63
- if ALL_PARAMETERS.include? arg or arg[2] == COOKIES
64
- return :immediate
68
+ if request_value? arg
69
+ return Match.new(:immediate, arg)
70
+ elsif request_value? arg[1]
71
+ return Match.new(:immediate, arg[1])
65
72
  elsif arg[2] == :url_for and include_user_input? arg
66
- return :immediate
73
+ return Match.new(:immediate, arg)
67
74
  #Ignore helpers like some_model_url?
68
75
  elsif arg[2].to_s =~ /_(url|path)$/
69
76
  return false
70
77
  end
71
- elsif params? arg or cookies? arg
72
- return :immediate
78
+ elsif request_value? arg
79
+ return Match.new(:immediate, arg)
73
80
  end
74
81
  end
75
82
 
@@ -85,10 +92,8 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
85
92
  def only_path? call
86
93
  call[3].each do |arg|
87
94
  if hash? arg
88
- hash_iterate(arg) do |k,v|
89
- if symbol? k and k[1] == :only_path and v.is_a? Sexp and v[0] == :true
90
- return true
91
- end
95
+ if value = hash_access(arg, :only_path)
96
+ return true if true?(value)
92
97
  end
93
98
  elsif call? arg and arg[2] == :url_for
94
99
  return check_url_for(arg)
@@ -103,10 +108,8 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
103
108
  def check_url_for call
104
109
  call[3].each do |arg|
105
110
  if hash? arg
106
- hash_iterate(arg) do |k,v|
107
- if symbol? k and k[1] == :only_path and v.is_a? Sexp and v[0] == :false
108
- return false
109
- end
111
+ if value = hash_access(arg, :only_path)
112
+ return false if false?(value)
110
113
  end
111
114
  end
112
115
  end
@@ -32,11 +32,10 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
32
32
  if sexp? view and not duplicate? result
33
33
  add_result result
34
34
 
35
- type, match = has_immediate_user_input? view
36
35
 
37
- if type
36
+ if input = has_immediate_user_input?(view)
38
37
  confidence = CONFIDENCE[:high]
39
- elsif type = include_user_input?(view)
38
+ elsif input = include_user_input?(view)
40
39
  if node_type? view, :string_interp, :dstr
41
40
  confidence = CONFIDENCE[:med]
42
41
  else
@@ -48,7 +47,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
48
47
 
49
48
  message = "Render path contains "
50
49
 
51
- case type
50
+ case input.type
52
51
  when :params
53
52
  message << "parameter value"
54
53
  when :cookies
@@ -66,6 +65,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
66
65
  warn :result => result,
67
66
  :warning_type => "Dynamic Render Path",
68
67
  :message => message,
68
+ :user_input => input.match,
69
69
  :confidence => confidence
70
70
  end
71
71
  end
@@ -19,19 +19,21 @@ class Brakeman::CheckSend < Brakeman::BaseCheck
19
19
  args = process result[:call][3]
20
20
  target = process result[:call][1]
21
21
 
22
- if has_immediate_user_input? args[1]
22
+ if input = has_immediate_user_input?(args[1])
23
23
  warn :result => result,
24
24
  :warning_type => "Dangerous Send",
25
25
  :message => "User controlled method execution",
26
26
  :code => result[:call],
27
+ :user_input => input.match,
27
28
  :confidence => CONFIDENCE[:high]
28
29
  end
29
30
 
30
- if has_immediate_user_input?(target)
31
+ if input = has_immediate_user_input?(target)
31
32
  warn :result => result,
32
33
  :warning_type => "Dangerous Send",
33
34
  :message => "User defined target of method invocation",
34
35
  :code => result[:call],
36
+ :user_input => input.match,
35
37
  :confidence => CONFIDENCE[:med]
36
38
  end
37
39
  end
@@ -52,30 +52,25 @@ class Brakeman::CheckSessionSettings < Brakeman::BaseCheck
52
52
 
53
53
  def check_for_issues settings, file
54
54
  if settings and hash? settings
55
- hash_iterate settings do |key, value|
56
- if symbol? key
57
-
58
- if key[1] == :session_http_only and
59
- sexp? value and
60
- value.node_type == :false
61
-
62
- warn :warning_type => "Session Setting",
63
- :message => "Session cookies should be set to HTTP only",
64
- :confidence => CONFIDENCE[:high],
65
- :line => key.line,
66
- :file => file
55
+ if value = hash_access(settings, :session_http_only)
56
+ if false? value
57
+ warn :warning_type => "Session Setting",
58
+ :message => "Session cookies should be set to HTTP only",
59
+ :confidence => CONFIDENCE[:high],
60
+ :line => value.line,
61
+ :file => file
62
+ end
63
+ end
67
64
 
68
- elsif key[1] == :secret and
69
- string? value and
70
- value[1].length < 30
65
+ if value = hash_access(settings, :secret)
66
+ if string? value and value[1].length < 30
71
67
 
72
- warn :warning_type => "Session Setting",
73
- :message => "Session secret should be at least 30 characters long",
74
- :confidence => CONFIDENCE[:high],
75
- :line => key.line,
76
- :file => file
68
+ warn :warning_type => "Session Setting",
69
+ :message => "Session secret should be at least 30 characters long",
70
+ :confidence => CONFIDENCE[:high],
71
+ :line => value.line,
72
+ :file => file
77
73
 
78
- end
79
74
  end
80
75
  end
81
76
  end
@@ -39,10 +39,8 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
39
39
  args = filter[3]
40
40
 
41
41
  if symbol? args[1] and args[1][1] == :verify_authenticity_token and hash? args.last
42
- hash_iterate args.last do |k, v|
43
- if symbol? k and k[1] == :except
44
- return true
45
- end
42
+ if hash_access(args.last, :except)
43
+ return true
46
44
  end
47
45
  end
48
46
 
@@ -17,9 +17,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
17
17
  @rails_version = tracker.config[:rails_version]
18
18
 
19
19
  if tracker.options[:rails3]
20
- @sql_targets = /^(find.*|last|first|all|count|sum|average|minumum|maximum|count_by_sql|where|order|group|having)$/
20
+ @sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql|where|order|group|having)$/
21
21
  else
22
- @sql_targets = /^(find.*|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/
22
+ @sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/
23
23
  end
24
24
 
25
25
  Brakeman.debug "Finding possible SQL calls on models"
@@ -122,15 +122,18 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
122
122
  if failed and not call.original_line and not duplicate? result
123
123
  add_result result
124
124
 
125
- if include_user_input? args[-1]
125
+ if input = include_user_input?(args[-1])
126
126
  confidence = CONFIDENCE[:high]
127
+ user_input = input.match
127
128
  else
128
129
  confidence = CONFIDENCE[:med]
130
+ user_input = nil
129
131
  end
130
132
 
131
133
  warn :result => result,
132
134
  :warning_type => "SQL Injection",
133
135
  :message => "Possible SQL injection",
136
+ :user_input => user_input,
134
137
  :confidence => confidence
135
138
  end
136
139
 
@@ -215,10 +218,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
215
218
  def check_for_limit_or_offset_vulnerability options
216
219
  return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash? options
217
220
 
218
- hash_iterate(options) do |key, value|
219
- if symbol? key
220
- return (key[1] == :limit or key[1] == :offset)
221
- end
221
+ if hash_access(options, :limit) or hash_access(options[:offset])
222
+ return true
222
223
  end
223
224
 
224
225
  false
@@ -28,10 +28,8 @@ class Brakeman::CheckValidationRegex < Brakeman::BaseCheck
28
28
 
29
29
  #Check validates_format_of
30
30
  def process_validator validator
31
- hash_iterate(validator[-1]) do |key, value|
32
- if key == WITH
33
- check_regex value, validator
34
- end
31
+ if value = hash_access(validator[-1], WITH)
32
+ check_regex value, validator
35
33
  end
36
34
  end
37
35
 
@@ -23,12 +23,9 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
23
23
 
24
24
  return if models.empty?
25
25
 
26
- @results = Set.new
27
-
28
26
  Brakeman.debug "Finding all mass assignments"
29
27
  calls = tracker.find_call :targets => models, :methods => [:new,
30
28
  :attributes=,
31
- :update_attribute,
32
29
  :update_attributes,
33
30
  :update_attributes!,
34
31
  :create,
@@ -45,16 +42,18 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
45
42
  call = res[:call]
46
43
  last_arg = call[3][-1]
47
44
 
48
- if hash? last_arg and not @results.include? call
45
+ if hash? last_arg and not call.original_line and not duplicate? res
49
46
 
50
- hash_iterate(last_arg) do |k,v|
51
- if symbol? k and k[1] == :without_protection and v[0] == :true
52
- @results << call
47
+ if value = hash_access(last_arg, :without_protection)
48
+ if true? value
49
+ add_result res
53
50
 
54
- if include_user_input? call[3]
51
+ if input = include_user_input?(call[3])
55
52
  confidence = CONFIDENCE[:high]
53
+ user_input = input.match
56
54
  else
57
55
  confidence = CONFIDENCE[:med]
56
+ user_input = nil
58
57
  end
59
58
 
60
59
  warn :result => res,
@@ -62,9 +61,9 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
62
61
  :message => "Unprotected mass assignment",
63
62
  :line => call.line,
64
63
  :code => call,
64
+ :user_input => user_input,
65
65
  :confidence => confidence
66
66
 
67
- break
68
67
  end
69
68
  end
70
69
  end
@@ -0,0 +1,61 @@
1
+ # extracting the diff logic to it's own class for consistency. Currently handles
2
+ # an array of Brakeman::Warnings or plain hash representations.
3
+ class Brakeman::Differ
4
+ DEFAULT_HASH = {:new => [], :fixed => []}
5
+ attr_reader :old_warnings, :new_warnings
6
+
7
+ def initialize new_warnings, old_warnings
8
+ @new_warnings = new_warnings
9
+ @old_warnings = old_warnings
10
+ end
11
+
12
+ def diff
13
+ # get the type of elements
14
+ return DEFAULT_HASH if @new_warnings.empty?
15
+
16
+ warnings = {}
17
+ warnings[:new] = @new_warnings - @old_warnings
18
+ warnings[:fixed] = @old_warnings - @new_warnings
19
+
20
+ second_pass(warnings)
21
+ end
22
+
23
+ # second pass to cleanup any vulns which have changed in line number only.
24
+ # Given a list of new warnings, delete pairs of new/fixed vulns that differ
25
+ # only by line number.
26
+ # Horrible O(n^2) performance. Keep n small :-/
27
+ def second_pass(warnings)
28
+ # keep track of the number of elements deleted because the index numbers
29
+ # won't update as the list is modified
30
+ elements_deleted_offset = 0
31
+
32
+ # dup this list since we will be deleting from it and the iterator gets confused.
33
+ # use _with_index for fast deletion as opposed to .reject!{|obj| obj == *_warning}
34
+ warnings[:new].dup.each_with_index do |new_warning, new_warning_id|
35
+ warnings[:fixed].each_with_index do |fixed_warning, fixed_warning_id|
36
+ if eql_except_line_number new_warning, fixed_warning
37
+ warnings[:new].delete_at(new_warning_id - elements_deleted_offset)
38
+ elements_deleted_offset += 1
39
+ warnings[:fixed].delete_at(fixed_warning_id)
40
+ break
41
+ end
42
+ end
43
+ end
44
+
45
+ warnings
46
+ end
47
+
48
+ def eql_except_line_number new_warning, fixed_warning
49
+ # can't do this ahead of time, as callers may be expecting a Brakeman::Warning
50
+ if new_warning.is_a? Brakeman::Warning
51
+ new_warning = new_warning.to_hash
52
+ fixed_warning = fixed_warning.to_hash
53
+ end
54
+
55
+ new_warning.keys.reject{|k,v| k == :line}.each do |attr|
56
+ return false if new_warning[attr] != fixed_warning[attr]
57
+ end
58
+
59
+ true
60
+ end
61
+ end