SPLWare / SPLWare/esProc

[Security] Unauthenticated Java deserialization RCE

Open
#67 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
4.7k
Forks
364
PR merge metrics
No merged PRs in 30d

Description

Unauthenticated Java deserialization RCE on the unit/node server

CWE: CWE-502 (Deserialization of Untrusted Data)
Affected versions: <=V20260507

Summary

The esProc unit/node server deserializes objects straight from a raw TCP socket using ObjectInputStream.readUnshared() with no ObjectInputFilter or any other class restriction. The server listens on TCP port 8281 by default. A network attacker who can reach the port sends a crafted serialized object that, combined with a gadget present on the classpath, yields remote code execution. No authentication or handshake is required before the read.

Vulnerability chain

Stage Component Location
TCP source Unit/node server accepts connections and builds a SocketData for each parallel/UnitWorker.java:45-48
Stream build ObjectInputStream wraps the raw socket stream, no filter set parallel/SocketData.java:65-68
Read (before auth) UnitWorker.run calls socketData.read() at the top of its loop parallel/UnitWorker.java:57
Deser sink SocketData.readois.readUnshared() on the unfiltered stream parallel/SocketData.java:123
Default port Unit context defaults the listener to port 8281 parallel/UnitContext.java:180

For each accepted connection, UnitWorker.run() immediately calls socketData.read() (line 57) before any request is interpreted. That read reaches SocketData.read()ois.readUnshared() (line 123), where ois is a plain ObjectInputStream constructed over the socket's input stream (lines 65-68). Because there is no ObjectInputFilter, any serializable type resolvable on the server classpath is instantiated during deserialization. The IP-whitelist gate (errorCheck) only affects later DFX task execution (line 69); it does not gate the deserialization itself, and it does not authenticate the connection.

Key code

The deserialization sink, with the unfiltered ObjectInputStream:

// src/main/java/com/scudata/parallel/SocketData.java:65-68  (server-side stream build)
public void holdCommunicateStreamServer() throws IOException {
    InputStream is = socket.getInputStream();
    BufferedInputStream bis = new BufferedInputStream(is);
    ois = new ObjectInputStream(bis);          // no ObjectInputFilter
    ...
}

// SocketData.java:121-125  (read sink)
public Object read() throws IOException, ClassNotFoundException {
    Object obj = ois.readUnshared();           // :123 — attacker object
    return obj;
}

The read happens before the request is parsed or the IP check is applied:

// src/main/java/com/scudata/parallel/UnitWorker.java:53-69
public void run() {
    try {
        Response response = null;
        while (!stop) {
            Object obj = socketData.read();    // :57 — deser before any gate

            if (obj == null || !(obj instanceof Request)) {
                break;
            }
            Request req = (Request) obj;
            switch (req.getActionType()) {
            case Request.TYPE_DFX:
                ...
                if (errorCheck) {              // :69 — IP-whitelist only affects task exec

Default listener port:

// src/main/java/com/scudata/parallel/UnitContext.java:177-180
public UnitContext(String specifyHost, int specifyPort) throws Exception {
    UnitConfig uc = getUnitConfig();
    String host = null;
    int port = 8281;                           // :180 — default port

Proof of Concept

No gadget bytes are included. This is the network-level trigger showing how the sink is reached unauthenticated.

The unit/node server accepts a plain TCP connection on the configured port (default 8281). Upon connect, UnitWorker immediately calls SocketData.read(), which performs ObjectInputStream.readUnshared() over the socket stream. An attacker opens a TCP connection to the port and writes a serialized Java object stream; the object is deserialized with no class filtering before any authentication or application-level request handling.


## Impact

A network attacker who can reach the unit/node server port (default 8281) achieves remote code execution as the JVM user running esProc, with no authentication and no prior handshake. Exploitability depends on a usable gadget chain being present on the classpath; even absent a gadget, untrusted deserialization can cause crashes / denial of service through crafted object graphs. The default bind host is taken from the configured node IP (commonly a reachable interface rather than loopback), so exposure depends on how the deployment is configured.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read parallel/SocketData.java, parallel/UnitWorker.java, and parallel/UnitContext.java to confirm the unauthenticated deserialization path and default listener behavior. Then locate existing unit/node server tests or connection-handling tests. Done means the unsafe pre-authentication path is mitigated and the resulting behavior is covered by regression testing.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, networking, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.