brakeman 1.7.1 → 1.8.0

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