brakeman 1.7.1 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/bin/brakeman +3 -0
  2. data/lib/brakeman.rb +2 -0
  3. data/lib/brakeman/brakeman.rake +4 -3
  4. data/lib/brakeman/checks/base_check.rb +40 -37
  5. data/lib/brakeman/checks/check_basic_auth.rb +3 -3
  6. data/lib/brakeman/checks/check_content_tag.rb +179 -0
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +41 -17
  8. data/lib/brakeman/checks/check_execute.rb +1 -1
  9. data/lib/brakeman/checks/check_file_access.rb +2 -2
  10. data/lib/brakeman/checks/check_link_to.rb +9 -7
  11. data/lib/brakeman/checks/check_link_to_href.rb +1 -1
  12. data/lib/brakeman/checks/check_mail_to.rb +1 -1
  13. data/lib/brakeman/checks/check_mass_assignment.rb +6 -5
  14. data/lib/brakeman/checks/check_redirect.rb +18 -17
  15. data/lib/brakeman/checks/check_render.rb +3 -1
  16. data/lib/brakeman/checks/check_select_tag.rb +2 -2
  17. data/lib/brakeman/checks/check_select_vulnerability.rb +3 -3
  18. data/lib/brakeman/checks/check_send.rb +3 -3
  19. data/lib/brakeman/checks/check_session_settings.rb +5 -5
  20. data/lib/brakeman/checks/check_single_quotes.rb +8 -8
  21. data/lib/brakeman/checks/check_skip_before_filter.rb +2 -2
  22. data/lib/brakeman/checks/check_sql.rb +36 -39
  23. data/lib/brakeman/checks/check_validation_regex.rb +3 -3
  24. data/lib/brakeman/checks/check_without_protection.rb +2 -2
  25. data/lib/brakeman/format/style.css +15 -0
  26. data/lib/brakeman/options.rb +4 -0
  27. data/lib/brakeman/processor.rb +1 -1
  28. data/lib/brakeman/processors/alias_processor.rb +63 -61
  29. data/lib/brakeman/processors/base_processor.rb +31 -45
  30. data/lib/brakeman/processors/controller_alias_processor.rb +11 -9
  31. data/lib/brakeman/processors/controller_processor.rb +26 -25
  32. data/lib/brakeman/processors/erb_template_processor.rb +12 -12
  33. data/lib/brakeman/processors/erubis_template_processor.rb +19 -17
  34. data/lib/brakeman/processors/gem_processor.rb +5 -5
  35. data/lib/brakeman/processors/haml_template_processor.rb +16 -12
  36. data/lib/brakeman/processors/lib/find_all_calls.rb +11 -17
  37. data/lib/brakeman/processors/lib/find_call.rb +16 -23
  38. data/lib/brakeman/processors/lib/processor_helper.rb +11 -5
  39. data/lib/brakeman/processors/lib/rails2_config_processor.rb +21 -20
  40. data/lib/brakeman/processors/lib/rails2_route_processor.rb +38 -34
  41. data/lib/brakeman/processors/lib/rails3_config_processor.rb +17 -17
  42. data/lib/brakeman/processors/lib/rails3_route_processor.rb +42 -40
  43. data/lib/brakeman/processors/lib/render_helper.rb +6 -6
  44. data/lib/brakeman/processors/lib/route_helper.rb +1 -1
  45. data/lib/brakeman/processors/library_processor.rb +11 -11
  46. data/lib/brakeman/processors/model_processor.rb +18 -16
  47. data/lib/brakeman/processors/template_alias_processor.rb +36 -29
  48. data/lib/brakeman/processors/template_processor.rb +4 -4
  49. data/lib/brakeman/report.rb +23 -4
  50. data/lib/brakeman/templates/error_overview.html.erb +9 -1
  51. data/lib/brakeman/templates/view_warnings.html.erb +16 -3
  52. data/lib/brakeman/tracker.rb +3 -0
  53. data/lib/brakeman/util.rb +5 -1
  54. data/lib/brakeman/version.rb +1 -1
  55. data/lib/brakeman/warning.rb +1 -1
  56. data/lib/ruby_parser/bm_sexp.rb +302 -2
  57. metadata +6 -5
@@ -55,6 +55,9 @@ end
55
55
  if options[:previous_results_json]
56
56
  vulns = Brakeman.compare options.merge(:quiet => options[:quiet])
57
57
  puts JSON.pretty_generate(vulns)
58
+ if options[:exit_on_warn] and (vulns[:new].count + vulns[:fixed].count > 0)
59
+ exit Brakeman::Warnings_Found_Exit_Code
60
+ end
58
61
  else
