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.read → ois.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.
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 noObjectInputFilteror 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
SocketDatafor eachparallel/UnitWorker.java:45-48ObjectInputStreamwraps the raw socket stream, no filter setparallel/SocketData.java:65-68UnitWorker.runcallssocketData.read()at the top of its loopparallel/UnitWorker.java:57SocketData.read→ois.readUnshared()on the unfiltered streamparallel/SocketData.java:123parallel/UnitContext.java:180For each accepted connection,
UnitWorker.run()immediately callssocketData.read()(line 57) before any request is interpreted. That read reachesSocketData.read()→ois.readUnshared()(line 123), whereoisis a plainObjectInputStreamconstructed over the socket's input stream (lines 65-68). Because there is noObjectInputFilter, 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:The read happens before the request is parsed or the IP check is applied:
Default listener 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,
UnitWorkerimmediately callsSocketData.read(), which performsObjectInputStream.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.