rails-html-sanitizer 1.6.0.rc2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -1
- data/README.md +54 -49
- data/lib/rails/html/sanitizer/version.rb +1 -1
- data/lib/rails/html/sanitizer.rb +1 -0
- data/lib/rails/html/scrubbers.rb +31 -7
- data/test/sanitizer_test.rb +208 -9
- data/test/scrubbers_test.rb +80 -2
- metadata +69 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad86488f25943fb1d0050513a08581231534a8552e4de66329270845ff650b12
|
4
|
+
data.tar.gz: ca70b518cc54e0ec2e224a5c3dbd82d567e39fd2d2ce3538e308f4d8846234b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6e2db01e0cd52d3bdcae7ceb90a081998111e84f19f4908d73a9b229a0ec87edfc10f05772b057d2c3e6c9cd08df267f82070f16015d9953d3edc85002dcafd
|
7
|
+
data.tar.gz: 746475ff0522b512e28f7bfd83a7e54be7445a29abcd9b1c8aa16c7b6c39b6f97f5e553ac228b3bad98af8b5f828c4fb6c311f75ddd4b286045ba9a353f0fd8a
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,55 @@
|
|
1
|
-
## 1.6.
|
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 `<` 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 & 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; Co.` which will render as "JPMorgan Chase &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 `<` 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 & 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; Co.` which will render as "JPMorgan Chase &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.
|
data/lib/rails/html/sanitizer.rb
CHANGED
data/lib/rails/html/scrubbers.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
111
|
-
|
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)
|
data/test/sanitizer_test.rb
CHANGED
@@ -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><script>alert(1)</script></style></svg>",
|
928
920
|
# libgumbo
|
929
|
-
"<svg><style
|
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><img src onerror=alert(1)>", ["svg", "style"]
|
982
|
+
actual = safe_list_sanitize(input, tags: tags)
|
983
|
+
acceptable_results = [
|
984
|
+
# libxml2
|
985
|
+
"<svg><style>&lt;img src onerror=alert(1)></style></svg>",
|
986
|
+
# libgumbo
|
987
|
+
"<svg><style><img src onerror=alert(1)></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><img src onerror=alert(1)>", ["math", "style"]
|
996
|
+
actual = safe_list_sanitize(input, tags: tags)
|
997
|
+
acceptable_results = [
|
998
|
+
# libxml2
|
999
|
+
"<math><style>&lt;img src onerror=alert(1)></style></math>",
|
1000
|
+
# libgumbo
|
1001
|
+
"<math><style><img src onerror=alert(1)></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><style class='</style>alert(1)'>",
|
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><style class='</style>alert(1)'>",
|
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><img src=: onerror=alert(1)></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><img src=: onerror=alert(1)></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><img src=: onerror=alert(1)></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=\"</noscript><script>alert(1)</script>\"></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><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
|
data/test/scrubbers_test.rb
CHANGED
@@ -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
|
-
|
211
|
-
|
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.
|
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:
|
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:
|
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:
|
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.
|
68
|
-
documentation_uri: https://www.rubydoc.info/gems/rails-html-sanitizer/1.6.
|
69
|
-
source_code_uri: https://github.com/rails/rails-html-sanitizer/tree/v1.6.
|
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:
|
137
|
+
version: '0'
|
84
138
|
requirements: []
|
85
|
-
rubygems_version: 3.
|
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:
|