[Security] Unauthenticated Java deserialization RCE
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.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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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