59
62
  #Run scan and output a report
60
63
  tracker = Brakeman.run options.merge(:print_report => true, :quiet => options[:quiet])
@@ -39,6 +39,7 @@ module Brakeman
39
39
  # * :safe_methods - array of methods to consider safe
40
40
  # * :skip_libs - do not process lib/ directory (default: false)
41
41
  # * :skip_checks - checks not to run (run all if not specified)
42
+ # * :relative_path - show relative path of each file(default: false)
42
43
  # * :summary_only - only output summary section of report
43
44
  # (does not apply to tabs format)
44
45
  #
@@ -119,6 +120,7 @@ module Brakeman
119
120
  :ignore_model_output => false,
120
121
  :message_limit => 100,
121
122
  :parallel_checks => true,
123
+ :relative_path => false,
122
124
  :quiet => true,
123
125
  :report_progress => true,
124
126
  :html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css"
@@ -1,9 +1,10 @@
1
1
  namespace :brakeman do
2
2
 
3
3
  desc "Run Brakeman"
4
- task :run, :output_file do |t, args|
4
+ task :run, :output_files do |t, args|
5
5
  require 'brakeman'
6
-
7
- Brakeman.run :app_path => ".", :output_file => args[:output_file], :print_report => true
6
+
7
+ files = args[:output_files].split(' ') if args[:output_files]
8
+ Brakeman.run :app_path => ".", :output_files => files, :print_report => true
8
9
  end
9
10
  end
@@ -58,16 +58,18 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
58
58
 
59
59
  #Process calls and check if they include user input
60
60
  def process_call exp
61
- process exp[1] if sexp? exp[1]
62
- process exp[3]
61
+ process exp.target if sexp? exp.target
62
+ process_all exp.args
63
63
 
64
- if params? exp[1]
64
+ target = exp.target
65
+
66
+ if params? target
65
67
  @has_user_input = Match.new(:params, exp)
66
- elsif cookies? exp[1]
68
+ elsif cookies? target
67
69
  @has_user_input = Match.new(:cookies, exp)
68
- elsif request_env? exp[1]
70
+ elsif request_env? target
69
71
  @has_user_input = Match.new(:request, exp)
70
- elsif sexp? exp[1] and model_name? exp[1][1]
72
+ elsif sexp? target and model_name? target[1]
71
73
  @has_user_input = Match.new(:model, exp)
72
74
  end
73
75
 
@@ -77,11 +79,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
77
79
  def process_if exp
78
80
  #This is to ignore user input in condition
79
81
  current_user_input = @has_user_input
80
- process exp[1]
82
+ process exp.condition
81
83
  @has_user_input = current_user_input
82
84
 
83
- process exp[2] if sexp? exp[2]
84
- process exp[3] if sexp? exp[3]
85
+ process exp.then_clause if sexp? exp.then_clause
86
+ process exp.else_clause if sexp? exp.else_clause
85
87
 
86
88
  exp
87
89
  end
@@ -258,11 +260,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
258
260
  elsif cookies? exp
259
261
  return Match.new(:cookies, exp)
260
262
  elsif call? exp
261
- if params? exp[1]
263
+ if params? exp.target
262
264
  return Match.new(:params, exp)
263
- elsif cookies? exp[1]
265
+ elsif cookies? exp.target
264
266
  return Match.new(:cookies, exp)
265
- elsif request_env? exp[1]
267
+ elsif request_env? exp.target
266
268
  return Match.new(:request, exp)
267
269
  else
268
270
  false
@@ -278,27 +280,25 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
278
280
  end
279
281
  false
280
282
  when :string_eval
281
- if sexp? exp[1]
282
- if exp[1].node_type == :rlist
283
- exp[1].each do |e|
284
- if sexp? e
285
- match = has_immediate_user_input?(e)
286
- return match if match
287
- end
283
+ if sexp? exp.value
284
+ if exp.value.node_type == :rlist
285
+ exp.value.each_sexp do |e|
286
+ match = has_immediate_user_input?(e)
287
+ return match if match
288
288
  end
289
289
  false
290
290
  else
291
- has_immediate_user_input? exp[1]
291
+ has_immediate_user_input? exp.value
292
292
  end
293
293
  end
294
294
  when :format
295
- has_immediate_user_input? exp[1]
295
+ has_immediate_user_input? exp.value
296
296
  when :if
