frohoff/ysoserial · warning · IOException
Not allowed to read object
Error message
Not allowed to read object
What it means
JRMPListener installs a strict ObjectInputStream whose resolveClass only permits a small whitelist (ObjID[], ObjID, UID) so unmarshalling attacker-controlled data cannot instantiate arbitrary classes. Any other class name in the stream causes IOException("Not allowed to read object"). This is a deliberate deserialization-gadget defense, not a bug.
Solutions
- Confirm the client only sends the minimal JRMP handshake objects (ObjID, UID, primitive fields)
- If you control the payload, strip extra serialized objects from the stream
- If a new required type is legitimate, extend the whitelist in resolveClass deliberately (understand the deserialization risk)
- Do not whitelist arbitrary classes — that reintroduces the gadget vulnerability
Example fix
// before
} else if ( "java.rmi.server.UID".equals(desc.getName())) {
return UID.class;
}
throw new IOException("Not allowed to read object");
// after
} else if ( "java.rmi.server.UID".equals(desc.getName())) {
return UID.class;
} else if ( "java.lang.String".equals(desc.getName())) { // only if genuinely needed
return String.class;
}
throw new IOException("Not allowed to read object"); Defensive patterns
Strategy: validation
Validate before calling
// client side: ensure the stream only contains whitelisted types // allowed: ObjID, UID, primitive fields; strip any other Serializable objects first
Type guard
static boolean isWhitelisted(Class<?> c) {
return c == ObjID.class || c == ObjID[].class || c == UID.class;
} Try / catch
try {
sendHandshake(stream);
} catch (IOException e) {
if (e.getMessage().equals("Not allowed to read object")) {
// remove non-whitelisted objects from the outgoing stream
}
} Prevention
- Only send the minimal JRMP handshake objects the listener whitelists
- Never extend the whitelist casually — it is a deserialization defense
- Test the handshake against the listener before integrating into tooling
When it happens
Trigger: During doCall/doMessage, the ObjectInputStream encounters a class descriptor whose name is not exactly one of 'java.rmi.server.ObjID[]', 'java.rmi.server.ObjID', or 'java.rmi.server.UID' — e.g. a client sends a serialized object graph with extra classes.
Common situations: A real RMI client (or exploit payload) sends serialized objects beyond the minimal handshake the listener expects; a client's stream includes string or other object types the whitelist does not cover.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- unable to read objID
- unknown transport op
- Failed to get object id
- Failed to get object id, separator
- mismatched columns
AI-assisted analysis of frohoff/ysoserial@218bcffcaa (2026-09-12).
Data as JSON: /api/errors/37c4c76122a41915.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/ysoserial/exploit/JRMPListener.java:256
}
s.close();
}
private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
ObjectInputStream ois = new ObjectInputStream(in) {
@Override
protected Class<?> resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException {
if ( "[Ljava.rmi.server.ObjID;".equals(desc.getName())) {
return ObjID[].class;
} else if ("java.rmi.server.ObjID".equals(desc.getName())) {
return ObjID.class;
} else if ( "java.rmi.server.UID".equals(desc.getName())) {
return UID.class;
}
throw new IOException("Not allowed to read object");
}
};
ObjID read;
try {
read = ObjID.read(ois);
}
catch ( java.io.IOException e ) {
throw new MarshalException("unable to read objID", e);
}
if ( read.hashCode() == 2 ) {
ois.readInt(); // method
ois.readLong(); // hash
System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject()));
}
View on GitHub (pinned to 218bcffcaa)