Skip to content

Commit 0b8e9c4

Browse files
committed
XCOMMONS-2606: Properly validate data attributes in SecureHTMLElementSanitizer
* Make sure that the attribute is XML-compatible * Add tests
1 parent 8c82fc6 commit 0b8e9c4

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

xwiki-commons-core/xwiki-commons-xml/src/main/java/org/xwiki/xml/internal/html/SecureHTMLElementSanitizer.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,18 @@ public class SecureHTMLElementSanitizer implements HTMLElementSanitizer, Initial
6565
static final Pattern ATTR_WHITESPACE =
6666
Pattern.compile("[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]");
6767

68-
static final Pattern DATA_ATTR = Pattern.compile("^data-[\\-\\w.\\u00B7-\\uFFFF]");
68+
/**
69+
* Pattern that matches valid data-attributes.
70+
* <p>
71+
* Following the <a href="https://html.spec.whatwg.org/multipage/dom.html
72+
#embedding-custom-non-visible-data-with-the-data-*-attributes">HTML standard</a>
73+
* this means that the name starts with "data-", has at least one character after the hyphen and is
74+
* <a href="https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible">XML-compatible</a>,
75+
* i.e., matches the <a href="https://www.w3.org/TR/xml/#NT-Name">Name production</a> without ":".
76+
*/
77+
static final Pattern DATA_ATTR = Pattern.compile("^data-[A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6"
78+
+ "\\u00F8-\\u02ff\\u0370-\\u037d\\u037f-\\u1fff\\u200c\\u200d\\u2070-\\u218f\\u2c00-\\u2fef\\u3001-\\ud7ff"
79+
+ "\\uf900-\\ufdcf\\ufdf0-\\ufffd\\x{10000}-\\x{EFFFF}\\-.0-9\\u00b7\\u0300-\\u036f\\u203f-\\u2040]+$");
6980

7081
static final Pattern ARIA_ATTR = Pattern.compile("^aria-[\\-\\w]+$");
7182

@@ -182,7 +193,7 @@ public boolean isAttributeAllowed(String elementName, String attributeName, Stri
182193
String lowerElement = elementName.toLowerCase();
183194
String lowerAttribute = attributeName.toLowerCase();
184195

185-
if ((DATA_ATTR.matcher(lowerAttribute).find() || ARIA_ATTR.matcher(lowerAttribute).find())
196+
if ((DATA_ATTR.matcher(lowerAttribute).matches() || ARIA_ATTR.matcher(lowerAttribute).matches())
186197
&& !this.forbidAttributes.contains(lowerAttribute))
187198
{
188199
result = true;

xwiki-commons-core/xwiki-commons-xml/src/test/java/org/xwiki/xml/internal/html/SecureHTMLElementSanitizerTest.java

+19
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
import java.util.Collections;
2424

2525
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.CsvSource;
2628
import org.xwiki.test.annotation.BeforeComponent;
2729
import org.xwiki.test.annotation.ComponentList;
2830
import org.xwiki.test.junit5.mockito.ComponentTest;
2931
import org.xwiki.test.junit5.mockito.InjectMockComponents;
3032
import org.xwiki.test.junit5.mockito.MockComponent;
3133
import org.xwiki.xml.html.HTMLConstants;
3234

35+
import static org.junit.jupiter.api.Assertions.assertEquals;
3336
import static org.junit.jupiter.api.Assertions.assertFalse;
3437
import static org.junit.jupiter.api.Assertions.assertTrue;
3538
import static org.mockito.Mockito.when;
@@ -133,4 +136,20 @@ void restrictedURIs()
133136
assertFalse(this.secureHTMLElementSanitizer.isAttributeAllowed(HTMLConstants.TAG_A,
134137
HTMLConstants.ATTRIBUTE_HREF, "http://example.com"));
135138
}
139+
140+
@ParameterizedTest
141+
@CsvSource({
142+
"data-, false",
143+
"data-a, true",
144+
"data-x-wiki.test_\u0192, true",
145+
"data-x\u2713, false",
146+
"data-x/test, false",
147+
"data-x>test, false",
148+
"data-x:y, false"
149+
})
150+
void dataAttributes(String attribute, boolean accepted)
151+
{
152+
assertEquals(accepted, this.secureHTMLElementSanitizer.isAttributeAllowed(HTMLConstants.TAG_DIV, attribute,
153+
"hello"));
154+
}
136155
}

0 commit comments

Comments
 (0)