Skip to content

XML External Entity (XXE) injection in ProcessDiagramLayoutFactory #4216

@geo-chen

Description

@geo-chen

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions