brakeman 1.5.3 → 1.6.0.pre1

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 (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