brakeman 1.9.5 → 2.0.0.pre2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +27 -0
  3. data/README.md +5 -2
  4. data/bin/brakeman +20 -15
  5. data/lib/brakeman.rb +106 -80
  6. data/lib/brakeman/app_tree.rb +22 -11
  7. data/lib/brakeman/call_index.rb +4 -4
  8. data/lib/brakeman/checks/base_check.rb +33 -5
  9. data/lib/brakeman/checks/check_basic_auth.rb +2 -1
  10. data/lib/brakeman/checks/check_content_tag.rb +8 -29
  11. data/lib/brakeman/checks/check_cross_site_scripting.rb +10 -19
  12. data/lib/brakeman/checks/check_deserialize.rb +57 -0
  13. data/lib/brakeman/checks/check_execute.rb +3 -11
  14. data/lib/brakeman/checks/check_file_access.rb +1 -14
  15. data/lib/brakeman/checks/check_forgery_setting.rb +5 -4
  16. data/lib/brakeman/checks/check_link_to.rb +4 -15
  17. data/lib/brakeman/checks/check_link_to_href.rb +1 -8
  18. data/lib/brakeman/checks/check_mass_assignment.rb +6 -2
  19. data/lib/brakeman/checks/check_model_attributes.rb +1 -0
  20. data/lib/brakeman/checks/check_model_serialize.rb +2 -1
  21. data/lib/brakeman/checks/check_render.rb +2 -15
  22. data/lib/brakeman/checks/check_select_tag.rb +1 -1
  23. data/lib/brakeman/checks/check_select_vulnerability.rb +2 -2
  24. data/lib/brakeman/checks/check_send.rb +3 -0
  25. data/lib/brakeman/checks/check_session_settings.rb +2 -3
  26. data/lib/brakeman/checks/check_skip_before_filter.rb +5 -2
  27. data/lib/brakeman/checks/check_sql.rb +59 -50
  28. data/lib/brakeman/checks/check_symbol_dos.rb +5 -14
  29. data/lib/brakeman/checks/check_unsafe_reflection.rb +2 -15
  30. data/lib/brakeman/options.rb +14 -9
  31. data/lib/brakeman/processors/controller_alias_processor.rb +4 -10
  32. data/lib/brakeman/processors/controller_processor.rb +53 -14
  33. data/lib/brakeman/processors/lib/find_all_calls.rb +40 -40
  34. data/lib/brakeman/processors/lib/processor_helper.rb +5 -1
  35. data/lib/brakeman/processors/lib/rails3_config_processor.rb +8 -0
  36. data/lib/brakeman/processors/output_processor.rb +23 -1
  37. data/lib/brakeman/report.rb +28 -14
  38. data/lib/brakeman/scanner.rb +5 -3
  39. data/lib/brakeman/tracker.rb +7 -7
  40. data/lib/brakeman/util.rb +5 -3
  41. data/lib/brakeman/version.rb +1 -1
  42. data/lib/brakeman/warning.rb +12 -9
  43. data/lib/ruby_parser/bm_sexp.rb +5 -1
  44. data/lib/tasks/brakeman.rake +10 -0
  45. metadata +11 -9
  46. data/lib/brakeman/checks/check_yaml_load.rb +0 -55
@@ -82,13 +82,13 @@ class Brakeman::CallIndex
82
82
  def remove_indexes_by_class classes
83
83
  @calls_by_method.each do |name, calls|
84
84
  calls.delete_if do |call|
85
- call[:location][0] == :class and classes.include? call[:location][1]
85
+ call[:location][:type] == :class and classes.include? call[:location][:class]
86
86
  end
87
87
  end
88
88
 
89
89
  @calls_by_target.each do |name, calls|
90
90
  calls.delete_if do |call|
91
- call[:location][0] == :class and classes.include? call[:location][1]
91
+ call[:location][:type] == :class and classes.include? call[:location][:class]
92
92
  end
93
93
  end
94
94
  end
@@ -206,8 +206,8 @@ class Brakeman::CallIndex
206
206
  end
207
207
 
208
208
  def from_template call, template_name
209
- return false unless call[:location][0] == :template
209
+ return false unless call[:location][:type] == :template
210
210
  return true if template_name.nil?
211
- call[:location][1] == template_name
211
+ call[:location][:template] == template_name
212
212
  end
213
213
  end
@@ -12,6 +12,14 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
12
12
  CONFIDENCE = { :high => 0, :med => 1, :low => 2 }
13
13
 
14
14
  Match = Struct.new(:type, :match)
15
+
16
+ class << self
17
+ attr_accessor :name
18
+
19
+ def inherited(subclass)
20
+ subclass.name = subclass.to_s.match(/^Brakeman::(.*)$/)[1]
21
+ end
22
+ end
15
23
 
