CVE-2015-7579 (Ruby, published 2016-02-16T02:59Z)
Description: value: Cross-site scripting (XSS) vulnerability in the rails-html-sanitizer gem 1.0.2 for Ruby on Rails 4.2.x and 5.x allows remote attackers to inject arbitrary web script or HTML via an HTML entity that is mishandled by the Rails::Html::FullSanitizer class.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/rails/rails-html-sanitizer
Files changed:
lib/rails/html/sanitizer.rb
lib/rails/html/scrubbers.rb
test/sanitizer_test.rb
test/scrubbers_test.rb
Commit message: Do not unescape already escaped HTML entities

The full sanitizer was using Loofah's #text method that automatically
escapes HTML entities. That behavior caused some problems where strings
that were not escaped in the older sanitizer started to be escaped. To
fix these problems we used the #text's `encode_special_chars` option as
`false` that not just skipped the HTML entities escaping but unescaped
already escaped entities.

This introduced a security bug because an attacker can pass escaped HTML
tags that will not be sanitized and will be returned as unescaped HTML
tags.

To fix it properly we introduced a new scrubber that will remove all
tags and keep just the text nodes of these tags without changing how
to escape the string.

CVE-2015-7579


Vulnerable code:
        Loofah.fragment(html).tap do |fragment|
          remove_xpaths(fragment, XPATHS_TO_REMOVE)
        end.text(options)

      def properly_encode(fragment, options)
        fragment.xml? ? fragment.to_xml(options) : fragment.to_html(options)
      end
  def test_full_sanitize_allows_turning_off_encoding_special_chars
    assert_equal '&', full_sanitize('&', encode_special_chars: false)
end

Fixed code:

      def properly_encode(fragment, options)
        fragment.xml? ? fragment.to_xml(options) : fragment.to_html(options)
      end
        loofah_fragment = Loofah.fragment(html)

        remove_xpaths(loofah_fragment, XPATHS_TO_REMOVE)
        loofah_fragment.scrub!(TextOnlyScrubber.new)

        properly_encode(loofah_fragment, encoding: 'UTF-8')

    # === Rails::Html::TextOnlyScrubber
    #
    # Rails::Html::TextOnlyScrubber allows you to permit text nodes.
    #
    # Unallowed elements will be stripped, i.e. element is removed but its subtree kept.
    class TextOnlyScrubber < Loofah::Scrubber
      def initialize
        @direction = :bottom_up
      end

      def scrub(node)
        if node.text?
          CONTINUE
        else
          node.before node.children
          node.remove
        end
      end
    end
  def test_full_sanitize_respect_html_escaping_of_the_given_string
    assert_equal 'test\r\nstring', full_sanitize('test\r\nstring')
    assert_equal '&amp;', full_sanitize('&amp;')
    assert_equal '&amp;amp;', full_sanitize('&amp;amp;')
    assert_equal 'omg &lt;script&gt;BOM&lt;/script&gt;', full_sanitize('omg &lt;script&gt;BOM&lt;/script&gt;')
class TextOnlyScrubberTest < ScrubberTest
  def setup
    @scrubber = Rails::Html::TextOnlyScrubber.new
  end

  def test_removes_all_tags_and_keep_the_content
    assert_scrubbed '<tag>hello</tag>', 'hello'
  end

  def test_skips_text_nodes
    assert_node_skipped('some text')
  end
end

end