diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..1e42050 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2026-06-27 - [Insecure Deserialization in Utils.readSerializable] +**Vulnerability:** In `me.desair.tus.server.util.Utils.readSerializable`, the code previously used an unrestricted `ObjectInputStream` to deserialize objects from a file on disk. This is a potential risk for arbitrary code execution if an attacker is able to upload a maliciously crafted serialized object and have it processed by the server, as the internal state objects (like `UploadInfo`) are serialized and deserialized to disk. +**Learning:** Java object deserialization is inherently insecure when deserializing untrusted data. Using an unrestricted `ObjectInputStream` opens the application to a wide range of deserialization attacks, even if the stream source is a local file, if that file can be influenced by an attacker (e.g., via a file upload vulnerability). +**Prevention:** Always use `org.apache.commons.io.serialization.ValidatingObjectInputStream` (or implement a custom `ObjectInputStream` that overrides `resolveClass`) to explicitly allow-list accepted classes or packages. The allow-list should be as narrow as possible (e.g., `java.lang.*`, `java.util.*`, and specific application packages). diff --git a/src/main/java/me/desair/tus/server/util/Utils.java b/src/main/java/me/desair/tus/server/util/Utils.java index e72af32..2fa3541 100644 --- a/src/main/java/me/desair/tus/server/util/Utils.java +++ b/src/main/java/me/desair/tus/server/util/Utils.java @@ -8,7 +8,6 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.BufferedOutputStream; import java.io.IOException; -import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; @@ -22,6 +21,7 @@ import java.util.regex.Pattern; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.checksum.ChecksumAlgorithm; +import org.apache.commons.io.serialization.ValidatingObjectInputStream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,7 +83,9 @@ public static T readSerializable(Path path, Class clazz) throws IOExcepti // Lock will be released when the channel is closed if (lockFileShared(channel) != null) { - try (ObjectInputStream ois = new ObjectInputStream(Channels.newInputStream(channel))) { + try (ValidatingObjectInputStream ois = + new ValidatingObjectInputStream(Channels.newInputStream(channel))) { + ois.accept("java.lang.*", "java.util.*", "me.desair.tus.server.*"); info = clazz.cast(ois.readObject()); } catch (ClassNotFoundException | java.io.EOFException