16
24
  #Initialize Check with Checks.
17
25
  def initialize(app_tree, tracker)
@@ -26,12 +34,12 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
26
34
  @active_record_models = nil
27
35
  @mass_assign_disabled = nil
28
36
  @has_user_input = nil
29
- @safe_input_attributes = Set[:to_i, :to_f, :arel_table]
37
+ @safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id]
30
38
  end
31
39
 
32
40
  #Add result to result list, which is used to check for duplicates
33
41
  def add_result result, location = nil
34
- location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][1]
42
+ location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
35
43
  location = location[:name] if location.is_a? Hash
36
44
  location = location.to_sym
37
45
 
@@ -116,9 +124,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
116
124
 
117
125
  #Report a warning
118
126
  def warn options
119
- extra_opts = { :check => self.class.to_s, :relative_file => relative_path(options[:file]) }
127
+ extra_opts = { :check => self.class.to_s }
128
+
120
129
  warning = Brakeman::Warning.new(options.merge(extra_opts))
121
130
  warning.file = file_for warning
131
+ warning.relative_path = relative_path(warning.file)
122
132
 
123
133
  @warnings << warning
124
134
  end
@@ -163,7 +173,6 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
163
173
  @mass_assign_disabled = false
164
174
 
165
175
  if version_between?("3.1.0", "3.9.9") and
166
- tracker.config[:rails] and
167
176
  tracker.config[:rails][:active_record] and
168
177
  tracker.config[:rails][:active_record][:whitelist_attributes] == Sexp.new(:true)
169
178
 
@@ -244,7 +253,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
244
253
  raise ArgumentError
245
254
  end
246
255
 
247
- location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][1]
256
+ location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
248
257
 
249
258
  location = location[:name] if location.is_a? Hash
250
259
  location = location.to_sym
@@ -507,4 +516,23 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
507
516
 
508
517
  @active_record_models
509
518
  end
519
+
520
+ def friendly_type_of input_type
521
+ if input_type.is_a? Match
522
+ input_type = input_type.type
523
+ end
524
+
525
+ case input_type
526
+ when :params
527
+ "parameter value"
528
+ when :cookies
529
+ "cookie value"
530
+ when :request
531
+ "request value"
532
+ when :model
533
+ "model attribute"
534
+ else
535
+ "user input"
536
+ end
537
+ end
510
538
  end
@@ -25,7 +25,8 @@ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
25
25
  :warning_code => :basic_auth_password,
26
26
  :message => "Basic authentication password stored in source code",
27
27
  :code => call,
28
- :confidence => 0
28
+ :confidence => 0,
29
+ :file => controller[:file]
29
30
 
30
31
  break
31
32
  end
@@ -45,7 +45,7 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
45
45
 
46
46
  call = result[:call] = result[:call].dup
47
47
 
48
- args = call.arglist
48
+ args = call.arglist
49
49
 
50
50
  tag_name = args[1]
51
51
  content = args[2]
@@ -94,19 +94,12 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
94
94
  end
95
95
 
96
96
  if input = has_immediate_user_input?(arg)
97
- case input.type
98
- when :params
99
- message = "Unescaped parameter value in content_tag"
100
- when :cookies
101
- message = "Unescaped cookie value in content_tag"
102
- else
103
- message = "Unescaped user input value in content_tag"
104
- end
97
+ message = "Unescaped #{friendly_type_of input} in content_tag"
105
98
 
106
99
  add_result result
107
100
 
108
101
  warn :result => result,
109
- :warning_type => "Cross Site Scripting",
102
+ :warning_type => "Cross Site Scripting",
110
103
  :warning_code => :xss_content_tag,
111
104
  :message => message,
112
105
  :user_input => input.match,
@@ -126,7 +119,7 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
126
119
  end
127
120
 
128
121
  warn :result => result,
129
- :warning_type => "Cross Site Scripting",
122
+ :warning_type => "Cross Site Scripting",
130
123
  :warning_code => :xss_content_tag,
131
124
  :message => "Unescaped model attribute in content_tag",
132
125
  :user_input => match,
@@ -135,28 +128,14 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
135
128
  end
136
129
 
137
130
  elsif @matched
138
- message = "Unescaped "
139
-
140
- case @matched.type
141
- when :model
142
- return if tracker.options[:ignore_model_output]
143
- message << "model attribute"
144
- when :params
145
- message << "parameter"
146
- when :cookies
147
- message << "cookie"
148
- when :session
149
- message << "session"
150
- else
151
- message << "user input"
152
- end
131
+ return if @matched.type == :model and tracker.options[:ignore_model_output]
153
132
 
