XML External Entity (XXE) injection in ProcessDiagramLayoutFactory
Summary
ProcessDiagramLayoutFactory.parseXml(InputStream) builds a DocumentBuilderFactory with no XXE
protections and parses the BPMN XML of a process definition. Unlike BpmnXMLConverter, this parser
is not covered by any "safe XML" flag, so a DOCTYPE with an external entity in a deployed BPMN
resource is resolved when the process diagram layout is computed. A user able to deploy a process
definition can read arbitrary local files and perform server-side requests (SSRF) from the engine
host, even when the rest of the engine is configured for safe XML handling.
Details
The diagram-layout parser in
modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/diagram/ProcessDiagramLayoutFactory.java
creates a default DocumentBuilderFactory and immediately parses the input stream:
protected Document parseXml(InputStream bpmnXmlStream) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder;
Document bpmnModel;
try {
builder = factory.newDocumentBuilder();
bpmnModel = builder.parse(bpmnXmlStream); // <-- DTD + external entities enabled by default
} catch (Exception e) {
throw new FlowableException("Error while parsing BPMN model.", e);
}
return bpmnModel;
}None of disallow-doctype-decl, external-general-entities, external-parameter-entities,
load-external-dtd, setExpandEntityReferences(false), or XMLConstants.ACCESS_EXTERNAL_DTD are set.
The JAXP defaults resolve external entities, so a SYSTEM entity reference in the parsed XML is
fetched and inlined into the DOM.
The entry point is RepositoryService.getProcessDiagramLayout(processDefinitionId):
// GetDeploymentProcessDiagramLayoutCmd.execute
InputStream processModelStream = new GetDeploymentProcessModelCmd(processDefinitionId).execute(commandContext);
InputStream processDiagramStream = new GetDeploymentProcessDiagramCmd(processDefinitionId).execute(commandContext);
return new ProcessDiagramLayoutFactory().getProcessDiagramLayout(processModelStream, processDiagramStream);processModelStream is the raw BPMN resource that was stored at deployment time. Deployment stores
the original bytes, so a <!DOCTYPE ... > block survives in the deployed resource even though the
primary BpmnXMLConverter parse path hardens its own XMLInputFactory. When the layout is later
computed, parseXml re-parses those stored bytes with the unhardened factory and the external entity
fires.
The same unhardened pattern is present in the Flowable 5 compatibility engine at
modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/diagram/ProcessDiagramLayoutFactory.java.
PoC
(available upon request)
Impact
Any principal permitted to deploy a process definition (a modeler/deployer role, not only a full
engine administrator) can embed an external entity that is resolved by the diagram-layout parser. This
yields arbitrary local file disclosure (engine configuration, credentials, key material) and
server-side request forgery against internal network endpoints from the engine host. The parser is not
protected by the enableSafeBpmnXml flag, so safe-XML deployments are still affected. Scope is changed
because the leaked data and outbound requests target resources outside the deploying principal's
authority.
Source: flowable/flowable-engine