Consolidated to avoid flooding the tracker — happy to split on request. All findings verified dynamically by the reporter on the standard flowable-rest deployment (2026-09-02/03).
Finding 1 — Unrestricted deserialization of type=serializable variables (High)
Summary. The REST API accepts multipart variables of type=serializable; the raw bytes pass through ObjectInputStream.readObject() on the engine host with no class allow-list. We confirmed readObject executes and reconstructs attacker-supplied objects. The shipped -Djdk.serialFilter=maxarray=...;maxdepth=... bounds graph size only — gadget classes on the engine classpath remain viable.
Affected: flowable-engine @ 1663e588179d (flowable-app-rest module), standard flowable-rest deployment.
CVSS v3.1 (self-assessed): 8.0 — AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H (AC:H for gadget dependency; demo deployments with published credentials raise this materially).
Attack Path
Step 1: POST /runtime/tasks (create a task; any assignee)
Step 2: POST /runtime/tasks/{taskId}/variables (multipart)
name=serV1 type=serializable scope=local
file=@<serialized Java object bytes>
→ stored, then read through ObjectInputStream on read-back
Step 3: GET /runtime/tasks/{taskId}/variables/serV1
→ server has executed readObject() and reconstructs the object
(Same sink on /runtime/executions/{id}/variables and /runtime/process-instances/{id}/variables.)
Prerequisites: REST API credentials. The official sample deployments ship well-known demo credentials (rest-admin), which makes this effectively unauthenticated RCE surface there.
Proof of Concept (executed). Uploading a serialized java.util.Date (epoch 1700000000000) returned:
HTTP 201 {"name":"serV1","scope":"local","type":"date","value":"2023-11-14T22:13:20Z"}
The server executed readObject() and recognized the attacker-supplied object as a Date, echoing its value — arbitrary bytes are deserialized server-side; a GET returns the same reconstructed value.
Impact. With any gadget present in the engine's large dependency surface: full RCE. Without: unexpected object construction side effects and resource exhaustion.
Suggested fix. Reject type=serializable on REST input (keep JSON-friendly types; opaque binary otherwise) and add a strict class allow-list jdk.serialFilter for any path that must deserialize.
Finding 2 — Deployable process artifacts execute without a sandbox (High as deployed)
Summary. These are designed extension points; the risk is that no expression/script sandbox or allow-list exists and the REST demo profile makes deployment a single authenticated request. All of the following were executed on the engine host:
| Artifact |
Result (observed) |
serviceTask flowable:type="shell" (command=echo) |
OS command executed; stdout returned as process variable (out=SH_RCE_OK) |
<scriptTask> (JSR-223) |
script evaluated (execution.setVariable("sv","SCRIPT_1337")) |
serviceTask flowable:class="java.lang.Thread" |
class loaded + instantiated (type-check error proves Class.forName) |
flowable:expression="${1337+10}" (BPMN), DMN outputEntry ${1337+10}, CMMN + Event Registry delegate expressions |
JUEL evaluated (1347 returned in returnVariables) |
Suggested fix. Provide an opt-in sandbox profile for deployments where engine admins are not OS-trusted: disabled shell tasks by config, restricted ScriptEngine lookup, blocked type resolution in expressions.
Finding 3 — orderAscendingColumn / orderDescendingColumn SQL injection (Medium)
GET /management/tables/{table}/data?orderAscendingColumn=ID_' → HTTP 500 with a raw H2 driver syntax error; ordering by KEY_ vs NAME_ yields different result orders — a workable error/boolean-based injection over the authenticated management API.
Suggested fix: validate the column identifier against the table's actual columns; never interpolate the raw parameter.
Finding 4 — HTTP service task requestUrl expression SSRF (Medium)
A deployed HTTP service task whose requestUrl resolves from a process variable makes the engine issue a GET to an arbitrary URL at process start. Verified: a loopback listener captured the engine's GET /V27_SSRF. Combined with Finding 2's deployment path this is an internal-network probing primitive.
Suggested fix: URL validation / egress policy for HTTP tasks.
All steps executed by the reporter against a live deployment. AI tools were used to help discover and prepare these findings. Reported 2026-09-07; suggesting a 90-day coordinated disclosure window — happy to coordinate on GHSA/CVE.
Finding 1 — Unrestricted deserialization of
type=serializablevariables (High)Summary. The REST API accepts multipart variables of
type=serializable; the raw bytes pass throughObjectInputStream.readObject()on the engine host with no class allow-list. We confirmedreadObjectexecutes and reconstructs attacker-supplied objects. The shipped-Djdk.serialFilter=maxarray=...;maxdepth=...bounds graph size only — gadget classes on the engine classpath remain viable.Affected: flowable-engine @
1663e588179d(flowable-app-rest module), standard flowable-rest deployment.CVSS v3.1 (self-assessed): 8.0 —
AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H(AC:H for gadget dependency; demo deployments with published credentials raise this materially).Attack Path
(Same sink on
/runtime/executions/{id}/variablesand/runtime/process-instances/{id}/variables.)Prerequisites: REST API credentials. The official sample deployments ship well-known demo credentials (
rest-admin), which makes this effectively unauthenticated RCE surface there.Proof of Concept (executed). Uploading a serialized
java.util.Date(epoch1700000000000) returned:HTTP 201 {"name":"serV1","scope":"local","type":"date","value":"2023-11-14T22:13:20Z"}The server executed
readObject()and recognized the attacker-supplied object as a Date, echoing its value — arbitrary bytes are deserialized server-side; a GET returns the same reconstructed value.Impact. With any gadget present in the engine's large dependency surface: full RCE. Without: unexpected object construction side effects and resource exhaustion.
Suggested fix. Reject
type=serializableon REST input (keep JSON-friendly types; opaquebinaryotherwise) and add a strict class allow-listjdk.serialFilterfor any path that must deserialize.Finding 2 — Deployable process artifacts execute without a sandbox (High as deployed)
Summary. These are designed extension points; the risk is that no expression/script sandbox or allow-list exists and the REST demo profile makes deployment a single authenticated request. All of the following were executed on the engine host:
serviceTask flowable:type="shell"(command=echo)out=SH_RCE_OK)<scriptTask>(JSR-223)execution.setVariable("sv","SCRIPT_1337"))serviceTask flowable:class="java.lang.Thread"Class.forName)flowable:expression="${1337+10}"(BPMN), DMNoutputEntry${1337+10}, CMMN + Event Registry delegate expressions1347returned inreturnVariables)Suggested fix. Provide an opt-in sandbox profile for deployments where engine admins are not OS-trusted: disabled shell tasks by config, restricted
ScriptEnginelookup, blocked type resolution in expressions.Finding 3 —
orderAscendingColumn/orderDescendingColumnSQL injection (Medium)GET /management/tables/{table}/data?orderAscendingColumn=ID_'→ HTTP 500 with a raw H2 driver syntax error; ordering byKEY_vsNAME_yields different result orders — a workable error/boolean-based injection over the authenticated management API.Suggested fix: validate the column identifier against the table's actual columns; never interpolate the raw parameter.
Finding 4 — HTTP service task
requestUrlexpression SSRF (Medium)A deployed HTTP service task whose
requestUrlresolves from a process variable makes the engine issue a GET to an arbitrary URL at process start. Verified: a loopback listener captured the engine'sGET /V27_SSRF. Combined with Finding 2's deployment path this is an internal-network probing primitive.Suggested fix: URL validation / egress policy for HTTP tasks.
All steps executed by the reporter against a live deployment. AI tools were used to help discover and prepare these findings. Reported 2026-09-07; suggesting a 90-day coordinated disclosure window — happy to coordinate on GHSA/CVE.