uuuyuqi opened a new issue, #16287:
URL: https://github.com/apache/dubbo/issues/16287

   ### Environment
   
   - Dubbo version: 3.2.15 (also affects 3.3.x — the relevant code path is in 
hessian-lite)
   - hessian-lite version: 3.2.13
   - JDK: 17
   
   ### Description
   
   `Hessian2SerializerFactory` hooks into `getDefaultSerializer()` to enforce 
the `checkSerializable` security check. However, in hessian-lite's 
`SerializerFactory.getSerializer(Class)`, classes that define a 
`writeReplace()` method are handled **before** `getDefaultSerializer()` is 
reached — they get a `JavaSerializer` directly and skip the 
`getDefaultSerializer` override entirely.
   
   This means:
   1. A class **without** `Serializable` and **without** `writeReplace()` → 
correctly rejected by `checkSerializable`
   2. A class **without** `Serializable` but **with** `writeReplace()` → 
**bypasses the check**, serialization succeeds
   
   The relevant priority chain in `SerializerFactory.getSerializer(Class)`:
   
   ```
   1. _staticSerializerMap (built-in types)
   2. Custom serializer factories
   3. EnumSet check
   4. writeReplace check → new JavaSerializer(cl, loader); goto cache   ← 
short-circuits here
   5. Map / Collection / Iterator / Enum / ...
   6. getDefaultSerializer(cl)   ← checkSerializable is here, never reached for 
step 4
   ```
   
   ### How to reproduce
   
   Define two DTOs:
   
   ```java
   // No Serializable, no writeReplace
   public class NormalDto {
       private String name;
       // getter/setter
   }
   
   // No Serializable, HAS writeReplace
   public class WriteReplaceDto {
       private String name;
       // getter/setter
   
       private Object writeReplace() {
           return this;
       }
   }
   ```
   
   Define a Dubbo service that accepts both as parameters:
   
   ```java
   public interface TestService {
       String sendNormal(NormalDto dto);
       String sendWriteReplace(WriteReplaceDto dto);
   }
   ```
   
   Call both methods from a consumer:
   
   - `sendNormal(new NormalDto("test"))` → **Fails on consumer side** with 
`"Serialized class ... has not implement Serializable interface"`
   - `sendWriteReplace(new WriteReplaceDto("test"))` → **Consumer serializes 
successfully** and sends the message (provider-side deserialization catches it 
via a different path — `loadSerializedClass`)
   
   ### Observed behavior
   
   The security check is **inconsistent** between serialization and 
deserialization:
   
   | Side | Path | writeReplace class caught? |
   |------|------|---------------------------|
   | Serialization (sender) | `getSerializer()` → writeReplace detected → 
`JavaSerializer` directly → **bypasses** `getDefaultSerializer` / 
`checkSerializable` | **No** |
   | Deserialization (receiver) | `loadSerializedClass()` → 
`DefaultSerializeClassChecker.loadClass()` | **Yes** |
   
   ### Expected behavior
   
   The `checkSerializable` enforcement should be consistent regardless of 
whether a class has `writeReplace()`. A class that doesn't implement 
`Serializable` should be rejected on both the serialization and deserialization 
sides.
   
   ### Suggested fix
   
   Override `getSerializer(Class)` in `Hessian2SerializerFactory` to ensure the 
security check runs before delegating to `super.getSerializer()`, so that 
writeReplace classes are also covered. Alternatively, the check could be moved 
into `SerializerFactory.getSerializer(Class)` in hessian-lite itself, before 
the writeReplace branch.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to