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 Unsafe Java Deserialization RCE in DL4J J7FileStatsStorage via the SQLite StatsStorage (.sqlite) file format

Summary

org.deeplearning4j.ui.model.storage.sqlite.J7FileStatsStorage reads Persistable objects out of an on-disk SQLite database by pulling a raw BLOB column (ObjectBytes) and passing it directly to an unfiltered ObjectInputStream.readObject(). Because the BLOB lives inside the .sqlite file the victim opens, its contents are fully attacker-controlled. Any Serializable gadget on the classpath executes its readObject() during a plain SELECT, yielding remote code execution the moment an application opens or re-opens a StatsStorage database.

  • Target (verified): Maven Central org.deeplearning4j:deeplearning4j-ui-model:1.0.0-M2.1 (latest released version). Class org/deeplearning4j/ui/model/storage/sqlite/J7FileStatsStorage.class confirmed present in the released jar.
  • Weakness: CWE-502 Deserialization of Untrusted Data β†’ RCE
  • Sink: J7FileStatsStorage.deserialize(byte[]) β€” J7FileStatsStorage.java:132-138
  • Reachable from public API: constructor new J7FileStatsStorage(File) + getStaticInfo / getAllStaticInfos / getUpdate / getUpdates / getStorageMetaData

Root cause

The private deserialize helper (from the released -sources.jar):

// J7FileStatsStorage.java:132-138
private static <T> T deserialize(byte[] bytes) {
    try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
        return (T) ois.readObject();
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

There is no ObjectInputFilter, no allow-list, no class validation. The bytes come straight from a BLOB column read out of the SQLite file:

private <T> T queryAndGet(String sql, int columnIndex) {
    ...
    ResultSet rs = statement.executeQuery(sql);
    ...
    byte[] bytes = rs.getBytes(columnIndex);
    return deserialize(bytes);   // attacker-controlled bytes -> readObject()
}

queryAndGet(...) -> deserialize(...) is invoked by every StatsStorage read accessor (getStaticInfo, getAllStaticInfos, getUpdate, getUpdates, getStorageMetaData). The (T) cast happens after readObject() returns, so the ClassCastException that a non-Persistable gadget triggers is post-execution β€” the gadget's readObject() has already run by then. The constructor new J7FileStatsStorage(File) opens any attacker-supplied file via DriverManager.getConnection("jdbc:sqlite:..."), so no additional trust boundary protects the deserialization.

Attack surface

Any application that opens/loads a DL4J StatsStorage .sqlite file with J7FileStatsStorage is exploitable:

  • Re-opening a previously-saved training-UI stats database.
  • A shared, downloaded, or uploaded stats artifact fed to J7FileStatsStorage.
  • Any remote/untrusted .sqlite passed to the constructor.

No gadget beyond what the victim classpath already provides is required; the PoC's self-contained Evil demonstrates the readObject() execution sink directly.

PoC β€” verified by ACTUAL EXECUTION

Dependencies (all released Maven Central artifacts): deeplearning4j-ui-model:1.0.0-M2.1, deeplearning4j-core:1.0.0-M2.1 (Persistable/StatsStorage interfaces), nd4j-api:1.0.0-M2.1, nd4j-common:1.0.0-M2.1, org.xerial:sqlite-jdbc:3.36.0.3.

Harness files (poc/):

  • Evil.java β€” Serializable gadget whose readObject() runs Runtime.exec(id) and drops a marker file.
  • MakeStatsDB.java β€” builds a malicious .sqlite with the exact J7FileStatsStorage schema (CREATE TABLE StaticInfo (... ObjectBytes BLOB ...)) and inserts a java-serialized Evil into the ObjectBytes column at SessionID='s', TypeID='t', WorkerID='w'. A benign mode inserts a serialized String (negative control).
  • Victim.java β€” uses ONLY the public API: new J7FileStatsStorage(new File(path)) then getStaticInfo("s","t","w").

Captured evidence (verbatim)

=== RUN VICTIM ON MALICIOUS ===
[Victim] new J7FileStatsStorage(new File("malicious-stats.sqlite"))
[Victim] sessions=[s]
[Victim] calling getStaticInfo("s","t","w")
[Evil.readObject] COMMAND EXECUTED -> /home/kali/hunt-workspace/dl4j-statsstorage-audit/poc/PWNED_statsstorage_16473214206027.txt
Exception in thread "main" java.lang.ClassCastException: class Evil cannot be cast to class org.deeplearning4j.core.storage.Persistable ...
    at org.deeplearning4j.ui.model.storage.sqlite.J7FileStatsStorage.getStaticInfo(J7FileStatsStorage.java:436)
    at Victim.main(Victim.java:14)
--- marker (PWNED_statsstorage_16473214206027.txt) ---
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)
dl4j-J7FileStatsStorage-deserialize-rce

=== NEGATIVE CONTROL: benign DB ===
[Victim] new J7FileStatsStorage(new File("benign-stats.sqlite"))
[Victim] sessions=[s]
[Victim] calling getStaticInfo("s","t","w")
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to ... Persistable
    at org.deeplearning4j.ui.model.storage.sqlite.J7FileStatsStorage.getStaticInfo(J7FileStatsStorage.java:436)
--- markers: NO MARKER DROPPED (control clean) ---

Malicious run: Evil.readObject fires β†’ id executes β†’ marker written; ClassCastException (Evil β†’ Persistable) thrown after execution at J7FileStatsStorage.java:436.

Negative control: benign String deserializes, no command runs, no marker dropped β€” only the same post-cast ClassCastException (String β†’ Persistable). Clean. This proves the RCE is attributable to the gadget's readObject(), not to any side effect of opening the DB or the cast itself.

Suggested fix

Install a strict ObjectInputFilter / allow-list on the ObjectInputStream (restricting to the expected Persistable/StorageMetaData implementation types), or replace Java serialization with a schema-bound format. Never call readObject() on bytes read from a file whose provenance is not trusted.

Dedup / prior-work note

This is a distinct sink from other DL4J deserialization issues: it is not ModelSerializer, not the SameDiff/FlatBuffers path, not the WordVectors loader, and not the Arbiter result reference. It is specifically the SQLite StatsStorage BLOB read in deeplearning4j-ui-model's J7FileStatsStorage.deserialize(byte[]), reachable through the public StatsStorage read API. No public CVE was found for this specific class/sink at the time of writing.

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