rails-html-sanitizer 1.6.0.rc2 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ce8562c96b3e842ebf50227e682c3fa948ebf8474786f100dbf78adff7f98d0
4
- data.tar.gz: 7ca7beb76be35dea0dd926819212445e885a2b205ca0b2e45628f58d734a1a9f
3
+ metadata.gz: ad86488f25943fb1d0050513a08581231534a8552e4de66329270845ff650b12
4
+ data.tar.gz: ca70b518cc54e0ec2e224a5c3dbd82d567e39fd2d2ce3538e308f4d8846234b4
5
5
  SHA512:
6
- metadata.gz: 30d9b9288698da75f713811e8b507edda0645eb4a485b0466847a4b8246aa854cd12e4dae238e61b09e371c950ee8516595b39207d4b10323bf81cf74b0a5114
7
- data.tar.gz: 81698f017c423bac3434e7129b70c4ecba80b27a4f8c6d86294d548aeeb9238d98cd65c15f06bcf62c6ee104e8b3beca782c8e9b338302590c33b65ab9ed8121
6
+ metadata.gz: f6e2db01e0cd52d3bdcae7ceb90a081998111e84f19f4908d73a9b229a0ec87edfc10f05772b057d2c3e6c9cd08df267f82070f16015d9953d3edc85002dcafd
7
+ data.tar.gz: 746475ff0522b512e28f7bfd83a7e54be7445a29abcd9b1c8aa16c7b6c39b6f97f5e553ac228b3bad98af8b5f828c4fb6c311f75ddd4b286045ba9a353f0fd8a
data/CHANGELOG.md CHANGED
@@ -1,4 +1,55 @@
1
- ## 1.6.0.rc2 / 2023-05-24
1
+ ## 1.6.1 / 2024-12-02
2
+
3
+ This is a performance and security release which addresses several possible XSS vulnerabilities.
4
+
5
+ * The dependency on Nokogiri is updated to v1.15.7 or >=1.16.8.
6
+
7
+ This change addresses CVE-2024-53985 (GHSA-w8gc-x259-rc7x).
8
+
9
+ *Mike Dalessio*
10
+
11
+ * Disallowed tags will be pruned when they appear in foreign content (i.e. SVG or MathML content),
12
+ regardless of the `prune:` option value. Previously, disallowed tags were "stripped" unless the
13
+ gem was configured with the `prune: true` option.
14
+
15
+ The CVEs addressed by this change are:
16
+
17
+ - CVE-2024-53986 (GHSA-638j-pmjw-jq48)
18
+ - CVE-2024-53987 (GHSA-2x5m-9ch4-qgrr)
19
+
20
+ *Mike Dalessio*
21
+
22
+ * The tags "noscript", "mglyph", and "malignmark" will not be allowed, even if explicitly added to
23
+ the allowlist. If applications try to allow any of these tags, a warning is emitted and the tags
24
+ are removed from the allow-list.
25
+
26
+ The CVEs addressed by this change are:
27
+
28
+ - CVE-2024-53988 (GHSA-cfjx-w229-hgx5)
29
+ - CVE-2024-53989 (GHSA-rxv5-gxqc-xx8g)
30
+
31
+ Please note that we _may_ restore support for allowing "noscript" in a future release. We do not
32
+ expect to ever allow "mglyph" or "malignmark", though, especially since browser support is minimal
33
+ for these tags.
34
+
35
+ *Mike Dalessio*
36
+
37
+ * Improve performance by eliminating needless operations on attributes that are being removed. #188
38
+
39
+ *Mike Dalessio*
40
+
41
+
42
+ ## 1.6.0 / 2023-05-26
43
+
44
+ * Dependencies have been updated:
45
+
46
+ - Loofah `~>2.21` and Nokogiri `~>1.14` for HTML5 parser support
47
+ - As a result, required Ruby version is now `>= 2.7.0`
48
+
49
+ Security updates will continue to be made on the `1.5.x` release branch as long as Rails 6.1
50
+ (which supports Ruby 2.5) is still in security support.
51
+
52
+ *Mike Dalessio*
2
53
 
3
54
  * HTML5 standards-compliant sanitizers are now available on platforms supported by
4
55
  Nokogiri::HTML5. These are available as:
data/README.md CHANGED
@@ -7,51 +7,6 @@ Rails HTML Sanitizer is only intended to be used with Rails applications. If you
7
7
 
8
8
  ## Usage
9
9
 