154
- message << " value in content_tag"
133
+ message = "Unescaped #{friendly_type_of @matched} in content_tag"
155
134
 
156
135
  add_result result
157
136
 
158
- warn :result => result,
159
- :warning_type => "Cross Site Scripting",
137
+ warn :result => result,
138
+ :warning_type => "Cross Site Scripting",
160
139
  :warning_code => :xss_content_tag,
161
140
  :message => message,
162
141
  :user_input => @matched.match,
@@ -62,10 +62,17 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
62
62
  initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
63
63
  initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
64
64
 
65
+ if tracker.config[:rails][:active_support] and
66
+ true? tracker.config[:rails][:active_support][:escape_html_entities_in_json]
67
+
68
+ json_escape_on = true
69
+ end
70
+
65
71
  if !json_escape_on or version_between? "0.0.0", "2.0.99"
66
72
  @known_dangerous << :to_json
67
73
  Brakeman.debug("Automatic to_json escaping not enabled, consider to_json dangerous")
68
74
  else
75
+ @safe_input_attributes << :to_json
69
76
  Brakeman.debug("Automatic to_json escaping is enabled.")
70
77
  end
71
78
 
@@ -97,16 +104,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
97
104
  if input = has_immediate_user_input?(out)
98
105
  add_result exp
99
106
 
100
- case input.type
101
- when :params
102
- message = "Unescaped parameter value"
103
- when :cookies
104
- message = "Unescaped cookie value"
105
- when :request
106
- message = "Unescaped request value"
107
- else
108
- message = "Unescaped user input value"
109
- end
107
+ message = "Unescaped #{friendly_type_of input}"
110
108
 
111
109
  warn :template => @current_template,
112
110
  :warning_type => "Cross Site Scripting",
@@ -187,15 +185,8 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
187
185
  message = nil
188
186
 
189
187
  if @matched
190
- case @matched.type
191
- when :model
192
- unless tracker.options[:ignore_model_output]
193
- message = "Unescaped model attribute"
194
- end
195
- when :params
196
- message = "Unescaped parameter value"
197
- when :cookies
198
- message = "Unescaped cookie value"
188
+ unless @matched.type and tracker.options[:ignore_model_output]
189
+ message = "Unescaped #{friendly_type_of @matched}"
199
190
  end
200
191
 
201
192
  if message and not duplicate? exp
@@ -0,0 +1,57 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for unsafe deserialization of objects"
7
+
8
+ def run_check
9
+ check_yaml
10
+ check_csv
11
+ check_marshal
12
+ end
13
+
14
+ def check_yaml
15
+ check_methods :YAML, :load, :load_documents, :load_stream, :parse_documents, :parse_stream
16
+ end
17
+
18
+ def check_csv
19
+ check_methods :CSV, :load
20
+ end
21
+
22
+ def check_marshal
23
+ check_methods :Marshal, :load, :restore
24
+ end
25
+
26
+ def check_methods target, *methods
27
+ tracker.find_call(:target => target, :methods => methods ).each do |result|
28
+ check_deserialize result, target
29
+ end
30
+ end
31
+
32
+ def check_deserialize result, target, arg = nil
33
+ return if duplicate? result
34
+ add_result result
35
+
36
+ arg ||= result[:call].first_arg
37
+ method = result[:call].method
38
+
39
+ if input = has_immediate_user_input?(arg)
40
+ confidence = CONFIDENCE[:high]
41
+ elsif input = include_user_input?(arg)
42
+ confidence = CONFIDENCE[:med]
43
+ end
44
+
45
+ if confidence
46
+ message = "#{target}.#{method} called with #{friendly_type_of input}"
47
+
48
+ warn :result => result,
49
+ :warning_type => "Remote Code Execution",
50
+ :warning_code => :unsafe_deserialize,
51
+ :message => message,
52
+ :user_input => input.match,
53
+ :confidence => confidence,
54
+ :link_path => "unsafe_deserialization"
55
+ end
56
+ end
57
+ end
@@ -84,20 +84,12 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
84
84
  user_input = nil
85
85
  end
86
86
 
87
- warning = { :warning_type => "Command Injection",
87
+ warn :result => result,
88
+ :warning_type => "Command Injection",
88
89
  :warning_code => :command_injection,
89
90
  :message => "Possible command injection",
90
91
  :code => exp,
91
92
  :user_input => user_input,
92
- :confidence => confidence }
93
-
94
- if result[:location][0] == :template
95
- warning[:template] = result[:location][1]
96
- else
97
- warning[:class] = result[:location][1]
98
- warning[:method] = result[:location][2]
99
- end
100
-
101
- warn warning
93
+ :confidence => confidence
102
94
  end
