diff --git a/pom.xml b/pom.xml index 03ef2189f..d2bea3521 100644 --- a/pom.xml +++ b/pom.xml @@ -201,6 +201,16 @@ 6.0.1 test + + + + xerces + xercesImpl + 2.12.2 + test + diff --git a/src/main/java/org/cyclonedx/CycloneDxSchema.java b/src/main/java/org/cyclonedx/CycloneDxSchema.java index d25b4fcaf..fbe3cb997 100644 --- a/src/main/java/org/cyclonedx/CycloneDxSchema.java +++ b/src/main/java/org/cyclonedx/CycloneDxSchema.java @@ -30,6 +30,7 @@ import com.networknt.schema.serialization.DefaultNodeReader; import org.cyclonedx.generators.json.BomJsonGenerator; import org.cyclonedx.generators.xml.BomXmlGenerator; +import org.cyclonedx.util.XmlFactoryUtils; import org.xml.sax.SAXException; import javax.xml.XMLConstants; @@ -331,9 +332,16 @@ private Schema getXmlSchema17() throws SAXException { } public Schema getXmlSchema(InputStream... inputStreams) throws SAXException { - final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + final SchemaFactory schemaFactory = XmlFactoryUtils.newSchemaFactory(); + try { + schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXException e) { + // JAXP 1.5 secure-processing properties are not supported by outdated SchemaFactory + // implementations (e.g. Xerces 2.x found on the classpath): restricting external access + // is a hardening measure and not required for correctness, as only local schema copies + // are used, so ignore and continue + } schemaFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); final Source[] schemaFiles = new Source[inputStreams.length]; for (int i = 0; i < inputStreams.length; i++) { diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java index b985c8f23..6176d7f05 100644 --- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java +++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java @@ -35,6 +35,7 @@ import org.cyclonedx.Version; import org.cyclonedx.exception.GeneratorException; import org.cyclonedx.model.Bom; +import org.cyclonedx.util.XmlFactoryUtils; import org.cyclonedx.util.introspector.VersionXmlAnnotationIntrospector; import org.cyclonedx.util.serializer.DependencySerializer; import org.w3c.dom.Document; @@ -84,7 +85,7 @@ private void registerDependencyModule(final ObjectMapper mapper, final boolean u * @throws javax.xml.parsers.ParserConfigurationException thrown if there is a parser configuration exception */ private DocumentBuilder buildSecureDocumentBuilder() throws ParserConfigurationException { - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilderFactory factory = XmlFactoryUtils.newDocumentBuilderFactory(); factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); diff --git a/src/main/java/org/cyclonedx/parsers/XmlParser.java b/src/main/java/org/cyclonedx/parsers/XmlParser.java index fb482ba43..42351c7b8 100644 --- a/src/main/java/org/cyclonedx/parsers/XmlParser.java +++ b/src/main/java/org/cyclonedx/parsers/XmlParser.java @@ -24,6 +24,7 @@ import org.cyclonedx.Version; import org.cyclonedx.exception.ParseException; import org.cyclonedx.model.Bom; +import org.cyclonedx.util.XmlFactoryUtils; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; @@ -335,9 +336,15 @@ private void extractNamespaces(Node node, List namespaces) { private Document createSecureDocument(InputSource in) throws ParserConfigurationException, IOException, SAXException { //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#xpathexpression - DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); - df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + DocumentBuilderFactory df = XmlFactoryUtils.newDocumentBuilderFactory(); + try { + df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (IllegalArgumentException e) { + // JAXP 1.5 secure-processing attributes are not supported by outdated + // DocumentBuilderFactory implementations (e.g. Xerces 2.x found on the classpath): + // secure processing below still prevents external entity resolution, so ignore + } df.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); DocumentBuilder builder = df.newDocumentBuilder(); return builder.parse(in); diff --git a/src/main/java/org/cyclonedx/util/XmlFactoryUtils.java b/src/main/java/org/cyclonedx/util/XmlFactoryUtils.java new file mode 100644 index 000000000..ad321b071 --- /dev/null +++ b/src/main/java/org/cyclonedx/util/XmlFactoryUtils.java @@ -0,0 +1,72 @@ +/* + * This file is part of CycloneDX Core (Java). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.cyclonedx.util; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.validation.SchemaFactory; + +/** + * Creates JAXP factories for XML processing, preferring the JDK's built-in system-default + * implementations over the JAXP lookup mechanism. + * + *

The standard {@code newInstance()} lookup uses the classpath (ServiceLoader / system + * properties), so an outdated XML parser leaking onto the classpath (e.g. Xerces 2.x pulled in + * transitively by another library) would be picked up and break BOM parsing and validation with + * errors like {@code Property 'http://javax.xml.XMLConstants/property/accessExternalDTD' is not + * recognized}, because such parsers pre-date the JAXP 1.5 secure-processing properties. + * See cyclonedx-gradle-plugin#349.

+ * + *

The {@code newDefaultInstance()} factory methods only exist since Java 9 while this library + * targets Java 8, so they are invoked reflectively, falling back to the standard lookup.

+ * + * @since 13.1.0 + */ +public final class XmlFactoryUtils +{ + private XmlFactoryUtils() { + } + + /** + * Creates a new {@link DocumentBuilderFactory}, preferring the JDK's built-in implementation. + * + * @return a new {@link DocumentBuilderFactory} + */ + public static DocumentBuilderFactory newDocumentBuilderFactory() { + try { + return (DocumentBuilderFactory) DocumentBuilderFactory.class.getMethod("newDefaultInstance").invoke(null); + } catch (ReflectiveOperationException e) { + return DocumentBuilderFactory.newInstance(); + } + } + + /** + * Creates a new {@link SchemaFactory} for W3C XML Schema, preferring the JDK's built-in + * implementation. + * + * @return a new {@link SchemaFactory} + */ + public static SchemaFactory newSchemaFactory() { + try { + return (SchemaFactory) SchemaFactory.class.getMethod("newDefaultInstance").invoke(null); + } catch (ReflectiveOperationException e) { + return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + } + } +}