CVE-2017-20189 (published 2024-01-22T06:15Z)

In Clojure before 1.9.0, classes can be used to construct a serialized object that executes arbitrary code upon deserialization. This is relevant if a server deserializes untrusted objects.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/clojure/clojure
commit_details: repo_url: https://github.com/clojure/clojure
author: Chouser
author_date: 2017-07-17 23:04:41-04:00
msg: CLJ-2204 Disable serialization of proxy classes

Signed-off-by: Stuart Halloway <stu@cognitect.com>
repository: repo_url: https://github.com/clojure/clojure
repo_name: clojure/clojure
description: The Clojure programming language
repo_language: Java
owner: clojure
file_changes: filename: core_proxy.clj
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@
 (import
  '(clojure.asm ClassWriter ClassVisitor Opcodes Type) 
  '(java.lang.reflect Modifier Constructor)
+ '(java.io Serializable NotSerializableException)
  '(clojure.asm.commons Method GeneratorAdapter)
  '(clojure.lang IProxy Reflector DynamicClassLoader IPersistentMap PersistentHashMap RT))
 
@@ -44,7 +45,8 @@
 
 (defn- generate-proxy [^Class super interfaces]
   (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS))
-        cname (.replace (proxy-name super interfaces) \. \/) ;(str "clojure/lang/" (gensym "Proxy__"))
+        pname (proxy-name super interfaces)
+        cname (.replace pname \. \/) ;(str "clojure/lang/" (gensym "Proxy__"))
         ctype (. Type (getObjectType cname))
         iname (fn [^Class c] (.. Type (getType c) (getInternalName)))
         fmap "__clojureFnMap"
@@ -148,6 +150,22 @@
             
             (. gen (returnValue))
             (. gen (endMethod)))))
+                                        ;disable serialization
+    (when (some #(isa? % Serializable) (cons super interfaces))
+      (let [m (. Method (getMethod "void writeObject(java.io.ObjectOutputStream)"))
+            gen (new GeneratorAdapter (. Opcodes ACC_PRIVATE) m nil nil cv)]
+        (. gen (visitCode))
+        (. gen (loadThis))
+        (. gen (loadArgs))
+        (. gen (throwException (totype NotSerializableException) pname))
+        (. gen (endMethod)))
+      (let [m (. Method (getMethod "void readObject(java.io.ObjectInputStream)"))
+            gen (new GeneratorAdapter (. Opcodes ACC_PRIVATE) m nil nil cv)]
+        (. gen (visitCode))
+        (. gen (loadThis))
+        (. gen (loadArgs))
+        (. gen (throwException (totype NotSerializableException) pname))
+        (. gen (endMethod))))
                                         ;add IProxy methods
     (let [m (. Method (getMethod "void __initClojureFnMappings(clojure.lang.IPersistentMap)"))
           gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]

programming_language: Haskell
filename: java_interop.clj
change_type: ModificationType.MODIFY
diff: @@ -10,7 +10,8 @@
 
 
 (ns clojure.test-clojure.java-interop
-  (:use clojure.test))
+  (:use clojure.test)
+  (:require [clojure.inspector]))
 
 ; http://clojure.org/java_interop
 ; http://clojure.org/compilation
@@ -171,6 +172,34 @@
         "chain chain chain")))
 
 
+;; serialized-proxy can be regenerated using a modified version of
+;; Clojure with the proxy serialization prohibition disabled and the
+;; following code:
+#_(let [baos (java.io.ByteArrayOutputStream.) ]
+    (with-open [baos baos]
+      (.writeObject (java.io.ObjectOutputStream. baos) (clojure.inspector/list-model nil)))
+    (println (apply str (for [c (String. (.toByteArray baos) "ISO-8859-1")]
+                          (if (<= 32 (int c) (int \z)) c (format "\\%03o" (int c)))))))
+(def serialized-proxy "\254\355\000\005sr\000Eclojure.inspector.proxy$javax.swing.table.AbstractTableModel$ff19274art\330\266_\010ME\002\000\001L\000\016__clojureFnMapt\000\035Lclojure/lang/IPersistentMap;xr\000$javax.swing.table.AbstractTableModelr\313\3538\256\001\377\276\002\000\001L\000\014listenerListt\000%Ljavax/swing/event/EventListenerList;xpsr\000#javax.swing.event.EventListenerList\2616\306\175\204\352\326D\003\000\000xppxsr\000\037clojure.lang.PersistentArrayMap\3437p\017\230\305\364\337\002\000\002L\000\005_metaq\000\176\000\001[\000\005arrayt\000\023[Ljava/lang/Object;xr\000\033clojure.lang.APersistentMap]\174/\003t r\173\002\000\002I\000\005_hashI\000\007_hasheqxp\000\000\000\000\000\000\000\000pur\000\023[Ljava.lang.Object;\220\316X\237\020s)l\002\000\000xp\000\000\000\006t\000\016getColumnCountsr\000%clojure.inspector$list_model$fn__8816H\252\320\325b\371!+\002\000\000xr\000\026clojure.lang.AFunction>\006p\234\236F\375\313\002\000\001L\000\021__methodImplCachet\000\036Lclojure/lang/MethodImplCache;xppt\000\013getRowCountsr\000%clojure.inspector$list_model$fn__8818-\037I\247\234/U\226\002\000\001L\000\005nrowst\000\022Ljava/lang/Object;xq\000\176\000\017ppt\000\012getValueAtsr\000%clojure.inspector$list_model$fn__8820\323\331\174ke\233\370\034\002\000\002L\000\011get_labelq\000\176\000\024L\000\011get_valueq\000\176\000\024xq\000\176\000\017ppp")
+
+(deftest test-proxy-non-serializable
+  (testing "That proxy classes refuse serialization and deserialization"
+    ;; Serializable listed directly in interface list:
+    (is (thrown? java.io.NotSerializableException
+                 (-> (java.io.ByteArrayOutputStream.)
+                     (java.io.ObjectOutputStream.)
+                     (.writeObject (proxy [Object java.io.Serializable] [])))))
+    ;; Serializable included via inheritence:
+    (is (thrown? java.io.NotSerializableException
+                 (-> (java.io.ByteArrayOutputStream.)
+                     (java.io.ObjectOutputStream.)
+                     (.writeObject (clojure.inspector/list-model nil)))))
+    ;; Deserialization also prohibited:
+    (is (thrown? java.io.NotSerializableException
+                 (-> serialized-proxy (.getBytes "ISO-8859-1")
+                     java.io.ByteArrayInputStream. java.io.ObjectInputStream.
+                     .readObject)))))
+
 (deftest test-bases
   (are [x y] (= x y)
       (bases java.lang.Math)

programming_language: Haskell