CVE-2022-28367 (Java, published 2022-04-21T23:15Z)
Description: value: OWASP AntiSamy before 1.6.6 allows XSS via HTML tag smuggling on STYLE content with crafted input. The output serializer does not properly encode the supposed Cascading Style Sheets (CSS) content.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/nahsra/antisamy
Files changed:
src/main/java/org/owasp/validator/html/scan/AntiSamyDOMScanner.java
src/test/java/org/owasp/validator/html/test/AntiSamyTest.java
src/test/java/org/owasp/validator/html/test/TestPolicy.java
Commit message: Support multiple children handling on style tags


Vulnerable code:
            Node firstChild = ele.getFirstChild();
            if (firstChild != null) {
                String toScan = firstChild.getNodeValue();
                final String cleanHTML = cr.getCleanHTML();
                if (cleanHTML == null || cleanHTML.equals("")) {
                    firstChild.setNodeValue("/* */");
                } else {
                    firstChild.setNodeValue(cleanHTML);
    protected TestPolicy(Policy.ParseContext parseContext) throws PolicyException {

Fixed code:
            if (ele.getChildNodes().getLength() > 0) {
                String toScan = "";

                for (int i = 0; i < ele.getChildNodes().getLength(); i++) {
                    Node childNode = ele.getChildNodes().item(i);
                    if (!toScan.isEmpty()){
                        toScan += "\n";
                    }
                    toScan += childNode.getTextContent();
                }
                String cleanHTML = cr.getCleanHTML();
                cleanHTML = cleanHTML == null || cleanHTML.equals("") ? "/* */" : cleanHTML;
                ele.getFirstChild().setNodeValue(cleanHTML);
                /*
                 * Remove every other node after cleaning CSS, there will
                 * be only one node in the end, as it always should have.
                 */
                for (int i = 1; i < ele.getChildNodes().getLength(); i++) {
                    Node childNode = ele.getChildNodes().item(i);
                    ele.removeChild(childNode);

    @Test
    public void testSmuggledTagsInStyleContent() throws ScanException, PolicyException {
        // HTML tags may be smuggled into a style tag after parsing input to an internal representation.
        // If that happens, they should be treated as text content and not as children nodes.

        Policy revised = policy.cloneWithDirective(Policy.USE_XHTML,"true");
        assertThat(as.scan("<style/>b<![cdata[</style><a href=javascript:alert(1)>test", revised, AntiSamy.DOM).getCleanHTML(), not(containsString("javascript")));
        assertThat(as.scan("<style/>b<![cdata[</style><a href=javascript:alert(1)>test", revised, AntiSamy.SAX).getCleanHTML(), not(containsString("javascript")));

        Policy revised2 = policy.cloneWithDirective(Policy.USE_XHTML,"false");
        assertThat(as.scan("<select<style/>W<xmp<script>alert(1)</script>", revised2, AntiSamy.DOM).getCleanHTML(), not(containsString("script")));
        assertThat(as.scan("<select<style/>W<xmp<script>alert(1)</script>", revised2, AntiSamy.SAX).getCleanHTML(), not(containsString("script")));
    }
    protected TestPolicy(Policy.ParseContext parseContext) {