1

i'm using the example from this repo https://github.com/kojenov/serial/tree/master/3-4.%20upload, which is presenting a method for specifying a way to protect form unsafe deserialization in Java by defining a custom ObjectInputStream and overriding a protected method resolveClass in which we have to specify which classes are allowed for deserialisation. My problem is I added a LocalDate field to the Planet class and when I deserialize a serialized object I get this exception:

invalid class except unsupported class; java.time.Ser

I searched online and I could not find any other encounter with that problem, so I'm really confused. I tried with instead of the LocalDate to add a LocalDateTime, the same error happens again. As far as I found that class java.time.Ser is a protected class somewhere in the hierarchy of the classes in that package. The class LocalDate is serializable, so this should not happen. I know for sure that the problem is in the LocalDate, because if I make that field transient code works as intended. Am I missing something or it's just a bug of Java Object Serialization? By the way, the examples are originally from a talk given by Alexei Kojenov, his site is kojenov.com, but i couldn't find his email to ask him personally.

2
  • Maybe that example considered the java.time classes unsafe for deserialization? (Cannot guess the reason why, though. As you said, they are serializable.) Commented Apr 25, 2021 at 11:27
  • I don't think that the example has anything to do with the specific java.time package. If you look at the custom ObjectInputStream it basically checks if the object is of some classname, not checking any of it's member data. So again, what is happening here :D Commented Apr 25, 2021 at 11:33

1 Answer 1

1

Serialization is recursive progress, which means when you're serializing a complex object, firstly you need to serialize all its properties. The same thing happens with deserialization.

Planet object contains fields of type int, double and java.lang.String which are primitives and don't need special (de)serialization. LocalDate or LocalDateTime aren't primitives and they're serialized and then deserialized with SafeObjectInputStream.

Serialization hack

As it said in java.io.Serializable documentation, objects can modify their serialization behaviour and even delegate serialization to another class by defining method writeReplace.

JavaDoc cite:

Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private, protected and package-private access. Subclass access to this method follows java accessibility rules.

Both LocalDate and LocalDateTime utilizes this possibility and define writeReplace method.

As an example, java.time.LocalDate's implementation:

private Object writeReplace() {
    return new Ser(Ser.LOCAL_DATE_TYPE, this);
}

java.time.Ser is a package-private final class that is used as a delegate for java.time.* objects.

Hereby, when you're serializing java.time.LocalDate or java.time.LocalDateTime, actually java.time.Ser being serialized.

Custom deserializer

Previously we found out that java.time.LocalDate was serialized as java.time.Ser. Now, let's try to deserialize it with SafeObjectInputStream.

Before deserialization, resolveClass method is called:

@Override
protected Class<?> resolveClass(ObjectStreamClass input)
                                throws IOException, ClassNotFoundException
{
  if (!input.getName().equals(Planet.class.getName())) {
    throw new InvalidClassException("Unsupported class", input.getName());
  }
  return super.resolveClass(input);
}

It checks for class name to be equal to Planet.class.getName(), but java.time.Ser is not, that's why you're getting an exception.

Solution

To resolve this issue, you need to add java.time.Ser to the list of trusted classes. I would suggest modifying your SafeObjectInputStream next way:

public class SafeObjectInputStream extends ObjectInputStream {

  private final List<String> supportedClasses = List.of(Planet.class.getName(), "java.time.Ser");

  public SafeObjectInputStream(InputStream inputStream) throws IOException {
    super(inputStream);
  }

  @Override
  protected Class<?> resolveClass(ObjectStreamClass input)
                                  throws IOException, ClassNotFoundException
  {
    if (!supportedClasses.contains(input.getName())) {
      throw new InvalidClassException("Unsupported class ", input.getName());
    }
    return super.resolveClass(input);
  }
}

NOTE: List.of was introduced in Java 9. If your Java version is less than 9, you can replace it with Arrays.asList.

Sign up to request clarification or add additional context in comments.

4 Comments

Yeah, that works. I wanted to try the same, but I couldn't get around the way that java.time.Ser is not accessible to get it's name, but I guess your approach is kind of hack around that, to just define the class name as a string(ok, after all we are comparing strings in the if statement). I'm wondering whether or not the author of the solution Kojenov would approve this as a valid solution. Thank you for your great and succinct explanation of the delegation the serialization mechanism, because even though I found out the same stuff I couldn't understand it fully.
As I mentioned in answer, java.time.Ser is package private class, so you can't access it from your code (unless your code not placed in java.time package, which I don't recommend doing). String reference for a class is fine, since it allows to avoid hardcoding allowed classes and take out class names to external configuration (e.g. configuration file or JVM properties)
However this solution is not exact. Maybe because the way serialization is recursive and we only specify a whitelist for the top level class, if we want to have a member of type Set, then again due to the delegated serialization of the collection we have to specify (!) the exact implementation(let's say HashMap) and some nested delegated java.util.CollSer and again it's possible to do the attack Kojenov proposed with the infinite recursive Set. I found a way to use Set as member data but it has to be constructed by using the factory method Set.of(), which returns immutable collection.
The reason to create whitelist is deserialize only classes you trust. So, Set is just an interface, it can have a lot of implementations. Somebody can implement their own set which will contain malicious code. That's why you need to whitelist implementations only. Filtration, provided in your example is very basic, it can be modified to use not only ObjectStreamClass#getName for filtration but other fields. Also, name filtration can be also adjusted. Actually, I don't know who Kojenov is and I'm not sure I can provide a solution he will accept.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.