Control: tags 1008324 + patch Control: tags 1008324 + pending Dear maintainer,
I've prepared an NMU for ruby-rails-html-sanitizer (versioned as 1.4.3-0.1) and uploaded it to DELAYED/15. Please feel free to tell me if I should cancel it. cu Adrian
diff -Nru ruby-rails-html-sanitizer-1.4.2/CHANGELOG.md ruby-rails-html-sanitizer-1.4.3/CHANGELOG.md --- ruby-rails-html-sanitizer-1.4.2/CHANGELOG.md 2021-08-29 18:20:32.000000000 +0300 +++ ruby-rails-html-sanitizer-1.4.3/CHANGELOG.md 2022-06-27 20:47:54.000000000 +0300 @@ -1,3 +1,14 @@ +## 1.4.3 / 2022-06-09 + +* Address a possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. + + Prevent the combination of `select` and `style` as allowed tags in SafeListSanitizer. + + Fixes CVE-2022-32209 + + *Mike Dalessio* + + ## 1.4.2 / 2021-08-23 * Slightly improve performance. diff -Nru ruby-rails-html-sanitizer-1.4.2/debian/changelog ruby-rails-html-sanitizer-1.4.3/debian/changelog --- ruby-rails-html-sanitizer-1.4.2/debian/changelog 2022-01-23 21:21:08.000000000 +0200 +++ ruby-rails-html-sanitizer-1.4.3/debian/changelog 2022-10-16 02:44:52.000000000 +0300 @@ -1,3 +1,12 @@ +ruby-rails-html-sanitizer (1.4.3-0.1) unstable; urgency=low + + * Non-maintainer upload. + * New upstream release. + - Fixes FTBFS with Ruby 3. (Closes: #1008324) + - CVE-2022-32209: Possible XSS vulnerability. + + -- Adrian Bunk <b...@debian.org> Sun, 16 Oct 2022 02:44:52 +0300 + ruby-rails-html-sanitizer (1.4.2-2) unstable; urgency=medium * Team upload. diff -Nru ruby-rails-html-sanitizer-1.4.2/lib/rails/html/sanitizer/version.rb ruby-rails-html-sanitizer-1.4.3/lib/rails/html/sanitizer/version.rb --- ruby-rails-html-sanitizer-1.4.2/lib/rails/html/sanitizer/version.rb 2021-08-29 18:20:32.000000000 +0300 +++ ruby-rails-html-sanitizer-1.4.3/lib/rails/html/sanitizer/version.rb 2022-06-27 20:47:54.000000000 +0300 @@ -1,7 +1,7 @@ module Rails module Html class Sanitizer - VERSION = "1.4.2" + VERSION = "1.4.3" end end end diff -Nru ruby-rails-html-sanitizer-1.4.2/lib/rails/html/sanitizer.rb ruby-rails-html-sanitizer-1.4.3/lib/rails/html/sanitizer.rb --- ruby-rails-html-sanitizer-1.4.2/lib/rails/html/sanitizer.rb 2021-08-29 18:20:32.000000000 +0300 +++ ruby-rails-html-sanitizer-1.4.3/lib/rails/html/sanitizer.rb 2022-06-27 20:47:54.000000000 +0300 @@ -141,8 +141,25 @@ private + def loofah_using_html5? + # future-proofing, see https://github.com/flavorjones/loofah/pull/239 + Loofah.respond_to?(:html5_mode?) && Loofah.html5_mode? + end + + def remove_safelist_tag_combinations(tags) + if !loofah_using_html5? && tags.include?("select") && tags.include?("style") + warn("WARNING: #{self.class}: removing 'style' from safelist, should not be combined with 'select'") + tags.delete("style") + end + tags + end + def allowed_tags(options) - options[:tags] || self.class.allowed_tags + if options[:tags] + remove_safelist_tag_combinations(options[:tags]) + else + self.class.allowed_tags + end end def allowed_attributes(options) diff -Nru ruby-rails-html-sanitizer-1.4.2/rails-html-sanitizer.gemspec ruby-rails-html-sanitizer-1.4.3/rails-html-sanitizer.gemspec --- ruby-rails-html-sanitizer-1.4.2/rails-html-sanitizer.gemspec 2021-08-29 18:20:32.000000000 +0300 +++ ruby-rails-html-sanitizer-1.4.3/rails-html-sanitizer.gemspec 2022-06-27 20:47:54.000000000 +0300 @@ -2,42 +2,36 @@ # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- -# stub: rails-html-sanitizer 1.4.2 ruby lib +# stub: rails-html-sanitizer 1.4.3 ruby lib Gem::Specification.new do |s| s.name = "rails-html-sanitizer".freeze - s.version = "1.4.2" + s.version = "1.4.3" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.metadata = { "bug_tracker_uri" => "https://github.com/rails/rails-html-sanitizer/issues", "changelog_uri" => "https://github.com/rails/rails-html-sanitizer/blob/v1.4.2/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/gems/rails-html-sanitizer/1.4.2", "source_code_uri" => "https://github.com/rails/rails-html-sanitizer/tree/v1.4.2" } if s.respond_to? :metadata= + s.metadata = { "bug_tracker_uri" => "https://github.com/rails/rails-html-sanitizer/issues", "changelog_uri" => "https://github.com/rails/rails-html-sanitizer/blob/v1.4.3/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/gems/rails-html-sanitizer/1.4.3", "source_code_uri" => "https://github.com/rails/rails-html-sanitizer/tree/v1.4.3" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Rafael Mendon\u00E7a Fran\u00E7a".freeze, "Kasper Timm Hansen".freeze] - s.date = "2021-08-24" + s.date = "2022-06-09" s.description = "HTML sanitization for Rails applications".freeze s.email = ["rafaelmfra...@gmail.com".freeze, "kas...@gmail.com".freeze] s.files = ["CHANGELOG.md".freeze, "MIT-LICENSE".freeze, "README.md".freeze, "lib/rails-html-sanitizer.rb".freeze, "lib/rails/html/sanitizer.rb".freeze, "lib/rails/html/sanitizer/version.rb".freeze, "lib/rails/html/scrubbers.rb".freeze, "test/sanitizer_test.rb".freeze, "test/scrubbers_test.rb".freeze] s.homepage = "https://github.com/rails/rails-html-sanitizer".freeze s.licenses = ["MIT".freeze] - s.rubygems_version = "2.7.6.2".freeze + s.rubygems_version = "3.2.5".freeze s.summary = "This gem is responsible to sanitize HTML fragments in Rails applications.".freeze s.test_files = ["test/sanitizer_test.rb".freeze, "test/scrubbers_test.rb".freeze] if s.respond_to? :specification_version then s.specification_version = 4 + end - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_development_dependency(%q<bundler>.freeze, [">= 1.3"]) - s.add_runtime_dependency(%q<loofah>.freeze, ["~> 2.3"]) - s.add_development_dependency(%q<minitest>.freeze, [">= 0"]) - s.add_development_dependency(%q<rails-dom-testing>.freeze, [">= 0"]) - s.add_development_dependency(%q<rake>.freeze, [">= 0"]) - else - s.add_dependency(%q<bundler>.freeze, [">= 1.3"]) - s.add_dependency(%q<loofah>.freeze, ["~> 2.3"]) - s.add_dependency(%q<minitest>.freeze, [">= 0"]) - s.add_dependency(%q<rails-dom-testing>.freeze, [">= 0"]) - s.add_dependency(%q<rake>.freeze, [">= 0"]) - end + if s.respond_to? :add_runtime_dependency then + s.add_development_dependency(%q<bundler>.freeze, [">= 1.3"]) + s.add_runtime_dependency(%q<loofah>.freeze, ["~> 2.3"]) + s.add_development_dependency(%q<minitest>.freeze, [">= 0"]) + s.add_development_dependency(%q<rails-dom-testing>.freeze, [">= 0"]) + s.add_development_dependency(%q<rake>.freeze, [">= 0"]) else s.add_dependency(%q<bundler>.freeze, [">= 1.3"]) s.add_dependency(%q<loofah>.freeze, ["~> 2.3"]) diff -Nru ruby-rails-html-sanitizer-1.4.2/test/sanitizer_test.rb ruby-rails-html-sanitizer-1.4.3/test/sanitizer_test.rb --- ruby-rails-html-sanitizer-1.4.2/test/sanitizer_test.rb 2021-08-29 18:20:32.000000000 +0300 +++ ruby-rails-html-sanitizer-1.4.3/test/sanitizer_test.rb 2022-06-27 20:47:54.000000000 +0300 @@ -2,6 +2,8 @@ require "rails-html-sanitizer" require "rails/dom/testing/assertions/dom_assertions" +puts Nokogiri::VERSION_INFO + class SanitizersTest < Minitest::Test include Rails::Dom::Testing::Assertions::DomAssertions @@ -12,13 +14,11 @@ end def test_sanitize_nested_script - sanitizer = Rails::Html::SafeListSanitizer.new - assert_equal '<script>alert("XSS");</script>', sanitizer.sanitize('<script><script></script>alert("XSS");<script><</script>/</script><script>script></script>', tags: %w(em)) + assert_equal '<script>alert("XSS");</script>', safe_list_sanitize('<script><script></script>alert("XSS");<script><</script>/</script><script>script></script>', tags: %w(em)) end def test_sanitize_nested_script_in_style - sanitizer = Rails::Html::SafeListSanitizer.new - assert_equal '<script>alert("XSS");</script>', sanitizer.sanitize('<style><script></style>alert("XSS");<style><</style>/</style><style>script></style>', tags: %w(em)) + assert_equal '<script>alert("XSS");</script>', safe_list_sanitize('<style><script></style>alert("XSS");<style><</style>/</style><style>script></style>', tags: %w(em)) end class XpathRemovalTestSanitizer < Rails::Html::Sanitizer @@ -54,7 +54,8 @@ def test_strip_tags_with_quote input = '<" <img src="trollface.gif" onload="alert(1)"> hi' - assert_equal ' hi', full_sanitize(input) + expected = libxml_2_9_14_recovery? ? %{<" hi} : %{ hi} + assert_equal(expected, full_sanitize(input)) end def test_strip_invalid_html @@ -75,15 +76,21 @@ end def test_remove_unclosed_tags - assert_equal "This is ", full_sanitize("This is <-- not\n a comment here.") + input = "This is <-- not\n a comment here." + expected = libxml_2_9_14_recovery? ? %{This is <-- not\n a comment here.} : %{This is } + assert_equal(expected, full_sanitize(input)) end def test_strip_cdata - assert_equal "This has a ]]> here.", full_sanitize("This has a <![CDATA[<section>]]> here.") + input = "This has a <![CDATA[<section>]]> here." + expected = libxml_2_9_14_recovery? ? %{This has a <![CDATA[]]> here.} : %{This has a ]]> here.} + assert_equal(expected, full_sanitize(input)) end def test_strip_unclosed_cdata - assert_equal "This has an unclosed ]] here...", full_sanitize("This has an unclosed <![CDATA[<section>]] here...") + input = "This has an unclosed <![CDATA[<section>]] here..." + expected = libxml_2_9_14_recovery? ? %{This has an unclosed <![CDATA[]] here...} : %{This has an unclosed ]] here...} + assert_equal(expected, full_sanitize(input)) end def test_strip_blank_string @@ -414,8 +421,25 @@ end def test_should_sanitize_div_background_image_unicode_encoded - raw = %(background-image:\u0075\u0072\u006C\u0028\u0027\u006a\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003a\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0032\u0033\u0034\u0029\u0027\u0029) - assert_equal '', sanitize_css(raw) + [ + convert_to_css_hex("url(javascript:alert(1))", false), + convert_to_css_hex("url(javascript:alert(1))", true), + convert_to_css_hex("url(https://example.com)", false), + convert_to_css_hex("url(https://example.com)", true), + ].each do |propval| + raw = "background-image:" + propval + assert_empty(sanitize_css(raw)) + end + end + + def test_should_allow_div_background_image_unicode_encoded_safe_functions + [ + convert_to_css_hex("rgb(255,0,0)", false), + convert_to_css_hex("rgb(255,0,0)", true), + ].each do |propval| + raw = "background-image:" + propval + assert_includes(sanitize_css(raw), "background-image") + end end def test_should_sanitize_div_style_expression @@ -433,11 +457,15 @@ end def test_should_sanitize_cdata_section - assert_sanitized "<![CDATA[<span>section</span>]]>", "section]]>" + input = "<![CDATA[<span>section</span>]]>" + expected = libxml_2_9_14_recovery? ? %{<![CDATA[<span>section</span>]]>} : %{section]]>} + assert_sanitized(input, expected) end def test_should_sanitize_unterminated_cdata_section - assert_sanitized "<![CDATA[<span>neverending...", "neverending..." + input = "<![CDATA[<span>neverending..." + expected = libxml_2_9_14_recovery? ? %{<![CDATA[<span>neverending...</span>} : %{neverending...} + assert_sanitized(input, expected) end def test_should_not_mangle_urls_with_ampersand @@ -488,7 +516,13 @@ text = safe_list_sanitize(html) - assert_equal %{<a href=\"examp<!--%22%20unsafeattr=foo()>-->le.com\">test</a>}, text + acceptable_results = [ + # nokogiri w/vendored+patched libxml2 + %{<a href="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + # nokogiri w/ system libxml2 + %{<a href="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + ] + assert_includes(acceptable_results, text) end def test_uri_escaping_of_src_attr_in_a_tag_in_safe_list_sanitizer @@ -498,7 +532,13 @@ text = safe_list_sanitize(html) - assert_equal %{<a src=\"examp<!--%22%20unsafeattr=foo()>-->le.com\">test</a>}, text + acceptable_results = [ + # nokogiri w/vendored+patched libxml2 + %{<a src="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + # nokogiri w/system libxml2 + %{<a src="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + ] + assert_includes(acceptable_results, text) end def test_uri_escaping_of_name_attr_in_a_tag_in_safe_list_sanitizer @@ -508,7 +548,13 @@ text = safe_list_sanitize(html) - assert_equal %{<a name=\"examp<!--%22%20unsafeattr=foo()>-->le.com\">test</a>}, text + acceptable_results = [ + # nokogiri w/vendored+patched libxml2 + %{<a name="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + # nokogiri w/system libxml2 + %{<a name="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + ] + assert_includes(acceptable_results, text) end def test_uri_escaping_of_name_action_in_a_tag_in_safe_list_sanitizer @@ -518,7 +564,13 @@ text = safe_list_sanitize(html, attributes: ['action']) - assert_equal %{<a action=\"examp<!--%22%20unsafeattr=foo()>-->le.com\">test</a>}, text + acceptable_results = [ + # nokogiri w/vendored+patched libxml2 + %{<a action="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + # nokogiri w/system libxml2 + %{<a action="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, + ] + assert_includes(acceptable_results, text) end def test_exclude_node_type_processing_instructions @@ -529,6 +581,25 @@ assert_equal("<div>text</div><b>text</b>", safe_list_sanitize("<div>text</div><!-- comment --><b>text</b>")) end + def test_disallow_the_dangerous_safelist_combination_of_select_and_style + input = "<select><style><script>alert(1)</script></style></select>" + tags = ["select", "style"] + warning = /WARNING: Rails::Html::SafeListSanitizer: removing 'style' from safelist/ + sanitized = nil + invocation = Proc.new { sanitized = safe_list_sanitize(input, tags: tags) } + + if html5_mode? + # if Loofah is using an HTML5 parser, + # then "style" should be removed by the parser as an invalid child of "select" + assert_silent(&invocation) + else + # if Loofah is using an HTML4 parser, + # then SafeListSanitizer should remove "style" from the safelist + assert_output(nil, warning, &invocation) + end + refute_includes(sanitized, "style") + end + protected def xpath_sanitize(input, options = {}) @@ -574,4 +645,23 @@ ensure Rails::Html::SafeListSanitizer.allowed_attributes = old_attributes end + + # note that this is used for testing CSS hex encoding: \\[0-9a-f]{1,6} + def convert_to_css_hex(string, escape_parens=false) + string.chars.map do |c| + if !escape_parens && (c == "(" || c == ")") + c + else + format('\00%02X', c.ord) + end + end.join + end + + def libxml_2_9_14_recovery? + Nokogiri.method(:uses_libxml?).arity == -1 && Nokogiri.uses_libxml?(">= 2.9.14") + end + + def html5_mode? + ::Loofah.respond_to?(:html5_mode?) && ::Loofah.html5_mode? + end end