rails-html-sanitizer 1.3.0 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rails-html-sanitizer might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8eba1aac52c80be280f186c5d378150709b7d4cd2a5d5b2367e6d2c036648d52
4
- data.tar.gz: 96408eae2efee778a704f7caf246b64868a63bfdbbb81905b294bcca731a9289
3
+ metadata.gz: 2f00d9f256478eb753c8d211c3b25efa4204bbdbc9c5abf0415413c811a2e404
4
+ data.tar.gz: 65d3871aa798dfbbfb1138b666d475b590e347cdb66614d6d39b72ad3531c742
5
5
  SHA512:
6
- metadata.gz: c4209cebc841299143a466143f4b776461fc1cc8bba112dc603e86835b68ee44a800566f64224b27f5a45d164d0b004049b228dc405c3de59068800ec7a5d564
7
- data.tar.gz: c899472b8dffe9f9fd4d15ae4739f07a775d74b9ed14143beb688bb546b6a82ec469add036747b81aff33510e6e241379e21458cb39d9b2a8e797824066e24e5
6
+ metadata.gz: e6e31eaa72b1a2e8356aae50600ac784f85a80828cbc49ce8061384ecd3f21a1d8eaee69845dc08537c5102728c3cc41a72cb3ed8b9789c4921038398afa61e2
7
+ data.tar.gz: 6b14a49842eaf4c3e0fbae5acd28fdf32a5deb6cd42f769aada848226847180c4d3a67a9dcbc439e1a4855699b0ea694cb4c7b6ee173391ac841bd334ae44b6f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,46 @@
1
+ ## 1.4.3 / 2022-06-09
2
+
3
+ * Address a possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer.
4
+
5
+ Prevent the combination of `select` and `style` as allowed tags in SafeListSanitizer.
6
+
7
+ Fixes CVE-2022-32209
8
+
9
+ *Mike Dalessio*
10
+
11
+
12
+ ## 1.4.2 / 2021-08-23
13
+
14
+ * Slightly improve performance.
15
+
16
+ Assuming elements are more common than comments, make one less method call per node.
17
+
18
+ *Mike Dalessio*
19
+
20
+ ## 1.4.1 / 2021-08-18
21
+
22
+ * Fix regression in v1.4.0 that did not pass comment nodes to the scrubber.
23
+
24
+ Some scrubbers will want to override the default behavior and allow comments, but v1.4.0 only
25
+ passed through elements to the scrubber's `keep_node?` method.
26
+
27
+ This change once again allows the scrubber to make the decision on comment nodes, but still skips
28
+ other non-elements like processing instructions (see #115).
29
+
30
+ *Mike Dalessio*
31
+
32
+ ## 1.4.0 / 2021-08-18
33
+
34
+ * Processing Instructions are no longer allowed by Rails::Html::PermitScrubber
35
+
36
+ Previously, a PI with a name (or "target") matching an allowed tag name was not scrubbed. There
37
+ are no known security issues associated with these PIs, but similar to comments it's preferred to
38
+ omit these nodes when possible from sanitized output.
39
+
40
+ Fixes #115.
41
+
42
+ *Mike Dalessio*
43
+
1
44
  ## 1.3.0
2
45
 
3
46
  * Address deprecations in Loofah 2.3.0.
data/README.md CHANGED
@@ -81,8 +81,10 @@ html_fragment.to_s # => "<a></a>"
81
81
  #### `Rails::Html::TargetScrubber`
82
82
 
83
83
  Where `PermitScrubber` picks out tags and attributes to permit in sanitization,
84
- `Rails::Html::TargetScrubber` targets them for removal.
84
+ `Rails::Html::TargetScrubber` targets them for removal. See https://github.com/flavorjones/loofah/blob/main/lib/loofah/html5/safelist.rb for the tag list.
85
85
 
86
+ **Note:** by default, it will scrub anything that is not part of the permitted tags from
87
+ loofah `HTML5::Scrub.allowed_element?`.
86
88
 
87
89
  ```ruby
88
90
  scrubber = Rails::Html::TargetScrubber.new
@@ -1,7 +1,7 @@
1
1
  module Rails
2
2
  module Html
3
3
  class Sanitizer
4
- VERSION = "1.3.0"
4
+ VERSION = "1.4.3"
5
5
  end
6
6
  end
7
7
  end
@@ -141,8 +141,25 @@ module Rails
141
141
 
142
142
  private
143
143
 
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
+
144
157
  def allowed_tags(options)
145
- options[:tags] || self.class.allowed_tags
158
+ if options[:tags]
159
+ remove_safelist_tag_combinations(options[:tags])
160
+ else
161
+ self.class.allowed_tags
162
+ end
146
163
  end
147
164
 
148
165
  def allowed_attributes(options)
@@ -68,7 +68,7 @@ module Rails
68
68
  end
69
69
  return CONTINUE if skip_node?(node)
70
70
 
71
- unless keep_node?(node)
71
+ unless (node.element? || node.comment?) && keep_node?(node)
72
72
  return STOP if scrub_node(node) == STOP
73
73
  end
74
74
 
@@ -2,6 +2,8 @@ require "minitest/autorun"
2
2
  require "rails-html-sanitizer"
3
3
  require "rails/dom/testing/assertions/dom_assertions"
4
4
 
5
+ puts Nokogiri::VERSION_INFO
6
+
5
7
  class SanitizersTest < Minitest::Test
6
8
  include Rails::Dom::Testing::Assertions::DomAssertions
7
9
 
@@ -12,13 +14,11 @@ class SanitizersTest < Minitest::Test
12
14
  end
13
15
 
14
16
  def test_sanitize_nested_script
15
- sanitizer = Rails::Html::SafeListSanitizer.new
16
- assert_equal '&lt;script&gt;alert("XSS");&lt;/script&gt;', sanitizer.sanitize('<script><script></script>alert("XSS");<script><</script>/</script><script>script></script>', tags: %w(em))
17
+ assert_equal '&lt;script&gt;alert("XSS");&lt;/script&gt;', safe_list_sanitize('<script><script></script>alert("XSS");<script><</script>/</script><script>script></script>', tags: %w(em))
17
18
  end
18
19
 
19
20
  def test_sanitize_nested_script_in_style
20
- sanitizer = Rails::Html::SafeListSanitizer.new
21
- assert_equal '&lt;script&gt;alert("XSS");&lt;/script&gt;', sanitizer.sanitize('<style><script></style>alert("XSS");<style><</style>/</style><style>script></style>', tags: %w(em))
21
+ assert_equal '&lt;script&gt;alert("XSS");&lt;/script&gt;', safe_list_sanitize('<style><script></style>alert("XSS");<style><</style>/</style><style>script></style>', tags: %w(em))
22
22
  end
23
23
 
24
24
  class XpathRemovalTestSanitizer < Rails::Html::Sanitizer
@@ -54,7 +54,8 @@ class SanitizersTest < Minitest::Test
54
54
 
55
55
  def test_strip_tags_with_quote
56
56
  input = '<" <img src="trollface.gif" onload="alert(1)"> hi'
57
- assert_equal ' hi', full_sanitize(input)
57
+ expected = libxml_2_9_14_recovery? ? %{&lt;" hi} : %{ hi}
58
+ assert_equal(expected, full_sanitize(input))
58
59
  end
59
60
 
60
61
  def test_strip_invalid_html
@@ -75,15 +76,21 @@ class SanitizersTest < Minitest::Test
75
76
  end
76
77
 
77
78
  def test_remove_unclosed_tags
78
- assert_equal "This is ", full_sanitize("This is <-- not\n a comment here.")
79
+ input = "This is <-- not\n a comment here."
80
+ expected = libxml_2_9_14_recovery? ? %{This is &lt;-- not\n a comment here.} : %{This is }
81
+ assert_equal(expected, full_sanitize(input))
79
82
  end
80
83
 
81
84
  def test_strip_cdata
82
- assert_equal "This has a ]]&gt; here.", full_sanitize("This has a <![CDATA[<section>]]> here.")
85
+ input = "This has a <![CDATA[<section>]]> here."
86
+ expected = libxml_2_9_14_recovery? ? %{This has a &lt;![CDATA[]]&gt; here.} : %{This has a ]]&gt; here.}
87
+ assert_equal(expected, full_sanitize(input))
83
88
  end
84
89
 
85
90
  def test_strip_unclosed_cdata
86
- assert_equal "This has an unclosed ]] here...", full_sanitize("This has an unclosed <![CDATA[<section>]] here...")
91
+ input = "This has an unclosed <![CDATA[<section>]] here..."
92
+ expected = libxml_2_9_14_recovery? ? %{This has an unclosed &lt;![CDATA[]] here...} : %{This has an unclosed ]] here...}
93
+ assert_equal(expected, full_sanitize(input))
87
94
  end
88
95
 
89
96
  def test_strip_blank_string
@@ -93,7 +100,7 @@ class SanitizersTest < Minitest::Test
93
100
  end
94
101
 
95
102
  def test_strip_tags_with_plaintext
96
- assert_equal "Dont touch me", full_sanitize("Dont touch me")
103
+ assert_equal "Don't touch me", full_sanitize("Don't touch me")
97
104
  end
98
105
 
99
106
  def test_strip_tags_with_tags
@@ -135,7 +142,7 @@ class SanitizersTest < Minitest::Test
135
142
  end
136
143
 
137
144
  def test_strip_links_with_plaintext
138
- assert_equal "Dont touch me", link_sanitize("Dont touch me")
145
+ assert_equal "Don't touch me", link_sanitize("Don't touch me")
139
146
  end
140
147
 
141
148
  def test_strip_links_with_line_feed_and_uppercase_tag
@@ -271,7 +278,8 @@ class SanitizersTest < Minitest::Test
271
278
 
272
279
  def test_scrub_style_if_style_attribute_option_is_passed
273
280
  input = '<p style="color: #000; background-image: url(http://www.ragingplatypus.com/i/cam-full.jpg);"></p>'
274
- assert_equal '<p style="color: #000;"></p>', safe_list_sanitize(input, attributes: %w(style))
281
+ actual = safe_list_sanitize(input, attributes: %w(style))
282
+ assert_includes(['<p style="color: #000;"></p>', '<p style="color:#000;"></p>'], actual)
275
283
  end
276
284
 
277
285
  def test_should_raise_argument_error_if_tags_is_not_enumerable
@@ -413,8 +421,25 @@ class SanitizersTest < Minitest::Test
413
421
  end
414
422
 
415
423
  def test_should_sanitize_div_background_image_unicode_encoded
416
- raw = %(background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029)
417
- assert_equal '', sanitize_css(raw)
424
+ [
425
+ convert_to_css_hex("url(javascript:alert(1))", false),
426
+ convert_to_css_hex("url(javascript:alert(1))", true),
427
+ convert_to_css_hex("url(https://example.com)", false),
428
+ convert_to_css_hex("url(https://example.com)", true),
429
+ ].each do |propval|
430
+ raw = "background-image:" + propval
431
+ assert_empty(sanitize_css(raw))
432
+ end
433
+ end
434
+
435
+ def test_should_allow_div_background_image_unicode_encoded_safe_functions
436
+ [
437
+ convert_to_css_hex("rgb(255,0,0)", false),
438
+ convert_to_css_hex("rgb(255,0,0)", true),
439
+ ].each do |propval|
440
+ raw = "background-image:" + propval
441
+ assert_includes(sanitize_css(raw), "background-image")
442
+ end
418
443
  end
419
444
 
420
445
  def test_should_sanitize_div_style_expression
@@ -432,11 +457,15 @@ class SanitizersTest < Minitest::Test
432
457
  end
433
458
 
434
459
  def test_should_sanitize_cdata_section
435
- assert_sanitized "<![CDATA[<span>section</span>]]>", "section]]&gt;"
460
+ input = "<![CDATA[<span>section</span>]]>"
461
+ expected = libxml_2_9_14_recovery? ? %{&lt;![CDATA[<span>section</span>]]&gt;} : %{section]]&gt;}
462
+ assert_sanitized(input, expected)
436
463
  end
437
464
 
438
465
  def test_should_sanitize_unterminated_cdata_section
439
- assert_sanitized "<![CDATA[<span>neverending...", "neverending..."
466
+ input = "<![CDATA[<span>neverending..."
467
+ expected = libxml_2_9_14_recovery? ? %{&lt;![CDATA[<span>neverending...</span>} : %{neverending...}
468
+ assert_sanitized(input, expected)
440
469
  end
441
470
 
442
471
  def test_should_not_mangle_urls_with_ampersand
@@ -487,7 +516,13 @@ class SanitizersTest < Minitest::Test
487
516
 
488
517
  text = safe_list_sanitize(html)
489
518
 
490
- assert_equal %{<a href=\"examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com\">test</a>}, text
519
+ acceptable_results = [
520
+ # nokogiri w/vendored+patched libxml2
521
+ %{<a href="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>},
522
+ # nokogiri w/ system libxml2
523
+ %{<a href="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>},
524
+ ]
525
+ assert_includes(acceptable_results, text)
491
526
  end
492
527
 
493
528
  def test_uri_escaping_of_src_attr_in_a_tag_in_safe_list_sanitizer
@@ -497,7 +532,13 @@ class SanitizersTest < Minitest::Test
497
532
 
498
533
  text = safe_list_sanitize(html)
499
534
 
500
- assert_equal %{<a src=\"examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com\">test</a>}, text
535
+ acceptable_results = [
536
+ # nokogiri w/vendored+patched libxml2
537
+ %{<a src="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>},
538
+ # nokogiri w/system libxml2
539
+ %{<a src="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>},
540
+ ]
541
+ assert_includes(acceptable_results, text)
501
542
  end
502
543
 
503
544
  def test_uri_escaping_of_name_attr_in_a_tag_in_safe_list_sanitizer
@@ -507,7 +548,13 @@ class SanitizersTest < Minitest::Test
507
548
 
508
549
  text = safe_list_sanitize(html)
509
550
 
510
- assert_equal %{<a name=\"examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com\">test</a>}, text
551
+ acceptable_results = [
552
+ # nokogiri w/vendored+patched libxml2
553
+ %{<a name="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>},
554
+ # nokogiri w/system libxml2
555
+ %{<a name="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>},
556
+ ]
557
+ assert_includes(acceptable_results, text)
511
558
  end
512
559
 
513
560
  def test_uri_escaping_of_name_action_in_a_tag_in_safe_list_sanitizer
@@ -517,7 +564,40 @@ class SanitizersTest < Minitest::Test
517
564
 
518
565
  text = safe_list_sanitize(html, attributes: ['action'])
519
566
 
520
- assert_equal %{<a action=\"examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com\">test</a>}, text
567
+ acceptable_results = [
568
+ # nokogiri w/vendored+patched libxml2
569
+ %{<a action="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>},
570
+ # nokogiri w/system libxml2
571
+ %{<a action="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>},
572
+ ]
573
+ assert_includes(acceptable_results, text)
574
+ end
575
+
576
+ def test_exclude_node_type_processing_instructions
577
+ assert_equal("<div>text</div><b>text</b>", safe_list_sanitize("<div>text</div><?div content><b>text</b>"))
578
+ end
579
+
580
+ def test_exclude_node_type_comment
581
+ assert_equal("<div>text</div><b>text</b>", safe_list_sanitize("<div>text</div><!-- comment --><b>text</b>"))
582
+ end
583
+
584
+ def test_disallow_the_dangerous_safelist_combination_of_select_and_style
585
+ input = "<select><style><script>alert(1)</script></style></select>"
586
+ tags = ["select", "style"]
587
+ warning = /WARNING: Rails::Html::SafeListSanitizer: removing 'style' from safelist/
588
+ sanitized = nil
589
+ invocation = Proc.new { sanitized = safe_list_sanitize(input, tags: tags) }
590
+
591
+ if html5_mode?
592
+ # if Loofah is using an HTML5 parser,
593
+ # then "style" should be removed by the parser as an invalid child of "select"
594
+ assert_silent(&invocation)
595
+ else
596
+ # if Loofah is using an HTML4 parser,
597
+ # then SafeListSanitizer should remove "style" from the safelist
598
+ assert_output(nil, warning, &invocation)
599
+ end
600
+ refute_includes(sanitized, "style")
521
601
  end
522
602
 
523
603
  protected
@@ -565,4 +645,23 @@ protected
565
645
  ensure
566
646
  Rails::Html::SafeListSanitizer.allowed_attributes = old_attributes
567
647
  end
648
+
649
+ # note that this is used for testing CSS hex encoding: \\[0-9a-f]{1,6}
650
+ def convert_to_css_hex(string, escape_parens=false)
651
+ string.chars.map do |c|
652
+ if !escape_parens && (c == "(" || c == ")")
653
+ c
654
+ else
655
+ format('\00%02X', c.ord)
656
+ end
657
+ end.join
658
+ end
659
+
660
+ def libxml_2_9_14_recovery?
661
+ Nokogiri.method(:uses_libxml?).arity == -1 && Nokogiri.uses_libxml?(">= 2.9.14")
662
+ end
663
+
664
+ def html5_mode?
665
+ ::Loofah.respond_to?(:html5_mode?) && ::Loofah.html5_mode?
666
+ end
568
667
  end
@@ -41,6 +41,16 @@ class PermitScrubberTest < ScrubberTest
41
41
  assert_scrubbed '<tag>hello</tag>', 'hello'
42
42
  end
43
43
 
44
+ def test_default_scrub_removes_comments
45
+ assert_scrubbed('<div>one</div><!-- two --><span>three</span>',
46
+ '<div>one</div><span>three</span>')
47
+ end
48
+
49
+ def test_default_scrub_removes_processing_instructions
50
+ assert_scrubbed('<div>one</div><?div two><span>three</span>',
51
+ '<div>one</div><span>three</span>')
52
+ end
53
+
44
54
  def test_default_attributes_removal_behavior
45
55
  assert_scrubbed '<p cooler="hello">hello</p>', '<p>hello</p>'
46
56
  end
@@ -56,6 +66,12 @@ class PermitScrubberTest < ScrubberTest
56
66
  assert_scrubbed html, '<tag>leave me now</tag>'
57
67
  end
58
68
 
69
+ def test_leaves_comments_when_supplied_as_tag
70
+ @scrubber.tags = %w(div comment)
71
+ assert_scrubbed('<div>one</div><!-- two --><span>three</span>',
72
+ '<div>one</div><!-- two -->three')
73
+ end
74
+
59
75
  def test_leaves_only_supplied_tags_nested
60
76
  html = '<tag>leave <em>me <span>now</span></em></tag>'
61
77
  @scrubber.tags = %w(tag)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-html-sanitizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafael Mendonça França
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-06 00:00:00.000000000 Z
12
+ date: 2022-06-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: loofah
@@ -101,7 +101,11 @@ files:
101
101
  homepage: https://github.com/rails/rails-html-sanitizer
102
102
  licenses:
103
103
  - MIT
104
- metadata: {}
104
+ metadata:
105
+ bug_tracker_uri: https://github.com/rails/rails-html-sanitizer/issues
106
+ changelog_uri: https://github.com/rails/rails-html-sanitizer/blob/v1.4.3/CHANGELOG.md
107
+ documentation_uri: https://www.rubydoc.info/gems/rails-html-sanitizer/1.4.3
108
+ source_code_uri: https://github.com/rails/rails-html-sanitizer/tree/v1.4.3
105
109
  post_install_message:
106
110
  rdoc_options: []
107
111
  require_paths:
@@ -117,10 +121,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
121
  - !ruby/object:Gem::Version
118
122
  version: '0'
119
123
  requirements: []
120
- rubygems_version: 3.0.3
124
+ rubygems_version: 3.3.5
121
125
  signing_key:
122
126
  specification_version: 4
123
127
  summary: This gem is responsible to sanitize HTML fragments in Rails applications.
124
128
  test_files:
125
- - test/scrubbers_test.rb
126
129
  - test/sanitizer_test.rb
130
+ - test/scrubbers_test.rb