brakeman 1.9.5 → 2.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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]