# DL4J ModelSerializer — Remote Code Execution on model load (CWE-502) `org.deeplearning4j.util.ModelSerializer.restoreMultiLayerNetwork(File)` / `restoreComputationGraph(File)` — the **standard DL4J model-load API** — read the `preprocessor.bin` entry of the model `.zip` and deserialize it with `new ObjectInputStream(...).readObject()`. The bytes come straight from the attacker-supplied model file, so **loading a malicious model executes arbitrary Java deserialization**, and with a gadget on the application classpath, arbitrary code. ## Files - `malicious-model.zip` — a DL4J model whose `preprocessor.bin` is a commons-collections gadget that runs `touch /tmp/DL4J_RCE_CANARY` - `LoadPoc.java` — the victim action (loads the model with the normal API) - `pom.xml` — DL4J 1.0.0-M2.1 + a representative classpath gadget (commons-collections 3.2.1) - `BuildMaliciousModel.java` — how the malicious model was crafted (transparency) ## Reproduce 1. `mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt` 2. `javac --release 17 -cp "$(cat cp.txt)" LoadPoc.java` 3. `rm -f /tmp/DL4J_RCE_CANARY` 4. `java -cp ".:$(cat cp.txt)" LoadPoc` 5. Observe: `/tmp/DL4J_RCE_CANARY` now exists → the command ran **during `restoreMultiLayerNetwork`** = RCE. ## Notes - The deserialization SINK is unconditional: a model whose `preprocessor.bin` is *any* serialized object is deserialized on load (e.g. a plain `HashMap` is deserialized, then fails the `(DataSetPreProcessor)` cast). No valid config/coefficients are required to reach it. - DL4J's own default dep is `commons-collections4:4.1` (opt-in serialization guard), so a *bare* classpath has no usable gadget; RCE is realized with any unguarded gadget (older commons-collections, commons-beanutils, Spring, etc.) — ubiquitous in JVM/Spark/Hadoop ML deployments. ## Fix Do not use `ObjectInputStream` on untrusted model data. Replace with a safe serializer for `DataSetPreProcessor`, or wrap with a strict `ObjectInputFilter` allowlist.