VYPR
Moderate severityNVD Advisory· Published Jun 30, 2011· Updated Apr 29, 2026

CVE-2011-2197

CVE-2011-2197

Description

The cross-site scripting (XSS) prevention feature in Ruby on Rails 2.x before 2.3.12, 3.0.x before 3.0.8, and 3.1.x before 3.1.0.rc2 does not properly handle mutation of safe buffers, which makes it easier for remote attackers to conduct XSS attacks via crafted strings to an application that uses a problematic string method, as demonstrated by the sub method.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
actionpackRubyGems
>= 2.0.0, < 2.3.122.3.12
actionpackRubyGems
>= 3.0.0, < 3.0.83.0.8
activesupportRubyGems
>= 2.0.0, < 2.3.122.3.12
activesupportRubyGems
>= 3.0.0, < 3.0.83.0.8

Affected products

49
  • Rubyonrails/Rails46 versions
    cpe:2.3:a:rubyonrails:rails:2.0.0:*:*:*:*:*:*:*+ 45 more
    • cpe:2.3:a:rubyonrails:rails:2.0.0:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.0.0:rc1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.0.0:rc2:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.0.2:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.0.4:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.1.0:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.1.1:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.1.2:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.2.0:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.2.1:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.2.2:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.3.10:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.3.11:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.3.2:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.3.3:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.3.4:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:2.3.9:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:beta:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:beta2:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:beta3:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:beta4:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:rc:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.0:rc2:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.1:pre:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.2:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.2:pre:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.3:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.4:rc1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.5:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.5:rc1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.6:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.6:rc1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.6:rc2:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.7:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.7:rc1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.7:rc2:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.8:rc1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.8:rc2:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.8:rc3:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.0.8:rc4:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.1.0:*:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.1.0:beta1:*:*:*:*:*:*
    • cpe:2.3:a:rubyonrails:rails:3.1.0:rc1:*:*:*:*:*:*
  • cpe:2.3:a:rubyonrails:ruby_on_rails:3.0.4:*:*:*:*:*:*:*
  • ghsa-coords2 versions
    >= 2.0.0, < 2.3.12+ 1 more
    • (no CPE)range: >= 2.0.0, < 2.3.12
    • (no CPE)range: >= 2.0.0, < 2.3.12

Patches

2
ed3796434af6

Do not modify a safe buffer in helpers

