class Railroader::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/railroader/checks/check_content_tag.rb, line 90
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 = "Unescaped #{friendly_type_of input} in 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"

  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 => "Unescaped model attribute in content_tag",
        :user_input => match,
        :confidence => confidence,
        :link_path => "content_tag"
    end

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

    message = "Unescaped #{friendly_type_of @matched} in 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"
  end
end
check_cve_2016_6316() click to toggle source
# File lib/railroader/checks/check_content_tag.rb, line 159
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 => "Rails #{rails_version} content_tag does not escape double quotes in attribute values (CVE-2016-6316). Upgrade to #{fix_version}",
      :confidence => confidence,
      :gem_info => gemfile_or_environment,
      :link_path => "https://groups.google.com/d/msg/ruby-security-ann/8B2iV2tPRSE/JkjCJkSoCgAJ"
  end
end
cve_2016_6316?() click to toggle source
# File lib/railroader/checks/check_content_tag.rb, line 195
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/railroader/checks/check_content_tag.rb, line 147
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/railroader/checks/check_content_tag.rb, line 45
def process_result result
  return if duplicate? result

  call = result[:call] = result[:call].dup

  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

  # 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

  # 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
end
raw?(exp) click to toggle source
# File lib/railroader/checks/check_content_tag.rb, line 191
def raw? exp
  call? exp and exp.method == :raw
end
run_check() click to toggle source
# File lib/railroader/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

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

  check_cve_2016_6316
end