CVE-2023-26119 (published 2023-04-03T05:15Z)

Versions of the package net.sourceforge.htmlunit:htmlunit from 0 and before 3.0.0 are vulnerable to Remote Code Execution (RCE) via XSTL, when browsing the attacker’s webpage.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/HtmlUnit/htmlunit
commit_details: repo_url: https://github.com/HtmlUnit/htmlunit
author: Ronald Brill
author_date: 2023-01-24 14:00:17+01:00
msg: enable FEATURE_SECURE_PROCESSING for the XSLT processor
repository: repo_url: https://github.com/HtmlUnit/htmlunit
repo_name: HtmlUnit/htmlunit
description: HtmlUnit is a "GUI-Less browser for Java programs".
repo_language: Java
owner: HtmlUnit
file_changes: filename: changes.xml
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,9 @@
 
     <body>
         <release version="2.70.0" date="Febuary xx, 2023" description="Bugfixes">
+            <action type="fix" dev="rbri">
+                Enable FEATURE_SECURE_PROCESSING for the XSLT processor.
+            </action>
             <action type="add" dev="rbri">
                 Document disabling of website certificate check in the FAQ.
             </action>

programming_language: HTML
filename: XSLTProcessor.java
change_type: ModificationType.MODIFY
diff: @@ -26,6 +26,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Result;
@@ -128,6 +129,11 @@ public class XSLTProcessor extends HtmlUnitScriptable {
 
             final TransformerFactory transformerFactory = TransformerFactory.newInstance();
 
+            // By default, the JDK turns on FSP for DOM and SAX parsers and XML schema validators,
+            // which sets a number of processing limits on the processors. Conversely, by default,
+            // the JDK turns off FSP for transformers and XPath, which enables extension functions for XSLT and XPath.
+            transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+
             final SgmlPage page = sourceDomNode.getPage();
             if (page != null && page.getWebClient().getBrowserVersion()
                                             .hasFeature(JS_XSLT_TRANSFORM_INDENT)) {

programming_language: Java
filename: ErrorOutputChecker.java
change_type: ModificationType.MODIFY
diff: @@ -47,7 +47,12 @@ public class ErrorOutputChecker implements MethodRule {
             // Quercus
             Pattern.compile(".*com.caucho.quercus.servlet.QuercusServlet initImpl\r?\n"),
             Pattern.compile(".*QuercusServlet starting as QuercusServletImpl\r?\n"),
-            Pattern.compile(".*Quercus finished initialization in \\d*ms\r?\n")
+            Pattern.compile(".*Quercus finished initialization in \\d*ms\r?\n"),
+
+            // Xalan
+            Pattern.compile("ERROR:\\s*'Use of the extension function "
+                    + "'http://xml\\.apache\\.org/xalan/java/.*' "
+                    + "is not allowed when the secure processing feature is set to true\\.'\r?\n"),
     };
 
     /**

programming_language: Java
filename: XSLTProcessorTest.java
change_type: ModificationType.MODIFY
diff: @@ -23,6 +23,7 @@ import com.gargoylesoftware.htmlunit.MockWebConnection;
 import com.gargoylesoftware.htmlunit.WebDriverTestCase;
 import com.gargoylesoftware.htmlunit.junit.BrowserRunner;
 import com.gargoylesoftware.htmlunit.junit.BrowserRunner.Alerts;
+import com.gargoylesoftware.htmlunit.junit.BrowserRunner.HtmlUnitNYI;
 import com.gargoylesoftware.htmlunit.util.MimeType;
 
 /**
@@ -38,28 +39,49 @@ public class XSLTProcessorTest extends WebDriverTestCase {
      * @throws Exception if the test fails
      */
     @Test
-    @Alerts("exception")
+    @Alerts(DEFAULT = "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
+                + "<head></head><body> <h2>My CD Collection</h2> "
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul> </body></html>",
+            FF = "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
+                + "<body><h2>My CD Collection</h2>"
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul></body></html>",
+            FF_ESR = "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
+                + "<body><h2>My CD Collection</h2>"
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul></body></html>",
+            IE = "exception")
+    @HtmlUnitNYI(CHROME = "<html><body><h2>My CD Collection</h2>"
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul></body></html>",
+            EDGE = "<html><body><h2>My CD Collection</h2>"
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul></body></html>",
+            FF = "<html><body><h2>My CD Collection</h2>"
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul></body></html>",
+            FF_ESR = "<html><body><h2>My CD Collection</h2>"
+                + "<ul><li>Empire Burlesque (Bob Dylan)</li></ul></body></html>")
     public void test() throws Exception {
         final String html = "<html><head>\n"
             + "<script>\n"
             + LOG_TITLE_FUNCTION
+
+            + "  function createXmlDocument() {\n"
+            + "    return document.implementation.createDocument('', '', null);\n"
+            + "  }\n"
+
+            + "  function  loadXMLDocument(url) {\n"
+            + "    var xhttp = new XMLHttpRequest();\n"
+            + "    xhttp.open('GET', url, false);\n"
+            + "    xhttp.send();\n"
+            + "    return xhttp.responseXML;\n"
+            + "  }"
+
             + "  function test() {\n"
             + "    try {\n"
-            + "      var xmlDoc = createXmlDocument();\n"
-            + "      xmlDoc.async = false;\n"
-            + "      xmlDoc.load('" + URL_SECOND + "1');\n"
-
-            + "      var xslDoc;\n"
-            + "      xslDoc = createXmlDocument();\n"
-            + "      xslDoc.async = false;\n"
-            + "      xslDoc.load('" + URL_SECOND + "2');\n"
+            + "      var xmlDoc = loadXMLDocument('" + URL_SECOND + "1');\n"
+            + "      var xslDoc = loadXMLDocument('" + URL_SECOND + "2');\n"
 
             + "      var processor = new XSLTProcessor();\n"
             + "      processor.importStylesheet(xslDoc);\n"
             + "      var newDocument = processor.transformToDocument(xmlDoc);\n"
-            + "      log(new XMLSerializer().serializeToString(newDocument.documentElement).length);\n"
-            + "      newDocument = processor.transformToDocument(xmlDoc.documentElement);\n"
-            + "      log(newDocument.documentElement);\n"
+            + "      log(new XMLSerializer().serializeToString(newDocument.documentElement));\n"
             + "    } catch(e) { log('exception'); }\n"
             + "  }\n"
 
@@ -199,4 +221,68 @@ public class XSLTProcessorTest extends WebDriverTestCase {
             + "</body></html>";
         loadPageVerifyTitle2(html);
     }
+
+    /**
+     * @throws Exception if the test fails
+     */
+    @Test
+    @Alerts(DEFAULT = {"preparation done", "null"},
+            FF = {"preparation done", "exception"},
+            FF_ESR = {"preparation done", "exception"},
+            IE = "exception")
+    @HtmlUnitNYI(CHROME = {"preparation done", "exception"},
+            EDGE = {"preparation done", "exception"})
+    public void testSecurity() throws Exception {
+        final String html = "<html><head>\n"
+            + "<script>\n"
+            + LOG_TITLE_FUNCTION
+
+            + "  function createXmlDocument() {\n"
+            + "    return document.implementation.createDocument('', '', null);\n"
+            + "  }\n"
+
+            + "  function  loadXMLDocument(url) {\n"
+            + "    var xhttp = new XMLHttpRequest();\n"
+            + "    xhttp.open('GET', url, false);\n"
+            + "    xhttp.send();\n"
+            + "    return xhttp.responseXML;\n"
+            + "  }"
+
+            + "  function test() {\n"
+            + "    try {\n"
+            + "      var xmlDoc = loadXMLDocument('" + URL_SECOND + "1');\n"
+            + "      var xslDoc = loadXMLDocument('" + URL_SECOND + "2');\n"
+
+            + "      var processor = new XSLTProcessor();\n"
+            + "      processor.importStylesheet(xslDoc);\n"
+            + "      log('preparation done');\n"
+            + "      var newDocument = processor.transformToDocument(xmlDoc);\n"
+            + "      log(newDocument);\n"
+            + "    } catch(e) { log('exception'); }\n"
+            + "  }\n"
+            + "</script></head>"
+            + "<body onload='test()'>\n"
+            + "</body></html>";
+
+        final String xml
+            = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
+            + "

";
+
+        final String xsl
+            = " <xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" "
+                    + "xmlns:rt=\"http://xml.apache.org/xalan/java/java.lang.Runtime\" "
+                    + "xmlns:ob=\"http://xml.apache.org/xalan/java/java.lang.Object\">\r\n"
+            + "    <xsl:template match='/'>\n"
+            + "      <xsl:variable name='rtobject' select='rt:getRuntime()'/>\n"
+            + "      <xsl:variable name=\"rtString\" select=\"ob:toString($rtobject)\"/>\n"
+            + "      <xsl:value-of select=\"$rtString\"/>\n"
+            + "    </xsl:template>\r\n"
+            + "  </xsl:stylesheet>";
+
+        final MockWebConnection conn = getMockWebConnection();
+        conn.setResponse(new URL(URL_SECOND, "1"), xml, MimeType.TEXT_XML);
+        conn.setResponse(new URL(URL_SECOND, "2"), xsl, MimeType.TEXT_XML);
+
+        loadPageVerifyTitle2(html);
+    }
 }

programming_language: HTML