class Railroader::CheckCrossSiteScripting
This check looks for unescaped output in templates which contains parameters or model attributes.
For example:
<%= User.find(:id).name %> <%= params %>
Constants
- CGI
- FORM_BUILDER
- HAML_HELPERS
- IGNORE_LIKE
- IGNORE_MODEL_METHODS
Model methods which are known to be harmless
- MODEL_METHODS
- URI
- XML_HELPER
Public Instance Methods
actually_process_call(exp)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 208 def actually_process_call exp return if @matched target = exp.target if sexp? target target = process target end method = exp.method # Ignore safe items if ignore_call? target, method @matched = false elsif sexp? target and model_name? target[1] # TODO: use method call? @matched = Match.new(:model, exp) elsif cookies? exp @matched = Match.new(:cookies, exp) elsif @inspect_arguments and params? exp @matched = Match.new(:params, exp) elsif @inspect_arguments process_call_args exp end end
cgi_escaped?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 361 def cgi_escaped? target, method method == :escape and (target == URI or target == CGI) end
check_for_immediate_xss(exp)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 55 def check_for_immediate_xss exp return :duplicate if duplicate? exp if exp.node_type == :output out = exp.value elsif exp.node_type == :escaped_output if raw_call? exp out = exp.value.first_arg elsif html_safe_call? exp out = exp.value.target end end return if call? out and ignore_call? out.target, out.method if input = has_immediate_user_input?(out) add_result exp message = "Unescaped #{friendly_type_of input}" warn :template => @current_template, :warning_type => "Cross-Site Scripting", :warning_code => :cross_site_scripting, :message => message, :code => input.match, :confidence => :high elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(out) method = if call? match match.method else nil end unless IGNORE_MODEL_METHODS.include? method add_result exp if likely_model_attribute? match confidence = :high else confidence = :medium end message = "Unescaped model attribute" link_path = "cross_site_scripting" warning_code = :cross_site_scripting if node_type?(out, :call, :safe_call, :attrasgn, :safe_attrasgn) && out.method == :to_json message += " in JSON hash" link_path += "_to_json" warning_code = :xss_to_json end warn :template => @current_template, :warning_type => "Cross-Site Scripting", :warning_code => warning_code, :message => message, :code => match, :confidence => confidence, :link_path => link_path end else false end end
form_builder_method?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 374 def form_builder_method? target, method target == FORM_BUILDER and @ignore_methods.include? method end
haml_escaped?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 366 def haml_escaped? target, method method == :html_escape and target == HAML_HELPERS end
html_safe_call?(exp)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 336 def html_safe_call? exp call? exp.value and exp.value.method == :html_safe end
ignore_call?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 340 def ignore_call? target, method ignored_method?(target, method) or safe_input_attribute?(target, method) or ignored_model_method?(target, method) or form_builder_method?(target, method) or haml_escaped?(target, method) or boolean_method?(method) or cgi_escaped?(target, method) or xml_escaped?(target, method) end
ignored_method?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 357 def ignored_method? target, method @ignore_methods.include? method or method.to_s =~ IGNORE_LIKE end
ignored_model_method?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 351 def ignored_model_method? target, method ((@matched and @matched.type == :model) or model_name? target) and IGNORE_MODEL_METHODS.include? method end
likely_model_attribute?(exp)
click to toggle source
Call already involves a model, but might not be acting on a record
# File lib/railroader/checks/check_cross_site_scripting.rb, line 123 def likely_model_attribute? exp return false unless call? exp method = exp.method if MODEL_METHODS.include? method or method.to_s.start_with? "find_by_" true else likely_model_attribute? exp.target end end
process_call(exp)
click to toggle source
Check a call for user input
Since we want to report an entire call and not just part of one, use @mark to mark when a call is started. Any dangerous values inside will then report the entire call chain.
# File lib/railroader/checks/check_cross_site_scripting.rb, line 161 def process_call exp if @mark actually_process_call exp else @mark = true actually_process_call exp message = nil if @matched unless @matched.type and tracker.options[:ignore_model_output] message = "Unescaped #{friendly_type_of @matched}" end if message and not duplicate? exp add_result exp link_path = "cross_site_scripting" warning_code = :cross_site_scripting if @known_dangerous.include? exp.method confidence = :high if exp.method == :to_json message += " in JSON hash" link_path += "_to_json" warning_code = :xss_to_json end else confidence = :weak end warn :template => @current_template, :warning_type => "Cross-Site Scripting", :warning_code => warning_code, :message => message, :code => exp, :user_input => @matched, :confidence => confidence, :link_path => link_path end end @mark = @matched = false end exp end
process_case(exp)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 270 def process_case exp # Ignore user input in case value # TODO: also ignore when values current = 2 while current < exp.length process exp[current] if exp[current] current += 1 end exp end
process_dstr(exp)
click to toggle source
Process as default
# File lib/railroader/checks/check_cross_site_scripting.rb, line 249 def process_dstr exp process_default exp end
process_escaped_output(exp)
click to toggle source
Look for calls to raw() Otherwise, ignore
# File lib/railroader/checks/check_cross_site_scripting.rb, line 142 def process_escaped_output exp unless check_for_immediate_xss exp if not duplicate? exp if raw_call? exp process exp.value.first_arg elsif html_safe_call? exp process exp.value.target end end end exp end
process_format(exp)
click to toggle source
Process as default
# File lib/railroader/checks/check_cross_site_scripting.rb, line 254 def process_format exp process_default exp end
process_format_escaped(exp)
click to toggle source
Ignore output HTML escaped via HAML
# File lib/railroader/checks/check_cross_site_scripting.rb, line 259 def process_format_escaped exp exp end
process_if(exp)
click to toggle source
Ignore condition in if Sexp
# File lib/railroader/checks/check_cross_site_scripting.rb, line 264 def process_if exp process exp.then_clause if sexp? exp.then_clause process exp.else_clause if sexp? exp.else_clause exp end
process_output(exp)
click to toggle source
Process an output Sexp
# File lib/railroader/checks/check_cross_site_scripting.rb, line 136 def process_output exp process exp.value.dup end
process_params(exp)
click to toggle source
Note that params have been found
# File lib/railroader/checks/check_cross_site_scripting.rb, line 232 def process_params exp @matched = Match.new(:params, exp) exp end
process_render(exp)
click to toggle source
Ignore calls to render
# File lib/railroader/checks/check_cross_site_scripting.rb, line 244 def process_render exp exp end
raw_call?(exp)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 332 def raw_call? exp exp.value.node_type == :call and exp.value.method == :raw end
run_check()
click to toggle source
Run check
# File lib/railroader/checks/check_cross_site_scripting.rb, line 37 def run_check setup tracker.each_template do |name, template| Railroader.debug "Checking #{name} for XSS" @current_template = template template.each_output do |out| unless check_for_immediate_xss out @matched = false @mark = false process out end end end end
safe_input_attribute?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 378 def safe_input_attribute? target, method target and always_safe_method? method end
setup()
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 283 def setup @ignore_methods = Set[:==, :!=, :button_to, :check_box, :content_tag, :escapeHTML, :escape_once, :field_field, :fields_for, :h, :hidden_field, :hidden_field, :hidden_field_tag, :image_tag, :label, :link_to, :mail_to, :radio_button, :select, :submit_tag, :text_area, :text_field, :text_field_tag, :url_encode, :u, :url_for, :will_paginate].merge tracker.options[:safe_methods] @models = tracker.models.keys @inspect_arguments = tracker.options[:check_arguments] @known_dangerous = Set[:truncate, :concat] if version_between? "2.0.0", "3.0.5" @known_dangerous << :auto_link elsif version_between? "3.0.6", "3.0.99" @ignore_methods << :auto_link end if version_between? "2.0.0", "2.3.14" or tracker.config.gem_version(:'rails-html-sanitizer') == '1.0.2' @known_dangerous << :strip_tags end if tracker.config.has_gem? :'rails-html-sanitizer' and version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer') @known_dangerous << :sanitize end json_escape_on = false initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json= initializers.each {|result| json_escape_on = true?(result.call.first_arg) } if tracker.config.escape_html_entities_in_json? json_escape_on = true elsif version_between? "4.0.0", "9.9.9" json_escape_on = true end if !json_escape_on or version_between? "0.0.0", "2.0.99" @known_dangerous << :to_json Railroader.debug("Automatic to_json escaping not enabled, consider to_json dangerous") else @safe_input_attributes << :to_json Railroader.debug("Automatic to_json escaping is enabled.") end end
xml_escaped?(target, method)
click to toggle source
# File lib/railroader/checks/check_cross_site_scripting.rb, line 370 def xml_escaped? target, method method == :escape_xml and target == XML_HELPER end