flowable / flowable/flowable-engine

XML External Entity (XXE) injection in ProcessDiagramLayoutFactory

Open
#4,216 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
9.5k
Forks
2.9k
Avg merge
7h 8m
Merged PRs (30d)
2

Description

### 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:

```java
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)`:

```java
// 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 `` 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.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.