chinabugotech/hutool · critical · InvalidClassException
Unauthorized deserialization attempt by black list
Error message
Unauthorized deserialization attempt by black list
What it means
ValidateObjectInputStream overrides resolveClass to inspect each class during deserialization. If a class name is found in the black list (populated via refuse()), it throws InvalidClassException("Unauthorized deserialization attempt by black list"). This blocks known dangerous gadget classes used in Java deserialization attacks.
Source
Thrown at hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java:84
/**
* 只允许反序列化SerialObject class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
validateClassName(desc.getName());
return super.resolveClass(desc);
}
/**
* 验证反序列化的类是否合法
* @param className 类名
* @throws InvalidClassException 非法类
*/
private void validateClassName(String className) throws InvalidClassException {
// 黑名单
if(CollUtil.isNotEmpty(this.blackClassSet)){
if(this.blackClassSet.contains(className)){
throw new InvalidClassException("Unauthorized deserialization attempt by black list", className);
}
}
if(CollUtil.isEmpty(this.whiteClassSet) || this.whiteClassSet.contains(className)){
return;
}
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
}
View on GitHub (pinned to 8870454b2a)
Solutions
- If the class is legitimately expected, remove it from the black list (stop calling refuse() with it).
- If the data is untrusted, this throw is correct protection — catch InvalidClassException and reject/audit the input.
- Switch to an explicit white list via accept() for tighter, intent-based control.
Example fix
// before: needed class accidentally refused vois.refuse(MyDto.class); MyDto o = (MyDto) IoUtil.readObj(vois, MyDto.class); // InvalidClassException // after: only refuse real gadget classes vois.refise(com.example.dangerous.Gadget.class); vois.accept(MyDto.class); MyDto o = (MyDto) IoUtil.readObj(vois, MyDto.class);
Defensive patterns
Strategy: try-catch
Validate before calling
// Only refuse classes you never want to deserialize. vois.refuse(org.apache.commons.collections.functors.InvokerTransformer.class); vois.refise(MyDto.class); // <- remove this if MyDto is legitimate
Try / catch
try {
return IoUtil.readObj(vois, clazz);
} catch (java.io.InvalidClassException e) {
if (e.getMessage().contains("black list")) {
// blocked by blacklist: audit/reject the untrusted payload
throw new SecurityException("rejected blacklisted class: " + e.classname, e);
}
throw e;
} Prevention
- Keep the black list limited to real gadget classes; do not refuse types you need.
- Prefer a white list (accept()) for intent-based control over deserialization.
- Never deserialize untrusted streams without validation; treat black-list hits as attacks.
When it happens
Trigger: Deserializing an object stream whose class, or any referenced class, matches a name added to the black list via refuse().
Common situations: Receiving untrusted serialized data containing gadget classes (e.g. CommonsCollectionsInvoker); a class needed at runtime was accidentally added to the black list; a library upgrade introduced a class now present in a shared black list.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized deserialization attempt
- Zip bomb attack detected, invalid sizes: compressed {}, unco
- Read limit exceeded
AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14).
Data as JSON: /api/errors/e517a2a3eae8aefd.
Report an issue: GitHub.