103
95
  end
@@ -48,20 +48,7 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
48
48
  end
49
49
 
50
50
  if match
51
- case match.type
52
- when :params
53
- message = "Parameter"
54
- when :cookies
55
- message = "Cookie"
56
- when :request
57
- message = "Request"
58
- when :model
59
- message = "Model attribute"
60
- else
61
- message = "User input"
62
- end
63
-
64
- message << " value used in file name"
51
+ message = "#{friendly_type_of(match).capitalize} used in file name"
65
52
 
66
53
  warn :result => result,
67
54
  :warning_type => "File Access",
@@ -11,15 +11,15 @@ class Brakeman::CheckForgerySetting < Brakeman::BaseCheck
11
11
 
12
12
  def run_check
13
13
  app_controller = tracker.controllers[:ApplicationController]
14
- if tracker.config[:rails] and
15
- tracker.config[:rails][:action_controller] and
14
+ if tracker.config[:rails][:action_controller] and
16
15
  tracker.config[:rails][:action_controller][:allow_forgery_protection] == Sexp.new(:false)
17
16
 
18
17
  warn :controller => :ApplicationController,
19
18
  :warning_type => "Cross-Site Request Forgery",
20
19
  :warning_code => :csrf_protection_disabled,
21
20
  :message => "Forgery protection is disabled",
22
- :confidence => CONFIDENCE[:high]
21
+ :confidence => CONFIDENCE[:high],
22
+ :file => app_controller[:file]
23
23
 
24
24
  elsif app_controller and not app_controller[:options][:protect_from_forgery]
25
25
 
@@ -27,7 +27,8 @@ class Brakeman::CheckForgerySetting < Brakeman::BaseCheck
27
27
  :warning_type => "Cross-Site Request Forgery",
28
28
  :warning_code => :csrf_protection_missing,
29
29
  :message => "'protect_from_forgery' should be called in ApplicationController",
30
- :confidence => CONFIDENCE[:high]
30
+ :confidence => CONFIDENCE[:high],
31
+ :file => app_controller[:file]
31
32
 
32
33
  elsif version_between? "2.1.0", "2.3.10"
33
34
 
@@ -68,14 +68,7 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
68
68
  input = has_immediate_user_input?(argument)
69
69
  return false unless input
70
70
 
71
- case input.type
72
- when :params
73
- message = "Unescaped parameter value in link_to"
74
- when :cookies
75
- message = "Unescaped cookie value in link_to"
76
- else
77
- message = "Unescaped user input value in link_to"
78
- end
71
+ message = "Unescaped #{friendly_type_of input} in link_to"
79
72
 
80
73
  warn_xss(result, message, input.match, CONFIDENCE[:high])
81
74
  end
@@ -96,15 +89,11 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
96
89
  # Check if we should warn about the matched result
97
90
  def check_matched(result, matched = nil)
98
91
  return false unless matched
99
- message = nil
92
+ return false if matched.type == :model and tracker.options[:ignore_model_output]
100
93
 
101
- if matched.type == :model and not tracker.options[:ignore_model_output]
102
- message = "Unescaped model attribute in link_to"
103
- elsif matched.type == :params
104
- message = "Unescaped parameter value in link_to"
105
- end
94
+ message = "Unescaped #{friendly_type_of matched} in link_to"
106
95
 
107
- message ? warn_xss(result, message, @matched.match, CONFIDENCE[:med]) : false
96
+ warn_xss(result, message, @matched.match, CONFIDENCE[:med])
108
97
  end
109
98
 
110
99
  # Create a warn for this xss
@@ -42,14 +42,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
42
42
 
43
43
 
44
44
  if input = has_immediate_user_input?(url_arg)
45
- case input.type
46
- when :params
47
- message = "Unsafe parameter value in link_to href"
48
- when :cookies
49
- message = "Unsafe cookie value in link_to href"
50
- else
51
- message = "Unsafe user input value in link_to href"
52
- end
45
+ message = "Unsafe #{friendly_type_of input} in link_to href"
53
46
 
54
47
  unless duplicate? result
55
48
  add_result result
@@ -63,8 +63,12 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
63
63
 
64
64
  if call? first_arg and (first_arg.method == :slice or first_arg.method == :only)
65
65
  return
66
- elsif not node_type? first_arg, :hash and not attr_protected
67
- confidence = CONFIDENCE[:high]
66
+ elsif not node_type? first_arg, :hash
67
+ if attr_protected
68
+ confidence = CONFIDENCE[:med]
69
+ else
70
+ confidence = CONFIDENCE[:high]
71
+ end
68
72
  user_input = input.match
69
73
  else
70
74
  confidence = CONFIDENCE[:low]