This check looks for unescaped output in templates which contains parameters or model attributes.
For example:
<%= User.find(:id).name %> <%= params[:id] %>
Model methods which are known to be harmless
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 226 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 (target.nil? and (@ignore_methods.include? method or method.to_s =~ IGNORE_LIKE)) or (@matched and @matched.type == :model and IGNORE_MODEL_METHODS.include? method) or (target == HAML_HELPERS and method == :html_escape) or ((target == URI or target == CGI) and method == :escape) or (target == XML_HELPER and method == :escape_xml) or (target == FORM_BUILDER and @ignore_methods.include? method) or (target and @safe_input_attributes.include? method) or (method.to_s[-1,1] == "?") #exp[0] = :ignore #should not be necessary @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
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 88 def check_for_immediate_xss exp return if duplicate? exp if exp.node_type == :output out = exp.value elsif exp.node_type == :escaped_output and raw_call? exp out = exp.value.first_arg end if input = has_immediate_user_input?(out) add_result exp case input.type when :params message = "Unescaped parameter value" when :cookies message = "Unescaped cookie value" when :request message = "Unescaped request value" else message = "Unescaped user input value" end warn :template => @current_template, :warning_type => "Cross Site Scripting", :message => message, :code => input.match, :confidence => 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 out if MODEL_METHODS.include? method or method.to_s =~ /^find_by/ confidence = CONFIDENCE[:high] else confidence = CONFIDENCE[:med] end message = "Unescaped model attribute" link_path = "cross_site_scripting" if node_type?(out, :call, :attrasgn) && out.method == :to_json message += " in JSON hash" link_path += "_to_json" end code = find_chain out, match warn :template => @current_template, :warning_type => "Cross Site Scripting", :message => message, :code => code, :confidence => confidence, :link_path => link_path end else false end end
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/brakeman/checks/check_cross_site_scripting.rb, line 176 def process_call exp if @mark actually_process_call exp else @mark = true actually_process_call exp message = nil if @matched case @matched.type when :model unless tracker.options[:ignore_model_output] message = "Unescaped model attribute" end when :params message = "Unescaped parameter value" when :cookies message = "Unescaped cookie value" end if message and not duplicate? exp add_result exp link_path = "cross_site_scripting" if @known_dangerous.include? exp.method confidence = CONFIDENCE[:high] if exp.method == :to_json message += " in JSON hash" link_path += "_to_json" end else confidence = CONFIDENCE[:low] end warn :template => @current_template, :warning_type => "Cross Site Scripting", :message => message, :code => exp, :user_input => @matched.match, :confidence => confidence, :link_path => link_path end end @mark = @matched = false end exp end
Look for calls to raw() Otherwise, ignore
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 161 def process_escaped_output exp unless check_for_immediate_xss exp if raw_call? exp and not duplicate? exp process exp.value.first_arg end end exp end
Process as default
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 281 def process_format exp process_default exp end
Ignore output HTML escaped via HAML
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 286 def process_format_escaped exp exp end
Ignore condition in if Sexp
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 291 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 an output Sexp
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 155 def process_output exp process exp.value.dup end
Note that params have been found
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 259 def process_params exp @matched = Match.new(:params, exp) exp end
Ignore calls to render
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 271 def process_render exp exp end
Process as default
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 276 def process_string_interp exp process_default exp end
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 297 def raw_call? exp exp.value.node_type == :call and exp.value.method == :raw end
Run check
# File lib/brakeman/checks/check_cross_site_scripting.rb, line 37 def run_check @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, :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" @known_dangerous << :strip_tags 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 !json_escape_on or version_between? "0.0.0", "2.0.99" @known_dangerous << :to_json Brakeman.debug("Automatic to_json escaping not enabled, consider to_json dangerous") else Brakeman.debug("Automatic to_json escaping is enabled.") end tracker.each_template do |name, template| @current_template = template template[:outputs].each do |out| Brakeman.debug "Checking #{name} for direct XSS" unless check_for_immediate_xss out Brakeman.debug "Checking #{name} for indirect XSS" @matched = false @mark = false process out end end end end
Generated with the Darkfish Rdoc Generator 2.