Skip to content

Commit 79bc10b

Browse files
authored
Merge pull request #147 from rails/flavorjones-port-1.4.4-changes
port: 1.4.4 changes
2 parents 71b5aca + 9ef5975 commit 79bc10b

File tree

6 files changed

+162
-49
lines changed

6 files changed

+162
-49
lines changed

CHANGELOG.md

+35
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,41 @@
77

88
*seyerian*
99

10+
## 1.4.4 / 2022-12-13
11+
12+
* Address inefficient regular expression complexity with certain configurations of Rails::Html::Sanitizer.
13+
14+
Fixes CVE-2022-23517. See
15+
[GHSA-5x79-w82f-gw8w](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-5x79-w82f-gw8w)
16+
for more information.
17+
18+
*Mike Dalessio*
19+
20+
* Address improper sanitization of data URIs.
21+
22+
Fixes CVE-2022-23518 and #135. See
23+
[GHSA-mcvf-2q2m-x72m](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-mcvf-2q2m-x72m)
24+
for more information.
25+
26+
*Mike Dalessio*
27+
28+
* Address possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer.
29+
30+
Fixes CVE-2022-23520. See
31+
[GHSA-rrfc-7g8p-99q8](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-rrfc-7g8p-99q8)
32+
for more information.
33+
34+
*Mike Dalessio*
35+
36+
* Address possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer.
37+
38+
Fixes CVE-2022-23519. See
39+
[GHSA-9h9g-93gc-623h](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-9h9g-93gc-623h)
40+
for more information.
41+
42+
*Mike Dalessio*
43+
44+
1045
## 1.4.3 / 2022-06-09
1146

1247
* Address a possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer.

lib/rails/html/sanitizer.rb

+1-18
Original file line numberDiff line numberDiff line change
@@ -141,25 +141,8 @@ def sanitize_css(style_string)
141141

142142
private
143143

144-
def loofah_using_html5?
145-
# future-proofing, see https://github.com/flavorjones/loofah/pull/239
146-
Loofah.respond_to?(:html5_mode?) && Loofah.html5_mode?
147-
end
148-
149-
def remove_safelist_tag_combinations(tags)
150-
if !loofah_using_html5? && tags.include?("select") && tags.include?("style")
151-
warn("WARNING: #{self.class}: removing 'style' from safelist, should not be combined with 'select'")
152-
tags.delete("style")
153-
end
154-
tags
155-
end
156-
157144
def allowed_tags(options)
158-
if options[:tags]
159-
remove_safelist_tag_combinations(options[:tags])
160-
else
161-
self.class.allowed_tags
162-
end
145+
options[:tags] || self.class.allowed_tags
163146
end
164147

165148
def allowed_attributes(options)

lib/rails/html/sanitizer/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module Rails
22
module Html
33
class Sanitizer
4-
VERSION = "1.4.2"
4+
VERSION = "1.5.0.dev"
55
end
66
end
77
end

lib/rails/html/scrubbers.rb

+7-9
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ def attributes=(attributes)
6262
end
6363

6464
def scrub(node)
65-
if node.cdata?
66-
text = node.document.create_text_node node.text
67-
node.replace text
65+
if Loofah::HTML5::Scrub.cdata_needs_escaping?(node)
66+
replacement = Loofah::HTML5::Scrub.cdata_escape(node)
67+
node.replace(replacement)
6868
return CONTINUE
6969
end
7070
return CONTINUE if skip_node?(node)
@@ -140,15 +140,13 @@ def scrub_attribute(node, attr_node)
140140
end
141141

