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

  1. If the class is legitimately expected, remove it from the black list (stop calling refuse() with it).
  2. If the data is untrusted, this throw is correct protection — catch InvalidClassException and reject/audit the input.
  3. 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

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

Related errors


AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14). Data as JSON: /api/errors/e517a2a3eae8aefd. Report an issue: GitHub.