297
- (sexp? exp[2] and has_immediate_user_input? exp[2]) or
298
- (sexp? exp[3] and has_immediate_user_input? exp[3])
297
+ (sexp? exp.then_clause and has_immediate_user_input? exp.then_clause) or
298
+ (sexp? exp.else_clause and has_immediate_user_input? exp.else_clause)
299
299
  when :or
300
- has_immediate_user_input? exp[1] or
301
- has_immediate_user_input? exp[2]
300
+ has_immediate_user_input? exp.lhs or
301
+ has_immediate_user_input? exp.rhs
302
302
  else
303
303
  false
304
304
  end
@@ -311,12 +311,12 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
311
311
  out = exp if out.nil?
312
312
 
313
313
  if sexp? exp and exp.node_type == :output
314
- exp = exp[1]
314
+ exp = exp.value
315
315
  end
316
316
 
317
317
  if call? exp
318
- target = exp[1]
319
- method = exp[2]
318
+ target = exp.target
319
+ method = exp.method
320
320
 
321
321
  if call? target and not method.to_s[-1,1] == "?"
322
322
  has_immediate_model? target, out
@@ -335,23 +335,26 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
335
335
  end
336
336
  false
337
337
  when :string_eval
338
- if sexp? exp[1]
339
- if exp[1].node_type == :rlist
340
- exp[1].each do |e|
341
- if sexp? e and match = has_immediate_model?(e, out)
338
+ if sexp? exp.value
339
+ if exp.value.node_type == :rlist
340
+ exp.value.each_sexp do |e|
341
+ if match = has_immediate_model?(e, out)
342
342
  return match
343
343
  end
344
344
  end
345
345
  false
346
346
  else
347
- has_immediate_model? exp[1], out
347
+ has_immediate_model? exp.value, out
348
348
  end
349
349
  end
350
350
  when :format
351
- has_immediate_model? exp[1], out
351
+ has_immediate_model? exp.value, out
352
352
  when :if
353
- ((sexp? exp[2] and has_immediate_model? exp[2], out) or
354
- (sexp? exp[3] and has_immediate_model? exp[3], out))
353
+ ((sexp? exp.then_clause and has_immediate_model? exp.then_clause, out) or
354
+ (sexp? exp.else_clause and has_immediate_model? exp.else_clause, out))
355
+ when :or
356
+ has_immediate_model? exp.lhs or
357
+ has_immediate_model? exp.rhs
355
358
  else
356
359
  false
357
360
  end
@@ -387,7 +390,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
387
390
 
388
391
  case exp.node_type
389
392
  when :output, :format
390
- find_chain exp[1], target
393
+ find_chain exp.value, target
391
394
  when :call
392
395
  if exp == target or include_target? exp, target
393
396
  return exp
@@ -33,10 +33,10 @@ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
33
33
  end
34
34
 
35
35
  def get_password call
36
- args = call[3][1]
36
+ arg = call.first_arg
37
37
 
38
- return false if args.nil? or not hash? args
38
+ return false if arg.nil? or not hash? arg
39
39
 
40
- hash_access(args, :password)
40
+ hash_access(arg, :password)
41
41
  end
42
42
  end
