Skip to content

Commit de02971

Browse files
committed
Release 4.1.2 with security fix
1 parent 7e9754b commit de02971

3 files changed

Lines changed: 48 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Unreleased
22

3+
# 4.1.2
4+
5+
* fix: unexpected namespace switches after cleanup can cause mXSS
6+
37
# 4.1.1
48

59
* chore: upgrade to [html5ever 0.35][]

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ammonia"
3-
version = "4.1.1"
3+
version = "4.1.2"
44
authors = ["Michael Howell <michael@notriddle.com>"]
55
description = "HTML Sanitization"
66
keywords = [ "sanitization", "html", "security", "xss" ]

src/lib.rs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,12 +1841,11 @@ impl<'a> Builder<'a> {
18411841
let parent = node.parent
18421842
.replace(None).expect("a node in the DOM will have a parent, except the root, which is not processed")
18431843
.upgrade().expect("a node's parent will be pointed to by its parent (or the root pointer), and will not be dropped");
1844-
if self.clean_node_content(&node) {
1844+
if self.clean_node_content(&node) || !self.check_expected_namespace(&parent, &node) {
18451845
removed.push(node);
18461846
continue;
18471847
}
1848-
let pass_clean = self.clean_child(&mut node);
1849-
let pass = pass_clean && self.check_expected_namespace(&parent, &node);
1848+
let pass = self.clean_child(&mut node);
18501849
if pass {
18511850
self.adjust_node_attributes(&mut node, &link_rel, self.id_prefix);
18521851
dom.append(&parent.clone(), NodeOrText::AppendNode(node.clone()));
@@ -2053,21 +2052,18 @@ impl<'a> Builder<'a> {
20532052
matches!(
20542053
&*parent.local,
20552054
"mi" | "mo" | "mn" | "ms" | "mtext" | "annotation-xml"
2056-
)
2055+
) && if child.ns == ns!(html) { is_html_tag(&child.local) } else { true }
20572056
// The only way to switch from svg to mathml/html is with an html integration point
20582057
} else if parent.ns == ns!(svg) && child.ns != ns!(svg) {
20592058
// https://html.spec.whatwg.org/#svg-0
20602059
matches!(&*parent.local, "foreignObject")
2060+
&& if child.ns == ns!(html) { is_html_tag(&child.local) } else { true }
20612061
} else if child.ns == ns!(svg) {
20622062
is_svg_tag(&child.local)
20632063
} else if child.ns == ns!(mathml) {
20642064
is_mathml_tag(&child.local)
20652065
} else if child.ns == ns!(html) {
2066-
(!is_svg_tag(&child.local) && !is_mathml_tag(&child.local))
2067-
|| matches!(
2068-
&*child.local,
2069-
"title" | "style" | "font" | "a" | "script" | "span"
2070-
)
2066+
is_html_tag(&child.local)
20712067
} else {
20722068
// There are no other supported ways to switch namespace
20732069
parent.ns == child.ns
@@ -2224,6 +2220,14 @@ fn is_url_attr(element: &str, attr: &str) -> bool {
22242220
|| (element == "video" && attr == "poster")
22252221
}
22262222

2223+
fn is_html_tag(element: &str) -> bool {
2224+
(!is_svg_tag(element) && !is_mathml_tag(element))
2225+
|| matches!(
2226+
element,
2227+
"title" | "style" | "font" | "a" | "script" | "span"
2228+
)
2229+
}
2230+
22272231
/// Given an element name, check if it's SVG
22282232
fn is_svg_tag(element: &str) -> bool {
22292233
// https://svgwg.org/svg2-draft/eltindex.html
@@ -3630,15 +3634,15 @@ mod test {
36303634
// https://github.com/cure53/DOMPurify/pull/495
36313635
let fragment = r##"<svg><iframe><a title="</iframe><img src onerror=alert(1)>">test"##;
36323636
let result = String::from(Builder::new().add_tags(&["iframe"]).clean(fragment));
3633-
assert_eq!(result.to_string(), "test");
3637+
assert_eq!(result.to_string(), "");
36343638

36353639
let fragment = "<svg><iframe>remove me</iframe></svg><iframe>keep me</iframe>";
36363640
let result = String::from(Builder::new().add_tags(&["iframe"]).clean(fragment));
3637-
assert_eq!(result.to_string(), "remove me<iframe>keep me</iframe>");
3641+
assert_eq!(result.to_string(), "<iframe>keep me</iframe>");
36383642

36393643
let fragment = "<svg><a>remove me</a></svg><iframe>keep me</iframe>";
36403644
let result = String::from(Builder::new().add_tags(&["iframe"]).clean(fragment));
3641-
assert_eq!(result.to_string(), "remove me<iframe>keep me</iframe>");
3645+
assert_eq!(result.to_string(), "<iframe>keep me</iframe>");
36423646

36433647
let fragment = "<svg><a>keep me</a></svg><iframe>keep me</iframe>";
36443648
let result = String::from(Builder::new().add_tags(&["iframe", "svg"]).clean(fragment));
@@ -3648,6 +3652,19 @@ mod test {
36483652
);
36493653
}
36503654

3655+
#[test]
3656+
fn ns_svg_2() {
3657+
let fragment = "<svg><foreignObject><table><path><xmp><!--</xmp><img title'--&gt;&lt;img src=1 onerror=alert(1)&gt;'>";
3658+
let result = Builder::default()
3659+
.strip_comments(false)
3660+
.add_tags(&["svg","foreignObject","table","path","xmp"])
3661+
.clean(fragment);
3662+
assert_eq!(
3663+
result.to_string(),
3664+
"<svg><foreignObject><table></table></foreignObject></svg>"
3665+
);
3666+
}
3667+
36513668
#[test]
36523669
fn ns_mathml() {
36533670
// https://github.com/cure53/DOMPurify/pull/495
@@ -3680,6 +3697,20 @@ mod test {
36803697
);
36813698
}
36823699

3700+
#[test]
3701+
fn ns_mathml_2() {
3702+
let fragment = "<math><mtext><table><mglyph><xmp><!--</xmp><img title='--&gt;&lt;img src=1 onerror=alert(1)&gt;'>";
3703+
let result = Builder::default()
3704+
.strip_comments(false)
3705+
.add_tags(&["math","mtext","table","mglyph","xmp"])
3706+
.clean(fragment);
3707+
assert_eq!(
3708+
result.to_string(),
3709+
"<math><mtext><table></table></mtext></math>"
3710+
);
3711+
}
3712+
3713+
36833714
#[test]
36843715
fn xml_processing_instruction() {
36853716
// https://blog.slonser.info/posts/dompurify-node-type-confusion/

0 commit comments

Comments
 (0)