brakeman-min 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/FEATURES +16 -0
  2. data/README.md +118 -0
  3. data/WARNING_TYPES +69 -0
  4. data/bin/brakeman +269 -0
  5. data/lib/checks.rb +67 -0
  6. data/lib/checks/base_check.rb +353 -0
  7. data/lib/checks/check_cross_site_scripting.rb +324 -0
  8. data/lib/checks/check_default_routes.rb +29 -0
  9. data/lib/checks/check_evaluation.rb +27 -0
  10. data/lib/checks/check_execute.rb +110 -0
  11. data/lib/checks/check_file_access.rb +46 -0
  12. data/lib/checks/check_forgery_setting.rb +42 -0
  13. data/lib/checks/check_mail_to.rb +48 -0
  14. data/lib/checks/check_mass_assignment.rb +72 -0
  15. data/lib/checks/check_model_attributes.rb +36 -0
  16. data/lib/checks/check_nested_attributes.rb +34 -0
  17. data/lib/checks/check_redirect.rb +98 -0
  18. data/lib/checks/check_render.rb +65 -0
  19. data/lib/checks/check_send_file.rb +15 -0
  20. data/lib/checks/check_session_settings.rb +36 -0
  21. data/lib/checks/check_sql.rb +124 -0
  22. data/lib/checks/check_validation_regex.rb +60 -0
  23. data/lib/format/style.css +105 -0
  24. data/lib/processor.rb +83 -0
  25. data/lib/processors/alias_processor.rb +384 -0
  26. data/lib/processors/base_processor.rb +237 -0
  27. data/lib/processors/config_processor.rb +146 -0
  28. data/lib/processors/controller_alias_processor.rb +237 -0
  29. data/lib/processors/controller_processor.rb +202 -0
  30. data/lib/processors/erb_template_processor.rb +84 -0
  31. data/lib/processors/erubis_template_processor.rb +62 -0
  32. data/lib/processors/haml_template_processor.rb +131 -0
  33. data/lib/processors/lib/find_call.rb +176 -0
  34. data/lib/processors/lib/find_model_call.rb +39 -0
  35. data/lib/processors/lib/processor_helper.rb +36 -0
  36. data/lib/processors/lib/render_helper.rb +137 -0
  37. data/lib/processors/library_processor.rb +118 -0
  38. data/lib/processors/model_processor.rb +125 -0
  39. data/lib/processors/output_processor.rb +233 -0
  40. data/lib/processors/params_processor.rb +77 -0
  41. data/lib/processors/route_processor.rb +338 -0
  42. data/lib/processors/template_alias_processor.rb +86 -0
  43. data/lib/processors/template_processor.rb +55 -0
  44. data/lib/report.rb +651 -0
  45. data/lib/scanner.rb +215 -0
  46. data/lib/scanner_erubis.rb +43 -0
  47. data/lib/tracker.rb +144 -0
  48. data/lib/util.rb +141 -0
  49. data/lib/version.rb +1 -0
  50. data/lib/warning.rb +97 -0
  51. metadata +141 -0
