class Brakeman::CheckContentTag

Checks for unescaped values in ‘content_tag`

content_tag :tag, body
                   ^-- Unescaped in Rails 2.x

content_tag, :tag, body, attribute => value
                            ^-- Unescaped in all versions

content_tag, :tag, body, attribute => value
                                        ^
                                        |
        Escaped by default, can be explicitly escaped
        or not by passing in (true|false) as fourth argument

Public Instance Methods

check_argument(result, exp) click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 104
def check_argument result, exp
  #Check contents of raw() calls directly
  if raw? exp
    arg = process exp.first_arg
  else
    arg = process exp
  end

  if input = has_immediate_user_input?(arg)
    message = msg("Unescaped ", msg_input(input), " in ", msg_code("content_tag"))

    add_result result

    warn :result => result,
      :warning_type => "Cross-Site Scripting",
      :warning_code => :xss_content_tag,
      :message => message,
      :user_input => input,
      :confidence => :high,
      :link_path => "content_tag",
      :cwe_id => [79]

  elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(arg)
    unless IGNORE_MODEL_METHODS.include? match.method
      add_result result

      if likely_model_attribute? match
        confidence = :high
      else
        confidence = :medium
      end

      warn :result => result,
        :warning_type => "Cross-Site Scripting",
        :warning_code => :xss_content_tag,
        :message => msg("Unescaped model attribute in ", msg_code("content_tag")),
        :user_input => match,
        :confidence => confidence,
        :link_path => "content_tag",
        :cwe_id => [79]
    end

  elsif @matched
    return if @matched.type == :model and tracker.options[:ignore_model_output]

    message = msg("Unescaped ", msg_input(@matched), " in ", msg_code("content_tag"))

    add_result result

    warn :result => result,
      :warning_type => "Cross-Site Scripting",
      :warning_code => :xss_content_tag,
      :message => message,
      :user_input => @matched,
      :confidence => :medium,
      :link_path => "content_tag",
      :cwe_id => [79]
  end
end
check_cve_2016_6316() click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 176
def check_cve_2016_6316
  if cve_2016_6316?
    confidence = if @content_tags.any?
                   :high
                 else
                   :medium
                 end

    fix_version = case
                  when version_between?("3.0.0", "3.2.22.3")
                    "3.2.22.4"
                  when version_between?("4.0.0", "4.2.7.0")
                    "4.2.7.1"
                  when version_between?("5.0.0", "5.0.0")
                    "5.0.0.1"
                  when (version.nil? and tracker.options[:rails3])
                    "3.2.22.4"
                  when (version.nil? and tracker.options[:rails4])
                    "4.2.7.2"
                  else
                    return
                  end

    warn :warning_type => "Cross-Site Scripting",
      :warning_code => :CVE_2016_6316,
      :message => msg(msg_version(rails_version), " ", msg_code("content_tag"), " does not escape double quotes in attribute values ", msg_cve("CVE-2016-6316"), ". Upgrade to ", msg_version(fix_version)),
      :confidence => confidence,
      :gem_info => gemfile_or_environment,
      :link_path => "https://groups.google.com/d/msg/ruby-security-ann/8B2iV2tPRSE/JkjCJkSoCgAJ",
      :cwe_id => [79]
  end
end
cve_2016_6316?() click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 213
def cve_2016_6316?
  version_between? "3.0.0", "3.2.22.3" or
  version_between? "4.0.0", "4.2.7.0" or
  version_between? "5.0.0", "5.0.0.0"
end
process_call(exp) click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 164
def process_call exp
  if @mark
    actually_process_call exp
  else
    @mark = true
    actually_process_call exp
    @mark = false
  end

  exp
end
process_result(result) click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 45
def process_result result
  return if duplicate? result

  case result[:location][:type]
  when :template
    @current_template = result[:location][:template]
  when :class
    @current_class = result[:location][:class]
    @current_method = result[:location][:method]
  end

  @current_file = result[:location][:file]

  call = result[:call]
  args = call.arglist

  tag_name = args[1]
  content = args[2]
  attributes = args[3]
  escape_attr = args[4]

  @matched = false

  #Silly, but still dangerous if someone uses user input in the tag type
  check_argument result, tag_name

  #Versions before 3.x do not escape body of tag, nor does the rails_xss gem
  unless @matched or (tracker.options[:rails3] and not raw? content)
    check_argument result, content
  end

  # This changed in Rails 6.1.6
  if version_between? '0.0.0', '6.1.5' 
    #Attribute keys are never escaped, so check them for user input
    if not @matched and hash? attributes and not request_value? attributes
      hash_iterate(attributes) do |k, _v|
        check_argument result, k
        return if @matched
      end
    end
  end

  #By default, content_tag escapes attribute values passed in as a hash.
  #But this behavior can be disabled. So only check attributes hash
  #if they are explicitly not escaped.
  if not @matched and attributes and (false? escape_attr or cve_2016_6316?)
    if request_value? attributes or not hash? attributes
      check_argument result, attributes
    else #check hash values
      hash_iterate(attributes) do |_k, v|
        check_argument result, v
        return if @matched
      end
    end
  end
ensure
  @current_template = @current_class = @current_method = @current_file = nil
end
raw?(exp) click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 209
def raw? exp
  call? exp and exp.method == :raw
end
run_check() click to toggle source
# File lib/brakeman/checks/check_content_tag.rb, line 21
def run_check
  @ignore_methods = Set[:button_to, :check_box, :escapeHTML, :escape_once,
                         :field_field, :fields_for, :h, :hidden_field,
                         :hidden_field, :hidden_field_tag, :image_tag, :label,
                         :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]

  @known_dangerous = []
  @content_tags = tracker.find_call :target => false, :method => :content_tag

  @models = tracker.models.keys
  @inspect_arguments = tracker.options[:check_arguments]
  @mark = nil

  Brakeman.debug "Checking for XSS in content_tag"
  @content_tags.each do |call|
    process_result call
  end

  check_cve_2016_6316
end