10
- ### A note on HTML entities
11
-
12
- __Rails HTML sanitizers are intended to be used by the view layer, at page-render time. They are *not* intended to sanitize persisted strings that will be sanitized *again* at page-render time.__
13
-
14
- Proper HTML sanitization will replace some characters with HTML entities. For example, text containing a `<` character will be updated to contain `&lt;` to ensure that the markup is well-formed.
15
-
16
- This is important to keep in mind because __HTML entities will render improperly if they are sanitized twice.__
17
-
18
-
19
- #### A concrete example showing the problem that can arise
20
-
21
- Imagine the user is asked to enter their employer's name, which will appear on their public profile page. Then imagine they enter `JPMorgan Chase & Co.`.
22
-
23
- If you sanitize this before persisting it in the database, the stored string will be `JPMorgan Chase &amp; Co.`
24
-
25
- When the page is rendered, if this string is sanitized a second time by the view layer, the HTML will contain `JPMorgan Chase &amp;amp; Co.` which will render as "JPMorgan Chase &amp;amp; Co.".
26
-
27
- Another problem that can arise is rendering the sanitized string in a non-HTML context (for example, if it ends up being part of an SMS message). In this case, it may contain inappropriate HTML entities.
28
-
29
-
30
- #### Suggested alternatives
31
-
32
- You might simply choose to persist the untrusted string as-is (the raw input), and then ensure that the string will be properly sanitized by the view layer.
33
-
34
- That raw string, if rendered in an non-HTML context (like SMS), must also be sanitized by a method appropriate for that context. You may wish to look into using [Loofah](https://github.com/flavorjones/loofah) or [Sanitize](https://github.com/rgrove/sanitize) to customize how this sanitization works, including omitting HTML entities in the final string.
35
-
36
- If you really want to sanitize the string that's stored in your database, you may wish to look into [Loofah::ActiveRecord](https://github.com/flavorjones/loofah-activerecord) rather than use the Rails HTML sanitizers.
37
-
38
-
39
- ### A note on module names
40
-
41
- In versions < 1.6, the only module defined by this library was `Rails::Html`. Starting in 1.6, we define three additional modules:
42
-
43
- - `Rails::HTML` for general functionality (replacing `Rails::Html`)
44
- - `Rails::HTML4` containing sanitizers that parse content as HTML4
45
- - `Rails::HTML5` containing sanitizers that parse content as HTML5 (if supported)
46
-
47
- The following aliases are maintained for backwards compatibility:
48
-
49
- - `Rails::Html` points to `Rails::HTML`
50
- - `Rails::HTML::FullSanitizer` points to `Rails::HTML4::FullSanitizer`
51
- - `Rails::HTML::LinkSanitizer` points to `Rails::HTML4::LinkSanitizer`
52
- - `Rails::HTML::SafeListSanitizer` points to `Rails::HTML4::SafeListSanitizer`
53
-
54
-
55
10
  ### Sanitizers
56
11
 
57
12
  All sanitizers respond to `sanitize`, and are available in variants that use either HTML4 or HTML5 parsing, under the `Rails::HTML4` and `Rails::HTML5` namespaces, respectively.
@@ -75,10 +30,6 @@ full_sanitizer.sanitize("<b>Bold</b> no more! <a href='more.html'>See more here
75
30
  # => Bold no more! See more here...
76
31
  ```
77
32
 
78
- HTML5 version:
79
-
80
-
81
-
82
33
  #### LinkSanitizer
83
34
 
84
35
  ```ruby
@@ -219,6 +170,51 @@ Using the `CommentScrubber` from above, you can use this in a Rails view like so
219
170
  <%= sanitize @comment, scrubber: CommentScrubber.new %>
220
171
  ```
221
172
 
173
+ ### A note on HTML entities
174
+
175
+ __Rails HTML sanitizers are intended to be used by the view layer, at page-render time. They are *not* intended to sanitize persisted strings that will be sanitized *again* at page-render time.__
176
+
177
+ Proper HTML sanitization will replace some characters with HTML entities. For example, text containing a `<` character will be updated to contain `&lt;` to ensure that the markup is well-formed.
178
+
179
+ This is important to keep in mind because __HTML entities will render improperly if they are sanitized twice.__
180
+
181
+
182
+ #### A concrete example showing the problem that can arise
183
+
184
+ Imagine the user is asked to enter their employer's name, which will appear on their public profile page. Then imagine they enter `JPMorgan Chase & Co.`.
185
+
186
+ If you sanitize this before persisting it in the database, the stored string will be `JPMorgan Chase &amp; Co.`
187
+
188
+ When the page is rendered, if this string is sanitized a second time by the view layer, the HTML will contain `JPMorgan Chase &amp;amp; Co.` which will render as "JPMorgan Chase &amp;amp; Co.".
189
+
190
+ Another problem that can arise is rendering the sanitized string in a non-HTML context (for example, if it ends up being part of an SMS message). In this case, it may contain inappropriate HTML entities.
191
+
192
+
193
+ #### Suggested alternatives
194
+
195
+ You might simply choose to persist the untrusted string as-is (the raw input), and then ensure that the string will be properly sanitized by the view layer.
196
+
197
+ That raw string, if rendered in an non-HTML context (like SMS), must also be sanitized by a method appropriate for that context. You may wish to look into using [Loofah](https://github.com/flavorjones/loofah) or [Sanitize](https://github.com/rgrove/sanitize) to customize how this sanitization works, including omitting HTML entities in the final string.
198
+
199
+ If you really want to sanitize the string that's stored in your database, you may wish to look into [Loofah::ActiveRecord](https://github.com/flavorjones/loofah-activerecord) rather than use the Rails HTML sanitizers.
200
+
201
+
202
+ ### A note on module names
203
+
204
+ In versions < 1.6, the only module defined by this library was `Rails::Html`. Starting in 1.6, we define three additional modules:
205
+
206
+ - `Rails::HTML` for general functionality (replacing `Rails::Html`)
207
+ - `Rails::HTML4` containing sanitizers that parse content as HTML4
208
+ - `Rails::HTML5` containing sanitizers that parse content as HTML5 (if supported)
209
+
210
+ The following aliases are maintained for backwards compatibility:
211
+
212
+ - `Rails::Html` points to `Rails::HTML`
213
+ - `Rails::HTML::FullSanitizer` points to `Rails::HTML4::FullSanitizer`
214
+ - `Rails::HTML::LinkSanitizer` points to `Rails::HTML4::LinkSanitizer`
215
+ - `Rails::HTML::SafeListSanitizer` points to `Rails::HTML4::SafeListSanitizer`
216
+
217
+
222
218
  ## Installation
223
219
 
224
220
  Add this line to your application's Gemfile:
@@ -234,6 +230,15 @@ Or install it yourself as:
234
230
  $ gem install rails-html-sanitizer
235
231
 
236
232
 
233
+ ## Support matrix
234
+
235
+ | branch | ruby support | actively maintained | security support |
236
+ |--------|--------------|---------------------|----------------------------------------|
237
+ | 1.6.x | >= 2.7 | yes | yes |
238
+ | 1.5.x | >= 2.5 | no | while Rails 6.1 is in security support |
239
+ | 1.4.x | >= 1.8.7 | no | no |
240
+
241
+
237
242
  ## Read more
238
243
 
239
244
  Loofah is what underlies the sanitizers and scrubbers of rails-html-sanitizer.
@@ -3,7 +3,7 @@
3
3
  module Rails
4
4
  module HTML
5
5
  class Sanitizer
6
- VERSION = "1.6.0.rc2"
6
+ VERSION = "1.6.1"
7
7
  end
8
8
  end
9
9
  end
@@ -106,6 +106,7 @@ module Rails
106
106
  "ins",
107
107
  "kbd",
108
108
  "li",
109
+ "mark",
109
110
  "ol",
110
111
  "p",
111
112
  "pre",
@@ -72,10 +72,11 @@ module Rails
72
72
  return CONTINUE if skip_node?(node)
73
73
 
74
74
  unless (node.element? || node.comment?) && keep_node?(node)
75
- return STOP if scrub_node(node) == STOP
75
+ return STOP unless scrub_node(node) == CONTINUE
76
76
  end
77
77
 
78
78
  scrub_attributes(node)
79
+ CONTINUE
79
80
  end
80
81
 
81
82
  protected
@@ -100,15 +101,22 @@ module Rails
100
101
  end
101
102
 
102
103
  def scrub_node(node)
103
- node.before(node.children) unless prune # strip
104
+ # If a node has a namespace, then it's a tag in either a `math` or `svg` foreign context,
105
+ # and we should always prune it to avoid namespace confusion and mutation XSS vectors.
106
+ unless prune || node.namespace
107
+ node.before(node.children)
108
+ end
104
109
  node.remove
105
110
  end
106
111
 
107
112
  def scrub_attributes(node)
108
113
  if @attributes
109
114
  node.attribute_nodes.each do |attr|
110
- attr.remove if scrub_attribute?(attr.name)
111
- scrub_attribute(node, attr)
115
+ if scrub_attribute?(attr.name)
116
+ attr.remove
117
+ else
118
+ scrub_attribute(node, attr)
119
+ end
112
120
  end
113
121
 
114
122
  scrub_css_attribute(node)
@@ -130,6 +138,24 @@ module Rails
130
138
  if var && !var.is_a?(Enumerable)
131
139
  raise ArgumentError, "You should pass :#{name} as an Enumerable"
132
140
  end
141
+
142
+ if var && name == :tags
143
+ if var.include?("mglyph")
144
+ warn("WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber and will be scrubbed")
145
+ var.delete("mglyph")
146
+ end
147
+
148
+ if var.include?("malignmark")
149
+ warn("WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber and will be scrubbed")
150
+ var.delete("malignmark")
151
+ end
152
+
153
+ if var.include?("noscript")
154
+ warn("WARNING: 'noscript' tags cannot be allowed by the PermitScrubber and will be scrubbed")
155
+ var.delete("noscript")
156
+ end
157
+ end
158
+
133
159
  var
134
160
  end
135
161
 
@@ -140,9 +166,7 @@ module Rails
140
166
  attr_node.node_name
141
167
  end
142
168
 
143
- if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name)
144
- return if Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
145
- end
169
+ return if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name) && Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
146
170
 
147
171
  if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
148
172
  Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node)
@@ -728,8 +728,6 @@ module SanitizerTests
728
728
  end
729
729
 
730
730
  def test_uri_escaping_of_href_attr_in_a_tag_in_safe_list_sanitizer
731
- skip if RUBY_VERSION < "2.3"
732
-
733
731
  html = %{<a href='examp<!--" unsafeattr=foo()>-->le.com'>test</a>}
734
732
 
735
733
  text = safe_list_sanitize(html)
@@ -747,8 +745,6 @@ module SanitizerTests
747
745
  end
748
746
 
749
747
  def test_uri_escaping_of_src_attr_in_a_tag_in_safe_list_sanitizer
750
- skip if RUBY_VERSION < "2.3"
751
-
752
748
  html = %{<a src='examp<!--" unsafeattr=foo()>-->le.com'>test</a>}
753
749
 
754
750
  text = safe_list_sanitize(html)
@@ -766,8 +762,6 @@ module SanitizerTests
766
762
  end
767
763
 
768
764
  def test_uri_escaping_of_name_attr_in_a_tag_in_safe_list_sanitizer
769
- skip if RUBY_VERSION < "2.3"
770
-
771
765
  html = %{<a name='examp<!--" unsafeattr=foo()>-->le.com'>test</a>}
772
766
 
773
767
  text = safe_list_sanitize(html)
@@ -785,8 +779,6 @@ module SanitizerTests
785
779
  end
786
780
 
787
781
  def test_uri_escaping_of_name_action_in_a_tag_in_safe_list_sanitizer
788
- skip if RUBY_VERSION < "2.3"
789
-
790
782
  html = %{<a action='examp<!--" unsafeattr=foo()>-->le.com'>test</a>}
791
783
 
792
784
  text = safe_list_sanitize(html, attributes: ["action"])
@@ -926,7 +918,7 @@ module SanitizerTests
926
918
  # libxml2
927
919
  "<svg><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></svg>",
928
920
  # libgumbo
929
- "<svg><style>alert(1)</style></svg>"
921
+ "<svg><style></style></svg>",
930
922
  ]
931
923
 
932
924
  assert_includes(acceptable_results, actual)
@@ -984,6 +976,76 @@ module SanitizerTests
984
976
  assert_includes(acceptable_results, actual)
985
977
  end
986
978
 
979
+ def test_combination_of_svg_and_style_with_escaped_img_payload
980
+ # https://hackerone.com/reports/2503220
981
+ input, tags = "<svg><style>&lt;img src onerror=alert(1)>", ["svg", "style"]
982
+ actual = safe_list_sanitize(input, tags: tags)
983
+ acceptable_results = [
984
+ # libxml2
985
+ "<svg><style>&amp;lt;img src onerror=alert(1)&gt;</style></svg>",
986
+ # libgumbo
987
+ "<svg><style>&lt;img src onerror=alert(1)&gt;</style></svg>",
988
+ ]
989
+
990
+ assert_includes(acceptable_results, actual)
991
+ end
992
+
993
+ def test_combination_of_math_and_style_with_escaped_img_payload
994
+ # https://hackerone.com/reports/2503220
995
+ input, tags = "<math><style>&lt;img src onerror=alert(1)>", ["math", "style"]
996
+ actual = safe_list_sanitize(input, tags: tags)
997
+ acceptable_results = [
998
+ # libxml2
999
+ "<math><style>&amp;lt;img src onerror=alert(1)&gt;</style></math>",
1000
+ # libgumbo
1001
+ "<math><style>&lt;img src onerror=alert(1)&gt;</style></math>",
1002
+ ]
1003
+
1004
+ assert_includes(acceptable_results, actual)
1005
+ end
1006
+
1007
+ def test_combination_of_style_and_disallowed_svg_with_script_payload
1008
+ # https://hackerone.com/reports/2519936
1009
+ input, tags = "<svg><style><style class='</style><script>alert(1)</script>'>", ["style"]
1010
+ actual = safe_list_sanitize(input, tags: tags)
1011
+ acceptable_results = [
1012
+ # libxml2
1013
+ "<style>&lt;style class='</style>alert(1)'&gt;",
1014
+ # libgumbo
1015
+ "",
1016
+ ]
1017
+
1018
+ assert_includes(acceptable_results, actual)
1019
+ end
1020
+
1021
+ def test_combination_of_style_and_disallowed_math_with_script_payload
1022
+ # https://hackerone.com/reports/2519936
1023
+ input, tags = "<math><style><style class='</style><script>alert(1)</script>'>", ["style"]
1024
+ actual = safe_list_sanitize(input, tags: tags)
1025
+ acceptable_results = [
1026
+ # libxml2
1027
+ "<style>&lt;style class='</style>alert(1)'&gt;",
1028
+ # libgumbo
1029
+ "",
1030
+ ]
1031
+
1032
+ assert_includes(acceptable_results, actual)
1033
+ end
1034
+
1035
+ def test_math_with_disallowed_mtext_and_img_payload
1036
+ # https://hackerone.com/reports/2519941
1037
+ input, tags = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>", ["math", "style"]
1038
+ actual = safe_list_sanitize(input, tags: tags)
1039
+ acceptable_results = [
1040
+ # libxml2
1041
+ "<math><style>&lt;img src=: onerror=alert(1)&gt;</style></math>",
1042
+ # libgumbo
1043
+ "<math></math>",
1044
+ ]
1045
+
1046
+ assert_includes(acceptable_results, actual)
1047
+ end
1048
+
987
1049
  def test_should_sanitize_illegal_style_properties
988
1050
  raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
989
1051
  expected = %(display:block;width:100%;height:100%;background-color:black;background-x:center;background-y:center;)
@@ -1034,6 +1096,64 @@ module SanitizerTests
1034
1096
  assert_equal "", sanitize_css(raw)
1035
1097
  end
1036
1098
 
1099
+ def test_should_prune_mglyph
1100
+ # https://hackerone.com/reports/2519936
1101
+ input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>"
1102
+ tags = %w(math mtext table mglyph style)
1103
+
1104
+ actual = nil
1105
+ assert_output(nil, /WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber/) do
1106
+ actual = safe_list_sanitize(input, tags: tags)
1107
+ end
1108
+
1109
+ acceptable_results = [
1110
+ # libxml2
1111
+ "<math><mtext><table><style>&lt;img src=: onerror=alert(1)&gt;</style></table></mtext></math>",
1112
+ # libgumbo
1113
+ "<math><mtext><style><img src=: onerror=alert(1)></style><table></table></mtext></math>",
1114
+ ]
1115
+
1116
+ assert_includes(acceptable_results, actual)
1117
+ end
1118
+
1119
+ def test_should_prune_malignmark
1120
+ # https://hackerone.com/reports/2519936
1121
+ input = "<math><mtext><table><malignmark><style><img src=: onerror=alert(1)>"
1122
+ tags = %w(math mtext table malignmark style)
1123
+
1124
+ actual = nil
1125
+ assert_output(nil, /WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber/) do
1126
+ actual = safe_list_sanitize(input, tags: tags)
1127
+ end
1128
+
1129
+ acceptable_results = [
1130
+ # libxml2
1131
+ "<math><mtext><table><style>&lt;img src=: onerror=alert(1)&gt;</style></table></mtext></math>",
1132
+ # libgumbo
1133
+ "<math><mtext><style><img src=: onerror=alert(1)></style><table></table></mtext></math>",
1134
+ ]
1135
+
1136
+ assert_includes(acceptable_results, actual)
1137
+ end
1138
+
1139
+ def test_should_prune_noscript
1140
+ # https://hackerone.com/reports/2509647
1141
+ input, tags = "<div><noscript><p id='</noscript><script>alert(1)</script>'></noscript>", ["p", "div", "noscript"]
1142
+ actual = nil
1143
+ assert_output(nil, /WARNING: 'noscript' tags cannot be allowed by the PermitScrubber/) do
1144
+ actual = safe_list_sanitize(input, tags: tags, attributes: %w(id))
1145
+ end
1146
+
1147
+ acceptable_results = [
1148
+ # libxml2
1149
+ "<div><p id=\"&lt;/noscript&gt;&lt;script&gt;alert(1)&lt;/script&gt;\"></p></div>",
1150
+ # libgumbo
1151
+ "<div><p id=\"</noscript><script>alert(1)</script>\"></p></div>",
1152
+ ]
1153
+
1154
+ assert_includes(acceptable_results, actual)
1155
+ end
1156
+
1037
1157
  protected
1038
1158
  def safe_list_sanitize(input, options = {})
1039
1159
  module_under_test::SafeListSanitizer.new.sanitize(input, options)
@@ -1083,5 +1203,84 @@ module SanitizerTests
1083
1203
  class HTML5SafeListSanitizerTest < Minitest::Test
1084
1204
  @module_under_test = Rails::HTML5
1085
1205
  include SafeListSanitizerTest
1206
+
1207
+ def test_should_not_be_vulnerable_to_nokogiri_foreign_style_serialization_bug
1208
+ # https://hackerone.com/reports/2503220
1209
+ input = "<svg><style>&lt;img src onerror=alert(1)>"
1210
+ result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: ["svg", "style"])
1211
+ browser = Nokogiri::HTML5::Document.parse(result)
1212
+ xss = browser.at_xpath("//img/@onerror")
1213
+
1214
+ assert_nil(xss)
1215
+ end
1216
+
1217
+ def test_should_not_be_vulnerable_to_ns_confusion_2519936
1218
+ # https://hackerone.com/reports/2519936
1219
+ input = "<math><style><style class='</style><script>alert(1)</script>'>"
1220
+ result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: ["style"])
1221
+ browser = Nokogiri::HTML5::Document.parse(result)
1222
+ xss = browser.at_xpath("//script")
1223
+
1224
+ assert_nil(xss)
1225
+ end
1226
+
1227
+ def test_should_not_be_vulnerable_to_ns_confusion_2519941
1228
+ # https://hackerone.com/reports/2519941
1229
+ input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>"
1230
+ result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: %w(math style))
1231
+ browser = Nokogiri::HTML5::Document.parse(result)
1232
+ xss = browser.at_xpath("//img/@onerror")
1233
+
1234
+ assert_nil(xss)
1235
+ end
1236
+
1237
+ def test_should_not_be_vulnerable_to_mglyph_namespace_confusion
1238
+ # https://hackerone.com/reports/2519936
1239
+ input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>"
1240
+ tags = %w(math mtext table mglyph style)
1241
+
1242
+ result = nil
1243
+ assert_output(nil, /WARNING/) do
1244
+ result = safe_list_sanitize(input, tags: tags)
1245
+ end
1246
+
1247
+ browser = Nokogiri::HTML5::Document.parse(result)
1248
+ xss = browser.at_xpath("//img/@onerror")
1249
+
1250
+ assert_nil(xss)
1251
+ end
1252
+
1253
+ def test_should_not_be_vulnerable_to_malignmark_namespace_confusion
1254
+ # https://hackerone.com/reports/2519936
1255
+ input = "<math><mtext><table><malignmark><style><img src=: onerror=alert(1)>"
1256
+ tags = %w(math mtext table malignmark style)
1257
+
1258
+ result = nil
1259
+ assert_output(nil, /WARNING/) do
1260
+ result = safe_list_sanitize(input, tags: tags)
1261
+ end
1262
+
1263
+ browser = Nokogiri::HTML5::Document.parse(result)
1264
+ xss = browser.at_xpath("//img/@onerror")
1265
+
1266
+ assert_nil(xss)
1267
+ end
1268
+
1269
+ def test_should_not_be_vulnerable_to_noscript_attacks
1270
+ # https://hackerone.com/reports/2509647
1271
+ skip("browser assertion requires parse_noscript_content_as_text") unless Nokogiri::VERSION >= "1.17"
1272
+
1273
+ input = '<noscript><p id="</noscript><script>alert(1)</script>"></noscript>'
1274
+
1275
+ result = nil
1276
+ assert_output(nil, /WARNING/) do
1277
+ result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: %w(p div noscript), attributes: %w(id class style))
1278
+ end
1279
+
1280
+ browser = Nokogiri::HTML5::Document.parse(result, parse_noscript_content_as_text: true)
1281
+ xss = browser.at_xpath("//script")
1282
+
1283
+ assert_nil(xss)
1284
+ end
1086
1285
  end if loofah_html5_support?
1087
1286
  end
@@ -121,6 +121,30 @@ class PermitScrubberTest < ScrubberTest
121
121
  assert_scrubbed html, '<tag></tag><tag cooler=""></tag>'
122
122
  end
123
123
 
124
+ def test_does_not_allow_safelisted_mglyph
125
+ # https://hackerone.com/reports/2519936
126
+ assert_output(nil, /WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber/) do
127
+ @scrubber.tags = ["div", "mglyph", "span"]
128
+ end
129
+ assert_equal(["div", "span"], @scrubber.tags)
130
+ end
131
+
132
+ def test_does_not_allow_safelisted_malignmark
133
+ # https://hackerone.com/reports/2519936
134
+ assert_output(nil, /WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber/) do
135
+ @scrubber.tags = ["div", "malignmark", "span"]
136
+ end
137
+ assert_equal(["div", "span"], @scrubber.tags)
138
+ end
139
+
140
+ def test_does_not_allow_safelisted_noscript
141
+ # https://hackerone.com/reports/2509647
142
+ assert_output(nil, /WARNING: 'noscript' tags cannot be allowed by the PermitScrubber/) do
143
+ @scrubber.tags = ["div", "noscript", "span"]
144
+ end
145
+ assert_equal(["div", "span"], @scrubber.tags)
146
+ end
147
+
124
148
  def test_leaves_text
125
149
  assert_scrubbed("some text")
126
150
  end
@@ -207,11 +231,65 @@ class ReturningStopFromScrubNodeTest < ScrubberTest
207
231
  end
208
232
  end
209
233
 
210
- def setup
211
- @scrubber = ScrubStopper.new
234
+ class ScrubContinuer < Rails::HTML::PermitScrubber
235
+ def scrub_node(node)
236
+ Loofah::Scrubber::CONTINUE
237
+ end
212
238
  end
213
239
 
214
240
  def test_returns_stop_from_scrub_if_scrub_node_does
241
+ @scrubber = ScrubStopper.new
215
242
  assert_scrub_stopped "<script>remove me</script>"
216
243
  end
244
+
245
+ def test_returns_continue_from_scrub_if_scrub_node_does
246
+ @scrubber = ScrubContinuer.new
247
+ assert_node_skipped "<script>keep me</script>"
248
+ end
249
+ end
250
+
251
+ class PermitScrubberMinimalOperationsTest < ScrubberTest
252
+ class TestPermitScrubber < Rails::HTML::PermitScrubber
253
+ def initialize
254
+ @scrub_attribute_args = []
255
+ @scrub_attributes_args = []
256
+
257
+ super
258
+
259
+ self.tags = ["div"]
260
+ self.attributes = ["class"]
261
+ end
262
+
263
+ def scrub_attributes(node)
264
+ @scrub_attributes_args << node.name
265
+
266
+ super
267
+ end
268
+
269
+ def scrub_attribute(node, attr)
270
+ @scrub_attribute_args << [node.name, attr.name]
271
+
272
+ super
273
+ end
274
+ end
275
+
276
+ def test_does_not_scrub_removed_attributes
277
+ @scrubber = TestPermitScrubber.new
278
+
279
+ input = "<div class='foo' href='bar'></div>"
280
+ frag = scrub_fragment(input)
281
+ assert_equal("<div class=\"foo\"></div>", frag)
282
+
283
+ assert_equal([["div", "class"]], @scrubber.instance_variable_get(:@scrub_attribute_args))
284
+ end
285
+
286
+ def test_does_not_scrub_attributes_of_a_removed_node
287
+ @scrubber = TestPermitScrubber.new
288
+
289
+ input = "<div class='foo' href='bar'><svg xlink:href='asdf'><set></set></svg></div>"
290
+ frag = scrub_fragment(input)
291
+ assert_equal("<div class=\"foo\"></div>", frag)
292
+
293
+ assert_equal(["div"], @scrubber.instance_variable_get(:@scrub_attributes_args))
294
+ end
217
295
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-html-sanitizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0.rc2
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafael Mendonça França
8
8
  - Kasper Timm Hansen
9
9
  - Mike Dalessio
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-05-24 00:00:00.000000000 Z
13
+ date: 2024-12-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: loofah
@@ -30,16 +30,70 @@ dependencies:
30
30
  name: nokogiri
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - "~>"
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.15.7
36
+ - - "!="
37
+ - !ruby/object:Gem::Version
38
+ version: 1.16.0
39
+ - - "!="
40
+ - !ruby/object:Gem::Version
41
+ version: 1.16.0.rc1
42
+ - - "!="
43
+ - !ruby/object:Gem::Version
44
+ version: 1.16.1
45
+ - - "!="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.16.2
48
+ - - "!="
34
49
  - !ruby/object:Gem::Version
35
- version: '1.14'
50
+ version: 1.16.3
51
+ - - "!="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.16.4
54
+ - - "!="
55
+ - !ruby/object:Gem::Version
56
+ version: 1.16.5
57
+ - - "!="
58
+ - !ruby/object:Gem::Version
59
+ version: 1.16.6
60
+ - - "!="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.16.7
36
63
  type: :runtime
37
64
  prerelease: false
38
65
  version_requirements: !ruby/object:Gem::Requirement
39
66
  requirements:
40
- - - "~>"
67
+ - - ">="
41
68
  - !ruby/object:Gem::Version
42
- version: '1.14'
69
+ version: 1.15.7
70
+ - - "!="
71
+ - !ruby/object:Gem::Version
72
+ version: 1.16.0
73
+ - - "!="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.16.0.rc1
76
+ - - "!="
77
+ - !ruby/object:Gem::Version
78
+ version: 1.16.1
79
+ - - "!="
80
+ - !ruby/object:Gem::Version
81
+ version: 1.16.2
82
+ - - "!="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.16.3
85
+ - - "!="
86
+ - !ruby/object:Gem::Version
87
+ version: 1.16.4
88
+ - - "!="
89
+ - !ruby/object:Gem::Version
90
+ version: 1.16.5
91
+ - - "!="
92
+ - !ruby/object:Gem::Version
93
+ version: 1.16.6
94
+ - - "!="
95
+ - !ruby/object:Gem::Version
96
+ version: 1.16.7
43
97
  description: HTML sanitization for Rails applications
44
98
  email:
45
99
  - rafaelmfranca@gmail.com
@@ -64,10 +118,10 @@ licenses:
64
118
  - MIT
65
119
  metadata:
66
120
  bug_tracker_uri: https://github.com/rails/rails-html-sanitizer/issues
67
- changelog_uri: https://github.com/rails/rails-html-sanitizer/blob/v1.6.0.rc2/CHANGELOG.md
68
- documentation_uri: https://www.rubydoc.info/gems/rails-html-sanitizer/1.6.0.rc2
69
- source_code_uri: https://github.com/rails/rails-html-sanitizer/tree/v1.6.0.rc2
70
- post_install_message:
121
+ changelog_uri: https://github.com/rails/rails-html-sanitizer/blob/v1.6.1/CHANGELOG.md
122
+ documentation_uri: https://www.rubydoc.info/gems/rails-html-sanitizer/1.6.1
123
+ source_code_uri: https://github.com/rails/rails-html-sanitizer/tree/v1.6.1
124
+ post_install_message:
71
125
  rdoc_options: []
72
126
  require_paths:
73
127
  - lib
@@ -78,12 +132,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
132
  version: 2.7.0
79
133
  required_rubygems_version: !ruby/object:Gem::Requirement
80
134
  requirements:
81
- - - ">"
135
+ - - ">="
82
136
  - !ruby/object:Gem::Version
83
- version: 1.3.1
137
+ version: '0'
84
138
  requirements: []
85
- rubygems_version: 3.4.10
86
- signing_key:
139
+ rubygems_version: 3.5.22
140
+ signing_key:
87
141
  specification_version: 4
88
142
  summary: This gem is responsible to sanitize HTML fragments in Rails applications.
89
143
  test_files: