Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import java.util.TreeMap;

import javax.imageio.ImageIO;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
Expand Down Expand Up @@ -103,6 +105,20 @@ protected Document parseXml(InputStream bpmnXmlStream) {
// Get one that understands namespaces
factory.setNamespaceAware(true);

try {
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (ParserConfigurationException e) {
throw new FlowableException("Error configuring XML parser for safe BPMN parsing", e);
}

DocumentBuilder builder;
Document bpmnModel;
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* 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.
*/

package org.flowable.engine.impl.bpmn.diagram;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import org.flowable.common.engine.api.FlowableException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.w3c.dom.Document;

public class ProcessDiagramLayoutFactoryTest {

private final ProcessDiagramLayoutFactory factory = new ProcessDiagramLayoutFactory();

@Test
public void testExternalEntityInElementTextIsRejected(@TempDir Path tempDir) throws Exception {
// The entity must be referenced in element text content. In an attribute value the JDK
// rejects an external entity reference even when the parser is not hardened, so such a
// test would also pass without the fix.
Path secret = tempDir.resolve("secret.txt");
Files.writeString(secret, "TOP-SECRET-CONTENT");

String maliciousBpmn = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE definitions [<!ENTITY xxe SYSTEM "%s">]>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<documentation>&xxe;</documentation>
</definitions>
""".formatted(secret.toUri());

assertThatThrownBy(() -> factory.parseXml(new ByteArrayInputStream(maliciousBpmn.getBytes(StandardCharsets.UTF_8))))
.isInstanceOf(FlowableException.class)
.hasMessageContaining("Error while parsing BPMN model");
}

@Test
public void testDoctypeIsRejected() {
String bpmnWithDoctype = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE definitions [<!ENTITY a "b">]>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"/>
""";

assertThatThrownBy(() -> factory.parseXml(new ByteArrayInputStream(bpmnWithDoctype.getBytes(StandardCharsets.UTF_8))))
.isInstanceOf(FlowableException.class);
}

@Test
public void testValidBpmnStillParses() {
String validBpmn = """
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="p1"/>
</definitions>
""";

Document document = factory.parseXml(new ByteArrayInputStream(validBpmn.getBytes(StandardCharsets.UTF_8)));

assertThat(document.getDocumentElement().getLocalName()).isEqualTo("definitions");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import java.util.TreeMap;

import javax.imageio.ImageIO;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
Expand Down Expand Up @@ -98,6 +100,20 @@ protected Document parseXml(InputStream bpmnXmlStream) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Get one that understands namespaces
factory.setNamespaceAware(true);

try {
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (ParserConfigurationException e) {
throw new FlowableException("Error configuring XML parser for safe BPMN parsing", e);
}

DocumentBuilder builder;
Document bpmnModel;
Expand Down