diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..d4dba02 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-06-28 - Insecure Deserialization in UploadInfo parsing +**Vulnerability:** The application reads serialized UploadInfo files from disk using a standard `ObjectInputStream`. This allows an attacker to inject arbitrary serialized objects if they have the ability to write to the storage directory, potentially leading to Remote Code Execution (RCE) via insecure deserialization. +**Learning:** `ObjectInputStream` inherently deserializes any class that implements `Serializable` and is available on the classpath. Even when expecting a specific class like `UploadInfo`, the object stream will instantiate the malicious class before the cast occurs. +**Prevention:** Use `ValidatingObjectInputStream` from `commons-io` to enforce a strict allowlist of classes that can be deserialized. This ensures only expected classes (e.g., `UploadInfo` and its valid fields like `UploadType`, `UploadId`) are processed, rejecting unexpected classes before instantiation. 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..cd1dde2 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.**", "[*"); info = clazz.cast(ois.readObject()); } catch (ClassNotFoundException | java.io.EOFException