142142
if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name)
143-
# this block lifted nearly verbatim from HTML5 sanitization
144-
val_unescaped = CGI.unescapeHTML(attr_node.value).gsub(Loofah::HTML5::Scrub::CONTROL_CHARACTERS,'').downcase
145-
if val_unescaped =~ /^[a-z0-9][-+.a-z0-9]*:/ && ! Loofah::HTML5::SafeList::ALLOWED_PROTOCOLS.include?(val_unescaped.split(Loofah::HTML5::SafeList::PROTOCOL_SEPARATOR)[0])
146-
attr_node.remove
147-
end
143+
return if Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
148144
end
145+
149146
if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
150-
attr_node.value = attr_node.value.gsub(/url\s*\(\s*[^#\s][^)]+?\)/m, ' ') if attr_node.value
147+
Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node)
151148
end
149+
152150
if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == 'xlink:href' && attr_node.value =~ /^\s*[^#\s].*/m
153151
attr_node.remove
154152
end

rails-html-sanitizer.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
2626

2727
# NOTE: There's no need to update this dependency for Loofah CVEs
2828
# in minor releases when users can simply run `bundle update loofah`.
29-
spec.add_dependency "loofah", "~> 2.3"
29+
spec.add_dependency "loofah", "~> 2.19", ">= 2.19.1"
3030

3131
spec.add_development_dependency "bundler", ">= 1.3"
3232
spec.add_development_dependency "rake"

test/sanitizer_test.rb

+117-20
Original file line numberDiff line numberDiff line change
@@ -587,23 +587,124 @@ def test_exclude_node_type_comment
587587
assert_equal("<div>text</div><b>text</b>", safe_list_sanitize("<div>text</div><!-- comment --><b>text</b>"))
588588
end
589589

590-
def test_disallow_the_dangerous_safelist_combination_of_select_and_style
591-
input = "<select><style><script>alert(1)</script></style></select>"
592-
tags = ["select", "style"]
593-
warning = /WARNING: Rails::Html::SafeListSanitizer: removing 'style' from safelist/
594-
sanitized = nil
595-
invocation = Proc.new { sanitized = safe_list_sanitize(input, tags: tags) }
596-
597-
if html5_mode?
598-
# if Loofah is using an HTML5 parser,
599-
# then "style" should be removed by the parser as an invalid child of "select"
600-
assert_silent(&invocation)
601-
else
602-
# if Loofah is using an HTML4 parser,
603-
# then SafeListSanitizer should remove "style" from the safelist
604-
assert_output(nil, warning, &invocation)
590+
%w[text/plain text/css image/png image/gif image/jpeg].each do |mediatype|
591+
define_method "test_mediatype_#{mediatype}_allowed" do
592+
input = %Q(<img src="data:#{mediatype};base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">)
593+
expected = input
594+
actual = safe_list_sanitize(input)
595+
assert_equal(expected, actual)
596+
597+
input = %Q(<img src="DATA:#{mediatype};base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">)
598+
expected = input
599+
actual = safe_list_sanitize(input)
600+
assert_equal(expected, actual)
605601
end
606-
refute_includes(sanitized, "style")
602+
end
603+
604+
def test_mediatype_text_html_disallowed
605+
input = %q(<img src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">)
606+
expected = %q(<img>)
607+
actual = safe_list_sanitize(input)
608+
assert_equal(expected, actual)
609+
610+
input = %q(<img src="DATA:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">)
611+
expected = %q(<img>)
612+
actual = safe_list_sanitize(input)
613+
assert_equal(expected, actual)
614+
end
615+
616+
def test_mediatype_image_svg_xml_disallowed
617+
input = %q(<img src="data:image/svg+xml;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">)
618+
expected = %q(<img>)
619+
actual = safe_list_sanitize(input)
620+
assert_equal(expected, actual)
621+
622+
input = %q(<img src="DATA:image/svg+xml;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">)
623+
expected = %q(<img>)
624+
actual = safe_list_sanitize(input)
625+
assert_equal(expected, actual)
626+
end
627+
628+
def test_mediatype_other_disallowed
629+
input = %q(<a href="data:foo;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">foo</a>)
630+
expected = %q(<a>foo</a>)
631+
actual = safe_list_sanitize(input)
632+
assert_equal(expected, actual)
633+
634+
input = %q(<a href="DATA:foo;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">foo</a>)
635+
expected = %q(<a>foo</a>)
636+
actual = safe_list_sanitize(input)
637+
assert_equal(expected, actual)
638+
end
639+
640+
def test_scrubbing_svg_attr_values_that_allow_ref
641+
input = %Q(<div fill="yellow url(http://bad.com/) #fff">hey</div>)
642+
expected = %Q(<div fill="yellow #fff">hey</div>)
643+
actual = scope_allowed_attributes %w(fill) do
644+
safe_list_sanitize(input)
645+
end
646+
647+
assert_equal(expected, actual)
648+
end
649+
650+
def test_style_with_css_payload
651+
input, tags = "<style>div > span { background: \"red\"; }</style>", ["style"]
652+
expected = "<style>div &gt; span { background: \"red\"; }</style>"
653+
actual = safe_list_sanitize(input, tags: tags)
654+
655+
assert_equal(expected, actual)
656+
end
657+
658+
def test_combination_of_select_and_style_with_css_payload
659+
input, tags = "<select><style>div > span { background: \"red\"; }</style></select>", ["select", "style"]
660+
expected = "<select><style>div &gt; span { background: \"red\"; }</style></select>"
661+
actual = safe_list_sanitize(input, tags: tags)
662+
663+
assert_equal(expected, actual)
664+
end
665+
666+
def test_combination_of_select_and_style_with_script_payload
667+
input, tags = "<select><style><script>alert(1)</script></style></select>", ["select", "style"]
668+
expected = "<select><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></select>"
669+
actual = safe_list_sanitize(input, tags: tags)
670+
671+
assert_equal(expected, actual)
672+
end
673+
674+
def test_combination_of_svg_and_style_with_script_payload
675+
input, tags = "<svg><style><script>alert(1)</script></style></svg>", ["svg", "style"]
676+
expected = "<svg><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></svg>"
677+
actual = safe_list_sanitize(input, tags: tags)
678+
679+
assert_equal(expected, actual)
680+
end
681+
682+
def test_combination_of_math_and_style_with_img_payload
683+
input, tags = "<math><style><img src=x onerror=alert(1)></style></math>", ["math", "style"]
684+
expected = "<math><style>&lt;img src=x onerror=alert(1)&gt;</style></math>"
685+
actual = safe_list_sanitize(input, tags: tags)
686+
687+
assert_equal(expected, actual)
688+
689+
input, tags = "<math><style><img src=x onerror=alert(1)></style></math>", ["math", "style", "img"]
690+
expected = "<math><style>&lt;img src=x onerror=alert(1)&gt;</style></math>"
691+
actual = safe_list_sanitize(input, tags: tags)
692+
693+
assert_equal(expected, actual)
694+
end
695+
696+
def test_combination_of_svg_and_style_with_img_payload
697+
input, tags = "<svg><style><img src=x onerror=alert(1)></style></svg>", ["svg", "style"]
698+
expected = "<svg><style>&lt;img src=x onerror=alert(1)&gt;</style></svg>"
699+
actual = safe_list_sanitize(input, tags: tags)
700+
701+
assert_equal(expected, actual)
702+
703+
input, tags = "<svg><style><img src=x onerror=alert(1)></style></svg>", ["svg", "style", "img"]
704+
expected = "<svg><style>&lt;img src=x onerror=alert(1)&gt;</style></svg>"
705+
actual = safe_list_sanitize(input, tags: tags)
706+
707+
assert_equal(expected, actual)
607708
end
608709

609710
protected
@@ -673,8 +774,4 @@ def libxml_2_9_14_recovery_lt_bang?
673774
# then reverted in 2.10.0, see https://gitlab.gnome.org/GNOME/libxml2/-/issues/380
674775
Nokogiri.method(:uses_libxml?).arity == -1 && Nokogiri.uses_libxml?("= 2.9.14")
675776
end
676-
677-
def html5_mode?
678-
::Loofah.respond_to?(:html5_mode?) && ::Loofah.html5_mode?
679-
end
680777
end

0 commit comments

Comments
 (0)