@@ -0,0 +1,179 @@
1
+ require 'brakeman/checks/check_cross_site_scripting'
2
+
3
+ #Checks for unescaped values in `content_tag`
4
+ #
5
+ # content_tag :tag, body
6
+ # ^-- Unescaped in Rails 2.x
7
+ #
8
+ # content_tag, :tag, body, attribute => value
9
+ # ^-- Unescaped in all versions
10
+ #
11
+ # content_tag, :tag, body, attribute => value
12
+ # ^
13
+ # |
14
+ # Escaped by default, can be explicitly escaped
15
+ # or not by passing in (true|false) as fourth argument
16
+ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
17
+ Brakeman::Checks.add self
18
+
19
+ @description = "Checks for XSS in calls to content_tag"
20
+
21
+ def run_check
22
+ @ignore_methods = Set[:button_to, :check_box, :escapeHTML, :escape_once,
23
+ :field_field, :fields_for, :h, :hidden_field,
24
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
25
+ :mail_to, :radio_button, :select,
26
+ :submit_tag, :text_area, :text_field,
27
+ :text_field_tag, :url_encode, :url_for,
28
+ :will_paginate].merge tracker.options[:safe_methods]
29
+
30
+ @known_dangerous = []
31
+ methods = tracker.find_call :target => false, :method => :content_tag
32
+
33
+ @models = tracker.models.keys
34
+ @inspect_arguments = tracker.options[:check_arguments]
35
+
36
+ Brakeman.debug "Checking for XSS in content_tag"
37
+ methods.each do |call|
38
+ process_result call
39
+ end
40
+ end
41
+
42
+ def process_result result
43
+ return if duplicate? result
44
+
45
+ call = result[:call] = result[:call].dup
46
+
47
+ args = call.arglist
48
+
49
+ tag_name = args[1]
50
+ content = args[2]
51
+ attributes = args[3]
52
+ escape_attr = args[4]
53
+
54
+ @matched = false
55
+
56
+ #Silly, but still dangerous if someone uses user input in the tag type
57
+ check_argument result, tag_name
58
+
59
+ #Versions before 3.x do not escape body of tag, nor does the rails_xss gem
60
+ unless @matched or (tracker.options[:rails3] and not raw? content)
61
+ check_argument result, content
62
+ end
63
+
64
+ #Attribute keys are never escaped, so check them for user input
65
+ if not @matched and hash? attributes and not request_value? attributes
66
+ hash_iterate(attributes) do |k, v|
67
+ check_argument result, k
68
+ return if @matched
69
+ end
70
+ end
71
+
72
+ #By default, content_tag escapes attribute values passed in as a hash.
73
+ #But this behavior can be disabled. So only check attributes hash
74
+ #if they are explicitly not escaped.
75
+ if not @matched and attributes and false? escape_attr
76
+ if request_value? attributes or not hash? attributes
77
+ check_argument result, attributes
78
+ else #check hash values
79
+ hash_iterate(attributes) do |k, v|
80
+ check_argument result, v
81
+ return if @matched
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def check_argument result, exp
88
+ #Check contents of raw() calls directly
89
+ if call? exp and exp.method == :raw
90
+ arg = process exp.first_arg
91
+ else
92
+ arg = process exp
93
+ end
94
+
95
+ if input = has_immediate_user_input?(arg)
96
+ case input.type
97
+ when :params
98
+ message = "Unescaped parameter value in content_tag"
99
+ when :cookies
100
+ message = "Unescaped cookie value in content_tag"
101
+ else
102
+ message = "Unescaped user input value in content_tag"
103
+ end
104
+
105
+ add_result result
106
+
107
+ warn :result => result,
108
+ :warning_type => "Cross Site Scripting",
109
+ :message => message,
110
+ :user_input => input.match,
111
+ :confidence => CONFIDENCE[:high],
112
+ :link_path => "content_tag"
113
+
114
+ elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(arg)
115
+ method = match[2]
116
+
117
+ unless IGNORE_MODEL_METHODS.include? method
118
+ add_result result
119
+
120
+ if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
121
+ confidence = CONFIDENCE[:high]
122
+ else
123
+ confidence = CONFIDENCE[:med]
124
+ end
125
+
126
+ warn :result => result,
127
+ :warning_type => "Cross Site Scripting",
128
+ :message => "Unescaped model attribute in content_tag",
129
+ :user_input => match,
130
+ :confidence => confidence,
131
+ :link_path => "content_tag"
132
+ end
133
+
134
+ elsif @matched
135
+ message = "Unescaped "
136
+
137
+ case @matched.type
138
+ when :model
139
+ return if tracker.options[:ignore_model_output]
140
+ message << "model attribute"
141
+ when :params
142
+ message << "parameter"
143
+ when :cookies
144
+ message << "cookie"
145
+ when :session
146
+ message << "session"
147
+ else
148
+ message << "user input"
149
+ end
150
+
151
+ message << " value in content_tag"
152
+
153
+ add_result result
154
+
155
+ warn :result => result,
156
+ :warning_type => "Cross Site Scripting",
157
+ :message => message,
158
+ :user_input => @matched.match,
159
+ :confidence => CONFIDENCE[:med],
160
+ :link_path => "content_tag"
161
+ end
162
+ end
163
+
164
+ def process_call exp
165
+ if @mark
166
+ actually_process_call exp
167
+ else
168
+ @mark = true
169
+ actually_process_call exp
170
+ @mark = false
171
+ end
172
+
173
+ exp
174
+ end
175
+
176
+ def raw? exp
177
+ call? exp and exp.method == :raw
178
+ end
179
+ end
@@ -34,7 +34,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
34
34
  FORM_BUILDER = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new, Sexp.new(:arglist))
35
35
 
36
36
  #Run check