data/lib/checks.rb ADDED
@@ -0,0 +1,67 @@
1
+ #Collects up results from running different checks.
2
+ #
3
+ #Checks can be added with +Check.add(check_class)+
4
+ #
5
+ #All .rb files in checks/ will be loaded.
6
+ class Checks
7
+ @checks = []
8
+
9
+ attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
10
+
11
+ #Add a check. This will call +_klass_.new+ when running tests
12
+ def self.add klass
13
+ @checks << klass
14
+ end
15
+
16
+ #No need to use this directly.
17
+ def initialize
18
+ @warnings = []
19
+ @template_warnings = []
20
+ @model_warnings = []
21
+ @controller_warnings = []
22
+ @checks_run = []
23
+ end
24
+
25
+ #Add Warning to list of warnings to report.
26
+ #Warnings are split into four different arrays
27
+ #for template, controller, model, and generic warnings.
28
+ def add_warning warning
29
+ case warning.warning_set
30
+ when :template
31
+ @template_warnings << warning
32
+ when :warning
33
+ @warnings << warning
34
+ when :controller
35
+ @controller_warnings << warning
36
+ when :model
37
+ @model_warnings << warning
38
+ else
39
+ raise "Unknown warning: #{warning.warning_set}"
40
+ end
41
+ end
42
+
43
+ #Run all the checks on the given Tracker.
44
+ #Returns a new instance of Checks with the results.
45
+ def self.run_checks tracker
46
+ checks = self.new
47
+ @checks.each do |c|
48
+ #Run or don't run check based on options
49
+ unless OPTIONS[:skip_checks].include? c.to_s or
50
+ (OPTIONS[:run_checks] and not OPTIONS[:run_checks].include? c.to_s)
51
+
52
+ warn " - #{c}"
53
+ c.new(checks, tracker).run_check
54
+
55
+ #Maintain list of which checks were run
56
+ #mainly for reporting purposes
57
+ checks.checks_run << c.to_s[5..-1]
58
+ end
59
+ end
60
+ checks
61
+ end
62
+ end
63
+
64
+ #Load all files in checks/ directory
65
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
66
+ require f.match(/(checks\/.*)\.rb$/)[0]
67
+ end
@@ -0,0 +1,353 @@
1
+ require 'rubygems'
2
+ require 'sexp_processor'
3
+ require 'processors/output_processor'
4
+ require 'warning'
5
+ require 'util'
6
+
7
+ #Basis of vulnerability checks.
8
+ class BaseCheck < SexpProcessor
9
+ include ProcessorHelper
10
+ include Util
11
+ attr_reader :checks, :tracker
12
+
13
+ CONFIDENCE = { :high => 0, :med => 1, :low => 2 }
14
+
15
+ #Initialize Check with Checks.
16
+ def initialize checks, tracker
17
+ super()
18
+ @results = [] #only to check for duplicates
19
+ @checks = checks
20
+ @tracker = tracker
21
+ @string_interp = false
22
+ @current_template = @current_module = @current_class = @current_method = nil
23
+ self.strict = false
24
+ self.auto_shift_type = false
25
+ self.require_empty = false
26
+ self.default_method = :process_default
27
+ self.warn_on_default = false
28
+ end
29
+
30
+ #Add result to result list, which is used to check for duplicates
31
+ def add_result result, location = nil
32
+ location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[1]
33
+ location = location[:name] if location.is_a? Hash
34
+ location = location.to_sym
35
+
36
+ @results << [result.line, location, result]
37
+ end
38
+
39
+ #Default Sexp processing. Iterates over each value in the Sexp
40
+ #and processes them if they are also Sexps.
41
+ def process_default exp
42
+ type = exp.shift
43
+ exp.each_with_index do |e, i|
44
+ if sexp? e
45
+ process e
46
+ else
47
+ e
48
+ end
49
+ end
50
+
51
+ exp.unshift type
52
+ end
53
+
54
+ #Process calls and check if they include user input
55
+ def process_call exp
56
+ process exp[1] if sexp? exp[1]
57
+ process exp[3]
58
+
59
+ if ALL_PARAMETERS.include? exp[1] or ALL_PARAMETERS.include? exp or params? exp[1]
60
+ @has_user_input = :params
61
+ elsif exp[1] == COOKIES or exp == COOKIES or cookies? exp[1]
62
+ @has_user_input = :cookies
63
+ elsif sexp? exp[1] and model_name? exp[1][1]
64
+ @has_user_input = :model
65
+ end
66
+
67
+ exp
68
+ end
69
+
70
+ #Note that params are included in current expression
71
+ def process_params exp
72
+ @has_user_input = :params
73
+ exp
74
+ end
75
+
76
+ #Note that cookies are included in current expression
77
+ def process_cookies exp
78
+ @has_user_input = :cookies
79
+ exp
80
+ end
81
+
82
+ private
83
+
84
+ #Report a warning
85
+ def warn options
86
+ @checks.add_warning Warning.new(options.merge({ :check => self.class.to_s }))
87
+ end
88
+
89
+ #Run _exp_ through OutputProcessor to get a nice String.
90
+ def format_output exp
91
+ OutputProcessor.new.format(exp).gsub(/\r|\n/, "")
92
+ end
93
+
94
+ #Checks if the model inherits from parent,
95
+ def parent? tracker, model, parent
96
+ if model == nil
97
+ false
98
+ elsif model[:parent] == parent
99
+ true
100
+ elsif model[:parent]
101
+ parent? tracker, tracker.models[model[:parent]], parent
102
+ else
103
+ false
104
+ end
105
+ end
106
+
107
+ #Checks if mass assignment is disabled globally in an initializer.
108
+ def mass_assign_disabled? tracker
109
+ matches = tracker.check_initializers(:"ActiveRecord::Base", :send)
110
+ if matches.empty?
111
+ false
112
+ else
113
+ matches.each do |result|
114
+ if result[3][3] == Sexp.new(:arg_list, Sexp.new(:lit, :attr_accessible), Sexp.new(:nil))
115
+ return true
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ #This is to avoid reporting duplicates. Checks if the result has been
122
+ #reported already from the same line number.
123
+ def duplicate? result, location = nil
124
+ line = result.line
125
+ location ||= (@current_template && @current_template[:name]) || @current_class || @current_module || @current_set || result[1]
126
+
127
+ location = location[:name] if location.is_a? Hash
128
+ location = location.to_sym
129
+ @results.each do |r|
130
+ if r[0] == line and r[1] == location
131
+ if OPTIONS[:combine_locations]
132
+ return true
133
+ elsif r[2] == result
134
+ return true
135
+ end
136
+ end
137
+ end
138
+
139
+ false
140
+ end
141
+
142
+ #Ignores ignores
143
+ def process_ignore exp
144
+ exp
145
+ end
146
+
147
+ #Does not actually process string interpolation, but notes that it occurred.
148
+ def process_string_interp exp
149
+ @string_interp = true
150
+ exp
151
+ end
152
+
153
+ #Checks if an expression contains string interpolation.
154
+ def include_interp? exp
155
+ @string_interp = false
156
+ process exp
157
+ @string_interp
158
+ end
159
+
160
+ #Checks if _exp_ includes parameters or cookies, but this only works
161
+ #with the base process_default.
162
+ def include_user_input? exp
163
+ @has_user_input = false
164
+ process exp
165
+ @has_user_input
166
+ end
167
+
168
+ #This is used to check for user input being used directly.
169
+ #
170
+ #Returns false if none is found, otherwise it returns an array
171
+ #where the first element is the type of user input
172
+ #(either :params or :cookies) and the second element is the matching
173
+ #expression
174
+ def has_immediate_user_input? exp
175
+ if params? exp
176
+ return :params, exp
177
+ elsif cookies? exp
178
+ return :cookies, exp
179
+ elsif call? exp
180
+ if sexp? exp[1]
181
+ if ALL_PARAMETERS.include? exp[1] or params? exp[1]
182
+ return :params, exp
183
+ elsif exp[1] == COOKIES
184
+ return :cookies, exp
185
+ else
186
+ false
187
+ end
188
+ else
189
+ false
190
+ end
191
+ elsif sexp? exp
192
+ case exp.node_type
193
+ when :string_interp
194
+ exp.each do |e|
195
+ if sexp? e
196
+ type, match = has_immediate_user_input?(e)
197
+ if type
198
+ return type, match
199
+ end
200
+ end
201
+ end
202
+ false
203
+ when :string_eval
204
+ if sexp? exp[1]
205
+ if exp[1].node_type == :rlist
206
+ exp[1].each do |e|
207
+ if sexp? e
208
+ type, match = has_immediate_user_input?(e)
209
+ if type
210
+ return type, match
211
+ end
212
+ end
213
+ end
214
+ false
215
+ else
216
+ has_immediate_user_input? exp[1]
217
+ end
218
+ end
219
+ when :format
220
+ has_immediate_user_input? exp[1]
221
+ when :if
222
+ (sexp? exp[2] and has_immediate_user_input? exp[2]) or
223
+ (sexp? exp[3] and has_immediate_user_input? exp[3])
224
+ else
225
+ false
226
+ end
227
+ end
228
+ end
229
+
230
+ #Checks for a model attribute at the top level of the
231
+ #expression.
232
+ def has_immediate_model? exp, out = nil
233
+ out = exp if out.nil?
234
+
235
+ if sexp? exp and exp.node_type == :output
236
+ exp = exp[1]
237
+ end
238
+
239
+ if call? exp
240
+ target = exp[1]
241
+ method = exp[2]
242
+
243
+ if call? target and not method.to_s[-1,1] == "?"
244
+ has_immediate_model? target, out
245
+ elsif model_name? target
246
+ exp
247
+ else
248
+ false
249
+ end
250
+ elsif sexp? exp
251
+ case exp.node_type
252
+ when :string_interp
253
+ exp.each do |e|
254
+ if sexp? e and match = has_immediate_model?(e, out)
255
+ return match
256
+ end
257
+ end
258
+ false
259
+ when :string_eval
260
+ if sexp? exp[1]
261
+ if exp[1].node_type == :rlist
262
+ exp[1].each do |e|
263
+ if sexp? e and match = has_immediate_model?(e, out)
264
+ return match
265
+ end
266
+ end
267
+ false
268
+ else
269
+ has_immediate_model? exp[1], out
270
+ end
271
+ end
272
+ when :format
273
+ has_immediate_model? exp[1], out
274
+ when :if
275
+ ((sexp? exp[2] and has_immediate_model? exp[2], out) or
276
+ (sexp? exp[3] and has_immediate_model? exp[3], out))
277
+ else
278
+ false
279
+ end
280
+ end
281
+ end
282
+
283
+ #Checks if +exp+ is a model name.
284
+ #
285
+ #Prior to using this method, either @tracker must be set to
286
+ #the current tracker, or else @models should contain an array of the model
287
+ #names, which is available via tracker.models.keys
288
+ def model_name? exp
289
+ @models ||= @tracker.models.keys
290
+
291
+ if exp.is_a? Symbol
292
+ @models.include? exp
293
+ elsif sexp? exp
294
+ klass = nil
295
+ begin
296
+ klass = class_name exp
297
+ rescue StandardError
298
+ end
299
+
300
+ klass and @models.include? klass
301
+ else
302
+ false
303
+ end
304
+ end
305
+
306
+ #Finds entire method call chain where +target+ is a target in the chain
307
+ def find_chain exp, target
308
+ return unless sexp? exp
309
+
310
+ case exp.node_type
311
+ when :output, :format
312
+ find_chain exp[1], target
313
+ when :call
314
+ if exp == target or include_target? exp, target
315
+ return exp
316
+ end
317
+ else
318
+ exp.each do |e|
319
+ if sexp? e
320
+ res = find_chain e, target
321
+ return res if res
322
+ end
323
+ end
324
+ nil
325
+ end
326
+ end
327
+
328
+ #Returns true if +target+ is in +exp+
329
+ def include_target? exp, target
330
+ return false unless call? exp
331
+
332
+ exp.each do |e|
333
+ return true if e == target or include_target? e, target
334
+ end
335
+
336
+ false
337
+ end
338
+
339
+ #Returns true if low_version <= RAILS_VERSION <= high_version
340
+ def version_between? low_version, high_version
341
+ version = tracker.config[:rails_version].split(".").map! { |n| n.to_i }
342
+ low_version = low_version.split(".").map! { |n| n.to_i }
343
+ high_version = high_version.split(".").map! { |n| n.to_i }
344
+
345
+ version.each_with_index do |n, i|
346
+ if n < low_version[i] or n > high_version[i]
347
+ return false
348
+ end
349
+ end
350
+
351
+ return true
352
+ end
353
+ end
@@ -0,0 +1,324 @@
1
+ require 'checks/base_check'
2
+ require 'processors/lib/find_call'
3
+ require 'processors/lib/processor_helper'
4
+ require 'util'
5
+ require 'set'
6
+
7
+ #This check looks for unescaped output in templates which contains
8
+ #parameters or model attributes.
9
+ #
10
+ #For example:
11
+ #
12
+ # <%= User.find(:id).name %>
13
+ # <%= params[:id] %>
14
+ class CheckCrossSiteScripting < BaseCheck
15
+ Checks.add self
16
+
17
+ #Ignore these methods and their arguments.
18
+ #It is assumed they will take care of escaping their output.
19
+ IGNORE_METHODS = Set.new([:h, :escapeHTML, :link_to, :text_field_tag, :hidden_field_tag,
20
+ :image_tag, :select, :submit_tag, :hidden_field, :url_encode,
21
+ :radio_button, :will_paginate, :button_to, :url_for, :mail_to,
22
+ :fields_for, :label, :text_area, :text_field, :hidden_field, :check_box,
23
+ :field_field])
24
+
25
+ IGNORE_MODEL_METHODS = Set.new([:average, :count, :maximum, :minimum, :sum])
26
+
27
+ MODEL_METHODS = Set.new([:all, :find, :first, :last, :new])
28
+
29
+ IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
30
+
31
+ HAML_HELPERS = Sexp.new(:colon2, Sexp.new(:const, :Haml), :Helpers)
32
+
33
+ URI = Sexp.new(:const, :URI)
34
+
35
+ CGI = Sexp.new(:const, :CGI)
36
+
37
+ FORM_BUILDER = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new, Sexp.new(:arglist))
38
+
39
+ #Run check
40
+ def run_check
41
+ IGNORE_METHODS.merge OPTIONS[:safe_methods]
42
+ @models = tracker.models.keys
43
+ @inspect_arguments = OPTIONS[:check_arguments]
44
+
45
+ CheckLinkTo.new(checks, tracker).run_check
46
+
47
+ tracker.each_template do |name, template|
48
+ @current_template = template
49
+
50
+ template[:outputs].each do |out|
51
+ type, match = has_immediate_user_input?(out[1])
52
+ if type and not duplicate? out
53
+ add_result out
54
+ case type
55
+ when :params
56
+ message = "Unescaped parameter value"
57
+ when :cookies
58
+ message = "Unescaped cookie value"
59
+ else
60
+ message = "Unescaped user input value"
61
+ end
62
+
63
+ warn :template => @current_template,
64
+ :warning_type => "Cross Site Scripting",
65
+ :message => message,
66
+ :line => match.line,
67
+ :code => match,
68
+ :confidence => CONFIDENCE[:high]
69
+
70
+ elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(out[1])
71
+ method = match[2]
72
+
73
+ unless duplicate? out or IGNORE_MODEL_METHODS.include? method
74
+ add_result out
75
+
76
+ if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
77
+ confidence = CONFIDENCE[:high]
78
+ else
79
+ confidence = CONFIDENCE[:med]
80
+ end
81
+
82
+ code = find_chain out, match
83
+ warn :template => @current_template,
84
+ :warning_type => "Cross Site Scripting",
85
+ :message => "Unescaped model attribute",
86
+ :line => code.line,
87
+ :code => code,
88
+ :confidence => confidence
89
+ end
90
+
91
+ else
92
+ @matched = false
93
+ @mark = false
94
+ process out
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ #Process an output Sexp
101
+ def process_output exp
102
+ process exp[1].dup
103
+ end
104
+
105
+ #Check a call for user input
106
+ #
107
+ #
108
+ #Since we want to report an entire call and not just part of one, use @mark
109
+ #to mark when a call is started. Any dangerous values inside will then
110
+ #report the entire call chain.
111
+ def process_call exp
112
+ if @mark
113
+ actually_process_call exp
114
+ else
115
+ @mark = true
116
+ actually_process_call exp
117
+ message = nil
118
+
119
+ if @matched == :model and not OPTIONS[:ignore_model_output]
120
+ message = "Unescaped model attribute"
121
+ elsif @matched == :params
122
+ message = "Unescaped parameter value"
123
+ end
124
+
125
+ if message and not duplicate? exp
126
+ add_result exp
127
+
128
+ warn :template => @current_template,
129
+ :warning_type => "Cross Site Scripting",
130
+ :message => message,
131
+ :line => exp.line,
132
+ :code => exp,
133
+ :confidence => CONFIDENCE[:low]
134
+ end
135
+
136
+ @mark = @matched = false
137
+ end
138
+
139
+ exp
140
+ end
141
+
142
+ def actually_process_call exp
143
+ return if @matched
144
+ target = exp[1]
145
+ if sexp? target
146
+ target = process target
147
+ end
148
+
149
+ method = exp[2]
150
+ args = exp[3]
151
+
152
+ #Ignore safe items
153
+ if (target.nil? and (IGNORE_METHODS.include? method or method.to_s =~ IGNORE_LIKE)) or
154
+ (@matched == :model and IGNORE_MODEL_METHODS.include? method) or
155
+ (target == HAML_HELPERS and method == :html_escape) or
156
+ ((target == URI or target == CGI) and method == :escape) or
157
+ (target == FORM_BUILDER and IGNORE_METHODS.include? method) or
158
+ (method.to_s[-1,1] == "?")
159
+
160
+ exp[0] = :ignore
161
+ @matched = false
162
+ elsif sexp? exp[1] and model_name? exp[1][1]
163
+
164
+ @matched = :model
165
+ elsif @inspect_arguments and (ALL_PARAMETERS.include?(exp) or params? exp)
166
+
167
+ @matched = :params
168
+ elsif @inspect_arguments
169
+ process args
170
+ end
171
+ end
172
+
173
+ #Note that params have been found
174
+ def process_params exp
175
+ @matched = :params
176
+ exp
177
+ end
178
+
179
+ #Note that cookies have been found
180
+ def process_cookies exp
181
+ @matched = :cookies
182
+ exp
183
+ end
184
+
185
+ #Ignore calls to render
186
+ def process_render exp
187
+ exp
188
+ end
189
+
190
+ #Process as default
191
+ def process_string_interp exp
192
+ process_default exp
193
+ end
194
+
195
+ #Process as default
196
+ def process_format exp
197
+ process_default exp
198
+ end
199
+
200
+ #Ignore output HTML escaped via HAML
201
+ def process_format_escaped exp
202
+ exp
203
+ end
204
+
205
+ #Ignore condition in if Sexp
206
+ def process_if exp
207
+ exp[2..-1].each do |e|
208
+ process e if sexp? e
209
+ end
210
+ exp
211
+ end
212
+ end
213
+
214
+ #This _only_ checks calls to link_to
215
+ class CheckLinkTo < CheckCrossSiteScripting
216
+ IGNORE_METHODS = IGNORE_METHODS - [:link_to]
217
+
218
+ def run_check
219
+ #Ideally, I think this should also check to see if people are setting
220
+ #:escape => false
221
+ methods = tracker.find_call [], :link_to
222
+
223
+ @models = tracker.models.keys
224
+ @inspect_arguments = OPTIONS[:check_arguments]
225
+
226
+ methods.each do |call|
227
+ process_result call
228
+ end
229
+ end
230
+
231
+ def process_result result
232
+ #Have to make a copy of this, otherwise it will be changed to
233
+ #an ignored method call by the code above.
234
+ call = result[-1] = result[-1].dup
235
+
236
+ @matched = false
237
+
238
+ return if call[3][1].nil?
239
+
240
+ #Only check first argument for +link_to+, as the second
241
+ #will *usually* be a record or escaped.
242
+ first_arg = process call[3][1]
243
+
244
+ type, match = has_immediate_user_input? first_arg
245
+
246
+ if type
247
+ case type
248
+ when :params
249
+ message = "Unescaped parameter value in link_to"
250
+ when :cookies
251
+ message = "Unescaped cookie value in link_to"
252
+ else
253
+ message = "Unescaped user input value in link_to"
254
+ end
255
+
256
+ unless duplicate? result
257
+ add_result result
258
+
259
+ warn :result => result,
260
+ :warning_type => "Cross Site Scripting",
261
+ :message => message,
262
+ :confidence => CONFIDENCE[:high]
263
+ end
264
+
265
+ elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(first_arg)
266
+ method = match[2]
267
+
268
+ unless duplicate? result or IGNORE_MODEL_METHODS.include? method
269
+ add_result result
270
+
271
+ if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
272
+ confidence = CONFIDENCE[:high]
273
+ else
274
+ confidence = CONFIDENCE[:med]
275
+ end
276
+
277
+ warn :result => result,
278
+ :warning_type => "Cross Site Scripting",
279
+ :message => "Unescaped model attribute in link_to",
280
+ :confidence => confidence
281
+ end
282
+
283
+ elsif @matched
284
+ if @matched == :model and not OPTIONS[:ignore_model_output]
285
+ message = "Unescaped model attribute in link_to"
286
+ elsif @matched == :params
287
+ message = "Unescaped parameter value in link_to"
288
+ end
289
+
290
+ if message and not duplicate? result
291
+ add_result result
292
+
293
+ warn :result => result,
294
+ :warning_type => "Cross Site Scripting",
295
+ :message => message,
296
+ :confidence => CONFIDENCE[:med]
297
+ end
298
+ end
299
+ end
300
+
301
+ def process_call exp
302
+ @mark = true
303
+ actually_process_call exp
304
+ exp
305
+ end
306
+
307
+
308
+ def actually_process_call exp
309
+ return if @matched
310
+
311
+ target = exp[1]
312
+ if sexp? target
313
+ target = process target.dup
314
+ end
315
+
316
+ #Bare records create links to the model resource,
317
+ #not a string that could have injection
318
+ if model_name? target and context == [:call, :arglist]
319
+ return exp
320
+ end
321
+
322
+ super
323
+ end
324
+ end