https://github.com/rails/railsBruno MichelMay 25, 2011via ghsa
2 files changed · +35 31
  • actionpack/lib/action_view/helpers/text_helper.rb+17 23 modified
    @@ -115,13 +115,12 @@ def highlight(text, phrases, *args)
             end
             options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
     
    -        text = sanitize(text) unless options[:sanitize] == false
    -        if text.blank? || phrases.blank?
    -          text
    -        else
    +        if text.present? && phrases.present?
               match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
    -          text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
    -        end.html_safe
    +          text = text.to_str.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
    +        end
    +        text = sanitize(text) unless options[:sanitize] == false
    +        text
           end
     
           # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
    @@ -251,14 +250,16 @@ def word_wrap(text, *args)
           #   simple_format("Look ma! A class!", :class => 'description')
           #   # => "<p class='description'>Look ma! A class!</p>"
           def simple_format(text, html_options={}, options={})
    -        text = ''.html_safe if text.nil?
    +        text = text ? text.to_str : ''
    +        text = text.dup if text.frozen?
             start_tag = tag('p', html_options, true)
    -        text = sanitize(text) unless options[:sanitize] == false
             text.gsub!(/\r\n?/, "\n")                    # \r\n and \r -> \n
             text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}")  # 2+ newline  -> paragraph
             text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline   -> br
             text.insert 0, start_tag
    -        text.html_safe.safe_concat("</p>")
    +        text.concat("</p>")
    +        text = sanitize(text) unless options[:sanitize] == false
    +        text
           end
     
           # Turns all URLs and e-mail addresses into clickable links. The <tt>:link</tt> option
    @@ -477,7 +478,7 @@ def set_cycle(name, cycle_object)
             # is yielded and the result is used as the link text.
             def auto_link_urls(text, html_options = {}, options = {})
               link_attributes = html_options.stringify_keys
    -          text.gsub(AUTO_LINK_RE) do
    +          text.to_str.gsub(AUTO_LINK_RE) do
                 scheme, href = $1, $&
                 punctuation = []
     
    @@ -494,33 +495,26 @@ def auto_link_urls(text, html_options = {}, options = {})
                     end
                   end
     
    -              link_text = block_given?? yield(href) : href
    +              link_text = block_given? ? yield(href) : href
                   href = 'http://' + href unless scheme
     
    -              unless options[:sanitize] == false
    -                link_text = sanitize(link_text)
    -                href      = sanitize(href)
    -              end
    -              content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
    +              sanitize = options[:sanitize] != false
    +              content_tag(:a, link_text, link_attributes.merge('href' => href), sanitize) + punctuation.reverse.join('')
                 end
               end
             end
     
             # Turns all email addresses into clickable links.  If a block is given,
             # each email is yielded and the result is used as the link text.
             def auto_link_email_addresses(text, html_options = {}, options = {})
    -          text.gsub(AUTO_EMAIL_RE) do
    +          text.to_str.gsub(AUTO_EMAIL_RE) do
                 text = $&
     
                 if auto_linked?($`, $')
                   text.html_safe
                 else
    -              display_text = (block_given?) ? yield(text) : text
    -
    -              unless options[:sanitize] == false
    -                text         = sanitize(text)
    -                display_text = sanitize(display_text) unless text == display_text
    -              end
    +              display_text = block_given? ? yield(text) : text
    +              display_text = sanitize(display_text) unless options[:sanitize] == false
                   mail_to text, display_text, html_options
                 end
               end
    
  • actionpack/test/template/text_helper_test.rb+18 8 modified
    @@ -48,6 +48,10 @@ def test_simple_format_should_not_sanitize_input_when_sanitize_option_is_false
         assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false)
       end
     
    +  def test_simple_format_should_not_be_html_safe_when_sanitize_option_is_false
    +    assert !simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false).html_safe?
    +  end
    +
       def test_truncate_should_not_be_html_safe
         assert !truncate("Hello World!", :length => 12).html_safe?
       end
    @@ -166,6 +170,13 @@ def test_highlight_with_options_hash
         )
       end
     
    +  def test_highlight_on_an_html_safe_string
    +    assert_equal(
    +      "<p>This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day</p>",
    +      highlight("<p>This is a beautiful morning, but also a beautiful day</p>".html_safe, "beautiful", :highlighter => '<b>\1</b>')
    +    )
    +  end
    +
       def test_highlight_with_html
         assert_equal(
           "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
    @@ -306,13 +317,10 @@ def test_auto_link_parsing
         end
       end
     
    -  def generate_result(link_text, href = nil, escape = false)
    -    href ||= link_text
    -    if escape
    -      %{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>}
    -    else
    -      %{<a href="#{href}">#{link_text}</a>}
    -    end
    +  def generate_result(link_text, href = nil)
    +    href = CGI::escapeHTML(href || link_text)
    +    text = CGI::escapeHTML(link_text)
    +    %{<a href="#{href}">#{text}</a>}
       end
     
       def test_auto_link_should_not_be_html_safe
    @@ -323,6 +331,8 @@ def test_auto_link_should_not_be_html_safe
         assert !auto_link('').html_safe?, 'should not be html safe'
         assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe'
         assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe'
    +    assert !auto_link(link_raw.html_safe).html_safe?, 'should not be html safe'
    +    assert !auto_link(email_raw.html_safe).html_safe?, 'should not be html safe'
       end
     
       def test_auto_link_email_address
    @@ -425,7 +435,7 @@ def test_auto_link
     
       def test_auto_link_should_sanitize_input_when_sanitize_option_is_not_false
         link_raw     = %{http://www.rubyonrails.com?id=1&num=2}
    -    assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw)
    +    assert_equal %{<a href="http://www.rubyonrails.com?id=1&amp;num=2">http://www.rubyonrails.com?id=1&amp;num=2</a>}, auto_link(link_raw)
       end
     
       def test_auto_link_should_not_sanitize_input_when_sanitize_option_is_false
    
53a2c0baf2b1

Ensure that the strings returned by SafeBuffer#gsub and friends aren't considered html_safe?

https://github.com/rails/railsMichael KoziarskiMay 16, 2011via ghsa
2 files changed · +25 0
  • activesupport/lib/active_support/core_ext/string/output_safety.rb+13 0 modified
    @@ -73,6 +73,7 @@ def html_safe?
     
     module ActiveSupport #:nodoc:
       class SafeBuffer < String
    +    UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze
         alias safe_concat concat
     
         def concat(value)
    @@ -103,6 +104,18 @@ def to_s
         def to_yaml(*args)
           to_str.to_yaml(*args)
         end
    +
    +    for unsafe_method in UNSAFE_STRING_METHODS
    +      class_eval <<-EOT, __FILE__, __LINE__
    +        def #{unsafe_method}(*args)
    +          super.to_str
    +        end
    +
    +        def #{unsafe_method}!(*args)
    +          raise TypeError, "Cannot modify SafeBuffer in place"
    +        end
    +      EOT
    +    end
       end
     end
     
    
  • activesupport/test/safe_buffer_test.rb+12 0 modified
    @@ -38,4 +38,16 @@ def setup
         new_buffer = @buffer.to_s
         assert_equal ActiveSupport::SafeBuffer, new_buffer.class
       end
    +
    +  test "Should not return safe buffer from gsub" do
    +    altered_buffer = @buffer.gsub('', 'asdf')
    +    assert_equal 'asdf', altered_buffer
    +    assert !altered_buffer.html_safe?
    +  end
    +
    +  test "Should not allow gsub! on safe buffers" do
    +    assert_raise TypeError do
    +      @buffer.gsub!('', 'asdf')
    +    end
    +  end
     end
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

13

News mentions

0

No linked articles in our index yet.