37
- def run_check
37
+ def run_check
38
38
  @ignore_methods = Set[:button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
39
39
  :field_field, :fields_for, :h, :hidden_field,
40
40
  :hidden_field, :hidden_field_tag, :image_tag, :label,
@@ -58,6 +58,16 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
58
58
  @known_dangerous << :strip_tags
59
59
  end
60
60
 
61
+ matches = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
62
+ json_escape_on = matches.detect {|result| true? result[-1].first_arg}
63
+
64
+ if !json_escape_on or version_between? "0.0.0", "2.0.99"
65
+ @known_dangerous << :to_json
66
+ Brakeman.debug("Automatic to_json escaping not enabled, consider to_json dangerous")
67
+ else
68
+ Brakeman.debug("Automatic to_json escaping is enabled.")
69
+ end
70
+
61
71
  tracker.each_template do |name, template|
62
72
  @current_template = template
63
73
  template[:outputs].each do |out|
@@ -77,10 +87,10 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
77
87
  def check_for_immediate_xss exp
78
88
  return if duplicate? exp
79
89
 
80
- if exp[0] == :output
81
- out = exp[1]
82
- elsif exp[0] == :escaped_output and raw_call? exp
83
- out = exp[1][3][1]
90
+ if exp.node_type == :output
91
+ out = exp.value
92
+ elsif exp.node_type == :escaped_output and raw_call? exp
93
+ out = exp.value.first_arg
84
94
  end
85
95
 
86
96
  if input = has_immediate_user_input?(out)
@@ -115,12 +125,20 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
115
125
  confidence = CONFIDENCE[:med]
116
126
  end
117
127
 
128
+ message = "Unescaped model attribute"
129
+ link_path = "cross_site_scripting"
130
+ if node_type?(out, :call, :attrasgn) && out.method == :to_json
131
+ message += " in JSON hash"
132
+ link_path += "_to_json"
133
+ end
134
+
118
135
  code = find_chain out, match
119
136
  warn :template => @current_template,
120
137
  :warning_type => "Cross Site Scripting",
121
- :message => "Unescaped model attribute",
138
+ :message => message,
122
139
  :code => code,
123
- :confidence => confidence
140
+ :confidence => confidence,
141
+ :link_path => link_path
124
142
  end
125
143
 
126
144
  else
@@ -130,15 +148,15 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
130
148
 
131
149
  #Process an output Sexp
132
150
  def process_output exp
133
- process exp[1].dup
151
+ process exp.value.dup
134
152
  end
135
153
 
136
154
  #Look for calls to raw()
137
155
  #Otherwise, ignore
138
156
  def process_escaped_output exp
139
157
  unless check_for_immediate_xss exp
140
- if raw_call? exp
141
- process exp[1][3][1]
158
+ if raw_call? exp and not duplicate? exp
159
+ process exp.value.first_arg
142
160
  end
143
161
  end
144
162
  exp
@@ -173,8 +191,13 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
173
191
  if message and not duplicate? exp
174
192
  add_result exp
175
193
 
176
- if exp[1].nil? and @known_dangerous.include? exp[2]
194
+ link_path = "cross_site_scripting"
195
+ if @known_dangerous.include? exp.method
177
196
  confidence = CONFIDENCE[:high]
197
+ if exp.method == :to_json
198
+ message += " in JSON hash"
199
+ link_path += "_to_json"
200
+ end
178
201
  else
179
202
  confidence = CONFIDENCE[:low]
180
203
  end
@@ -184,7 +207,8 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
184
207
  :message => message,
185
208
  :code => exp,
186
209
  :user_input => @matched.match,
187
- :confidence => confidence
210
+ :confidence => confidence,
211
+ :link_path => link_path
188
212
  end
189
213
  end
190
214
 
@@ -196,13 +220,13 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
196
220
 
197
221
  def actually_process_call exp
198
222
  return if @matched
199
- target = exp[1]
223
+ target = exp.target
200
224
  if sexp? target
201
225
  target = process target
202
226
  end
203
227
 
204
- method = exp[2]
205
- args = exp[3]
228
+ method = exp.method
229
+ args = exp.arglist
206
230
 
207
231
  #Ignore safe items
208
232
  if (target.nil? and (@ignore_methods.include? method or method.to_s =~ IGNORE_LIKE)) or
@@ -215,7 +239,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
215
239
 
216
240
  #exp[0] = :ignore #should not be necessary
217
241
  @matched = false
218
- elsif sexp? exp[1] and model_name? exp[1][1]
242
+ elsif sexp? target and model_name? target[1]
219
243
  @matched = Match.new(:model, exp)
220
244
  elsif cookies? exp
221
245
  @matched = Match.new(:cookies, exp)
@@ -267,6 +291,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
267
291
  end
268
292
 
269
293
  def raw_call? exp
270
- exp[1].node_type == :call and exp[1][2] == :raw
294
+ exp.value.node_type == :call and exp.value.method == :raw
271
295
  end
272
296
  end