Skip to content

Commit 14f84fd

Browse files
committed
Recognize that <style> is not really workable inside <select>
Rather than mucking with `<style>` tag content in all cases, this is a more tailored fix to the recent vulnerability that just closes `<style>` elements when we realize they're in a dodgy parsing context.
1 parent e2b29e8 commit 14f84fd

File tree

4 files changed

+47
-39
lines changed

4 files changed

+47
-39
lines changed

src/main/java/org/owasp/html/HtmlStreamRenderer.java

-21
Original file line numberDiff line numberDiff line change
@@ -254,25 +254,8 @@ private final void writeCloseTag(String uncanonElementName)
254254
Encoding.stripBannedCodeunits(cdataContent);
255255
int problemIndex = checkHtmlCdataCloseable(lastTagOpened, cdataContent);
256256
if (problemIndex == -1) {
257-
String prefix = "";
258-
String suffix = "";
259-
Set<String> bannedSubstrings = Collections.emptySet();
260-
if ("style".equals(elementName)) {
261-
prefix = "/*<![CDATA[<!--*/\n";
262-
suffix = "\n/*-->]]>*/";
263-
bannedSubstrings = BANNED_IN_STYLE_ELEMENTS;
264-
}
265-
266-
for (String bannedSubstring : bannedSubstrings) {
267-
if (cdataContent.indexOf(bannedSubstring) >= 0) {
268-
cdataContent.setLength(0);
269-
}
270-
}
271-
272257
if (cdataContent.length() != 0) {
273-
output.append(prefix);
274258
output.append(cdataContent);
275-
output.append(suffix);
276259
}
277260
} else {
278261
error(
@@ -457,8 +440,4 @@ public void close() throws IOException {
457440
private static boolean isTagEnd(char ch) {
458441
return ch < 63 && 0 != (TAG_ENDS & (1L << ch));
459442
}
460-
461-
private static Set<String> BANNED_IN_STYLE_ELEMENTS = ImmutableSet.of(
462-
"<![CDATA[", "]]>", "<!--", "-->"
463-
);
464443
}

src/main/java/org/owasp/html/TagBalancingHtmlStreamEventReceiver.java

+20
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,15 @@ && canContain(elIndex, toResume, nOpen)) {
217217
private boolean canContain(
218218
int child, int container, int containerIndexOnStack) {
219219
Preconditions.checkArgument(containerIndexOnStack >= 0);
220+
if (child == HtmlElementTables.TEXT_NODE && hasSpecialTextMode(container)) {
221+
// If there's a select element on the stack, then we need to be extra careful.
222+
int selectElementIndex = METADATA.indexForName("select");
223+
for (int i = containerIndexOnStack; --i >= 0;) {
224+
if (selectElementIndex == openElements.get(i)) {
225+
return false;
226+
}
227+
}
228+
}
220229
int anc = container;
221230
int ancIndexOnStack = containerIndexOnStack;
222231
while (true) {
@@ -363,6 +372,17 @@ private static boolean isHeaderElementName(String canonElementName) {
363372
&& canonElementName.charAt(1) <= '9';
364373
}
365374

375+
private static boolean hasSpecialTextMode(int elementIndex) {
376+
String name = METADATA.canonNameForIndex(elementIndex);
377+
switch (HtmlTextEscapingMode.getModeForTag(name)) {
378+
case PCDATA: case VOID:
379+
return false;
380+
case CDATA: case CDATA_SOMETIMES: case RCDATA: case PLAIN_TEXT:
381+
return true;
382+
}
383+
throw new IllegalArgumentException(name);
384+
}
385+
366386
private static final byte ALL_SCOPES;
367387
private static final byte[] SCOPES_BY_ELEMENT;
368388
private static final byte[] SCOPE_FOR_END_TAG;

src/test/java/org/owasp/html/SanitizersTest.java

+24-15
Original file line numberDiff line numberDiff line change
@@ -439,48 +439,57 @@ public static final void testStyleTagsInAllTheWrongPlaces() {
439439
String input = ""
440440
+ "<select><option><style><script>alert(1)</script></style></option></select>"
441441
+ "<svg><style>.r { color: red }</style></svg>"
442-
+ "<style>.b { color: blue }</style>"
443-
+ "<style>#a { content: \"<!--\" }</style>"
444-
+ "<style>#a { content: \"<![CDATA[\" }</style>"
445-
+ "<style>#a { content: \"-->\" }</style>"
446-
+ "<style>#a { content: \"]]>\" }</style>";
442+
+ "<style>.b { color: blue }</style>";
447443
PolicyFactory pf = new HtmlPolicyBuilder()
448444
.allowElements("option", "select", "style", "svg")
449445
.allowTextIn("style")
450446
.toFactory();
451447
assertEquals(
452448
""
453449
+ "<select><option>"
454-
+ "<style>/*<![CDATA[<!--*/\n<script>alert(1)</script>\n/*-->]]>*/</style>"
450+
+ "<style></style>&lt;script&gt;alert(1)&lt;/script&gt;"
455451
+ "</option></select>"
456452
+ "<svg>"
457-
+ "<style>/*<![CDATA[<!--*/\n.r { color: red }\n/*-->]]>*/</style>"
453+
+ "<style>.r { color: red }</style>"
458454
+ "</svg>"
459-
+ "<style>/*<![CDATA[<!--*/\n.b { color: blue }\n/*-->]]>*/</style>"
460-
+ "<style></style>"
461-
+ "<style></style>"
462-
+ "<style></style>"
463-
+ "<style></style>",
455+
+ "<style>.b { color: blue }</style>",
464456
pf.sanitize(input)
465457
);
466458
}
467459

468460
@Test
469461
public static final void testSelectIsOdd() {
462+
// Special text modes interact badly with select and option
470463
String input = "<select><option><xmp><script>alert(1)</script></xmp></option></select>";
471464
PolicyFactory pf = new HtmlPolicyBuilder()
472465
.allowElements("option", "select", "xmp")
473-
.allowTextIn("xmp")
466+
.allowTextIn("xmp", "option")
474467
.toFactory();
475468
assertEquals(
476469
""
477-
+ "<select><option>"
478-
+ "<pre>&lt;script&gt;alert(1)&lt;/script&gt;</pre>"
470+
+ "<select><option><pre></pre>"
471+
+ "&lt;script&gt;alert(1)&lt;/script&gt;"
479472
+ "</option></select>",
480473
pf.sanitize(input)
481474
);
482475
}
483476

477+
@Test
478+
public static final void testOptionAllowsText() {
479+
String input = "<select><option><pre>code goes here</pre></option></select>";
480+
PolicyFactory pf = new HtmlPolicyBuilder()
481+
.allowElements("option", "select", "pre")
482+
.allowTextIn("pre", "option")
483+
.toFactory();
484+
assertEquals(
485+
""
486+
+ "<select><option>"
487+
+ "<pre>code goes here</pre>"
488+
+ "</option></select>",
489+
pf.sanitize(input)
490+
);
491+
}
492+
484493
@Test
485494
public static final void testStyleGlobally() {
486495
PolicyFactory policyBuilder = new HtmlPolicyBuilder()

src/test/java/org/owasp/html/TagBalancingHtmlStreamRendererTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ public final void testTextContent() {
158158
+ "<p>Hello, <textarea>World!</textarea></p>"
159159
+ "<h1>Hello"
160160
// Text allowed in special style tag.
161-
+ "<style type=\"text/css\">/*<![CDATA[<!--*/\n"
162-
+ "\n.World {\n color: blue\n}\n"
163-
+ "\n/*-->]]>*/</style></h1>"
161+
+ "<style type=\"text/css\">\n"
162+
+ ".World {\n color: blue\n}\n"
163+
+ "</style></h1>"
164164
// Whitespace allowed inside <ul> but non-whitespace text nodes are
165165
// moved inside <li>.
166166
+ "<ul><li>Hello,</li><li>World!</li></ul>",

0 commit comments

Comments
 (0)