2012-07-06 19:12:59 +00:00
|
|
|
|
(ns taoensso.nippy
|
2016-04-07 05:49:26 +00:00
|
|
|
|
"High-performance serialization library for Clojure"
|
2015-09-17 03:55:09 +00:00
|
|
|
|
{:author "Peter Taoussanis (@ptaoussanis)"}
|
2016-10-28 03:25:46 +00:00
|
|
|
|
(:require
|
2020-08-25 20:07:33 +00:00
|
|
|
|
[clojure.string :as str]
|
2017-02-13 16:35:18 +00:00
|
|
|
|
[clojure.java.io :as jio]
|
2020-07-24 20:50:05 +00:00
|
|
|
|
[taoensso.encore :as enc]
|
2016-10-28 03:25:46 +00:00
|
|
|
|
[taoensso.nippy
|
|
|
|
|
|
[utils :as utils]
|
|
|
|
|
|
[compression :as compression]
|
|
|
|
|
|
[encryption :as encryption]])
|
|
|
|
|
|
|
|
|
|
|
|
(:import
|
|
|
|
|
|
[java.io ByteArrayInputStream ByteArrayOutputStream DataInputStream
|
|
|
|
|
|
DataOutputStream Serializable ObjectOutputStream ObjectInputStream
|
|
|
|
|
|
DataOutput DataInput]
|
2020-07-24 17:38:16 +00:00
|
|
|
|
[java.lang.reflect Method Field Constructor]
|
|
|
|
|
|
[java.net URI]
|
2016-10-28 03:25:46 +00:00
|
|
|
|
[java.util Date UUID]
|
|
|
|
|
|
[java.util.regex Pattern]
|
|
|
|
|
|
[clojure.lang Keyword Symbol BigInt Ratio
|
|
|
|
|
|
APersistentMap APersistentVector APersistentSet
|
|
|
|
|
|
IPersistentMap ; IPersistentVector IPersistentSet IPersistentList
|
|
|
|
|
|
PersistentQueue PersistentTreeMap PersistentTreeSet PersistentList
|
2020-07-24 17:38:16 +00:00
|
|
|
|
LazySeq IRecord ISeq IType]))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2015-09-29 07:30:25 +00:00
|
|
|
|
(if (vector? enc/encore-version)
|
2020-08-29 12:18:46 +00:00
|
|
|
|
(enc/assert-min-encore-version [2 126 2])
|
|
|
|
|
|
(enc/assert-min-encore-version 2.126))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(set! *unchecked-math* :warn-on-boxed)
|
|
|
|
|
|
(set! *unchecked-math* false)
|
|
|
|
|
|
(thaw (freeze stress-data)))
|
2015-02-18 10:22:37 +00:00
|
|
|
|
|
2016-10-28 03:35:21 +00:00
|
|
|
|
;;;; TODO
|
|
|
|
|
|
;; - Performance would benefit from ^:static support / direct linking / etc.
|
2016-07-16 12:09:38 +00:00
|
|
|
|
|
2014-07-04 13:05:18 +00:00
|
|
|
|
;;;; Nippy data format
|
2015-09-28 09:25:43 +00:00
|
|
|
|
;; * 4-byte header (Nippy v2.x+) (may be disabled but incl. by default) [1]
|
2016-04-07 05:49:26 +00:00
|
|
|
|
;; { * 1-byte type id
|
|
|
|
|
|
;; * Arb-length payload determined by freezer for this type [2] } ...
|
2014-07-04 13:05:18 +00:00
|
|
|
|
;;
|
2015-09-29 04:33:49 +00:00
|
|
|
|
;; [1] Inclusion of header is *strongly* recommended. Purpose:
|
2015-09-28 09:25:43 +00:00
|
|
|
|
;; * Sanity check (confirm that data appears to be Nippy data)
|
|
|
|
|
|
;; * Nippy version check (=> supports changes to data schema over time)
|
|
|
|
|
|
;; * Supports :auto thaw compressor, encryptor
|
2015-04-19 03:48:01 +00:00
|
|
|
|
;; * Supports :auto freeze compressor (since this depends on :auto thaw
|
2015-09-28 09:25:43 +00:00
|
|
|
|
;; compressor)
|
2014-01-21 07:21:56 +00:00
|
|
|
|
;;
|
2016-07-16 12:09:38 +00:00
|
|
|
|
;; [2] See `IFreezable1` protocol for type-specific payload formats,
|
2016-04-07 05:49:26 +00:00
|
|
|
|
;; `thaw-from-in!` for reference type-specific thaw implementations
|
|
|
|
|
|
;;
|
2016-04-14 06:07:59 +00:00
|
|
|
|
(def ^:private ^:const charset "UTF-8")
|
|
|
|
|
|
(def ^:private head-sig "First 3 bytes of Nippy header" (.getBytes "NPY" charset))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(def ^:private ^:const head-version "Current Nippy header format version" 1)
|
|
|
|
|
|
(def ^:private ^:const head-meta
|
|
|
|
|
|
"Final byte of 4-byte Nippy header stores version-dependent metadata"
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
|
|
|
|
|
;; Currently
|
|
|
|
|
|
;; - 5 compressors, #{nil :snappy :lz4 :lzma2 :else}
|
|
|
|
|
|
;; - 4 encryptors, #{nil :aes128-cbc-sha512 :aes128-gcm-sha512 :else}
|
|
|
|
|
|
|
2014-04-05 11:30:28 +00:00
|
|
|
|
{(byte 0) {:version 1 :compressor-id nil :encryptor-id nil}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(byte 2) {:version 1 :compressor-id nil :encryptor-id :aes128-cbc-sha512}
|
|
|
|
|
|
(byte 14) {:version 1 :compressor-id nil :encryptor-id :aes128-gcm-sha512}
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(byte 4) {:version 1 :compressor-id nil :encryptor-id :else}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(byte 1) {:version 1 :compressor-id :snappy :encryptor-id nil}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(byte 3) {:version 1 :compressor-id :snappy :encryptor-id :aes128-cbc-sha512}
|
|
|
|
|
|
(byte 15) {:version 1 :compressor-id :snappy :encryptor-id :aes128-gcm-sha512}
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(byte 7) {:version 1 :compressor-id :snappy :encryptor-id :else}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
2015-04-19 03:48:01 +00:00
|
|
|
|
;;; :lz4 used for both lz4 and lz4hc compressor (the two are compatible)
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(byte 8) {:version 1 :compressor-id :lz4 :encryptor-id nil}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(byte 9) {:version 1 :compressor-id :lz4 :encryptor-id :aes128-cbc-sha512}
|
|
|
|
|
|
(byte 16) {:version 1 :compressor-id :lz4 :encryptor-id :aes128-gcm-sha512}
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(byte 10) {:version 1 :compressor-id :lz4 :encryptor-id :else}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(byte 11) {:version 1 :compressor-id :lzma2 :encryptor-id nil}
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(byte 12) {:version 1 :compressor-id :lzma2 :encryptor-id :aes128-cbc-sha512}
|
|
|
|
|
|
(byte 17) {:version 1 :compressor-id :lzma2 :encryptor-id :aes128-gcm-sha512}
|
|
|
|
|
|
(byte 13) {:version 1 :compressor-id :lzma2 :encryptor-id :else}
|
|
|
|
|
|
|
|
|
|
|
|
(byte 5) {:version 1 :compressor-id :else :encryptor-id nil}
|
|
|
|
|
|
(byte 18) {:version 1 :compressor-id :else :encryptor-id :aes128-cbc-sha512}
|
|
|
|
|
|
(byte 19) {:version 1 :compressor-id :else :encryptor-id :aes128-gcm-sha512}
|
|
|
|
|
|
(byte 6) {:version 1 :compressor-id :else :encryptor-id :else}})
|
|
|
|
|
|
|
|
|
|
|
|
(comment (count (sort (keys head-meta))))
|
2013-06-12 18:14:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defmacro ^:private when-debug [& body] (when #_true false `(do ~@body)))
|
|
|
|
|
|
|
|
|
|
|
|
(def ^:private type-ids
|
|
|
|
|
|
"{<byte-id> <type-name-kw>}, ~random ordinal ids for historical reasons.
|
|
|
|
|
|
-ive ids reserved for custom (user-defined) types.
|
|
|
|
|
|
|
|
|
|
|
|
Size-optimized suffixes:
|
|
|
|
|
|
-0 (empty => 0-sized)
|
|
|
|
|
|
-sm (small => byte-sized)
|
|
|
|
|
|
-md (medium => short-sized)
|
|
|
|
|
|
-lg (large => int-sized) ; Default when no suffix
|
|
|
|
|
|
-xl (extra large => long-sized)"
|
|
|
|
|
|
|
|
|
|
|
|
{82 :prefixed-custom
|
|
|
|
|
|
|
|
|
|
|
|
47 :reader-sm
|
|
|
|
|
|
51 :reader-md
|
|
|
|
|
|
52 :reader-lg
|
2016-07-26 05:20:40 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
75 :serializable-q-sm ; Quarantined
|
|
|
|
|
|
76 :serializable-q-md ; ''
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
48 :record-sm
|
|
|
|
|
|
49 :record-md
|
2020-07-24 20:50:05 +00:00
|
|
|
|
80 :record-lg ; Unrealistic, future removal candidate
|
2016-07-26 05:20:40 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
81 :type
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
3 :nil
|
|
|
|
|
|
8 :true
|
|
|
|
|
|
9 :false
|
|
|
|
|
|
10 :char
|
|
|
|
|
|
|
|
|
|
|
|
34 :str-0
|
|
|
|
|
|
105 :str-sm
|
|
|
|
|
|
16 :str-md
|
|
|
|
|
|
13 :str-lg
|
|
|
|
|
|
|
|
|
|
|
|
106 :kw-sm
|
2020-07-24 20:50:05 +00:00
|
|
|
|
77 :kw-md
|
|
|
|
|
|
14 :kw-lg ; Unrealistic, future removal candidate
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
56 :sym-sm
|
2020-07-24 20:50:05 +00:00
|
|
|
|
78 :sym-md
|
|
|
|
|
|
57 :sym-lg ; Unrealistic, future removal candidate
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
58 :regex
|
2020-07-24 17:38:16 +00:00
|
|
|
|
71 :uri
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
53 :bytes-0
|
|
|
|
|
|
7 :bytes-sm
|
|
|
|
|
|
15 :bytes-md
|
|
|
|
|
|
2 :bytes-lg
|
|
|
|
|
|
|
|
|
|
|
|
17 :vec-0
|
|
|
|
|
|
113 :vec-2
|
|
|
|
|
|
114 :vec-3
|
|
|
|
|
|
110 :vec-sm
|
|
|
|
|
|
69 :vec-md
|
|
|
|
|
|
21 :vec-lg
|
|
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
115 :objects-lg ; TODO Could include md, sm, 0 later if there's demand
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
18 :set-0
|
|
|
|
|
|
111 :set-sm
|
|
|
|
|
|
32 :set-md
|
|
|
|
|
|
23 :set-lg
|
|
|
|
|
|
|
|
|
|
|
|
19 :map-0
|
|
|
|
|
|
112 :map-sm
|
|
|
|
|
|
33 :map-md
|
|
|
|
|
|
30 :map-lg
|
|
|
|
|
|
|
|
|
|
|
|
35 :list-0
|
|
|
|
|
|
36 :list-sm
|
|
|
|
|
|
54 :list-md
|
|
|
|
|
|
20 :list-lg
|
|
|
|
|
|
|
|
|
|
|
|
37 :seq-0
|
|
|
|
|
|
38 :seq-sm
|
|
|
|
|
|
39 :seq-md
|
|
|
|
|
|
24 :seq-lg
|
|
|
|
|
|
|
|
|
|
|
|
28 :sorted-set
|
|
|
|
|
|
31 :sorted-map
|
|
|
|
|
|
26 :queue
|
|
|
|
|
|
25 :meta
|
|
|
|
|
|
|
|
|
|
|
|
40 :byte
|
|
|
|
|
|
41 :short
|
|
|
|
|
|
42 :integer
|
|
|
|
|
|
|
|
|
|
|
|
0 :long-zero
|
|
|
|
|
|
100 :long-sm
|
|
|
|
|
|
101 :long-md
|
|
|
|
|
|
102 :long-lg
|
|
|
|
|
|
43 :long-xl
|
|
|
|
|
|
|
|
|
|
|
|
44 :bigint
|
|
|
|
|
|
45 :biginteger
|
|
|
|
|
|
|
|
|
|
|
|
60 :float
|
|
|
|
|
|
55 :double-zero
|
|
|
|
|
|
61 :double
|
|
|
|
|
|
62 :bigdec
|
|
|
|
|
|
70 :ratio
|
|
|
|
|
|
|
|
|
|
|
|
90 :date
|
|
|
|
|
|
91 :uuid
|
|
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
59 :cached-0
|
|
|
|
|
|
63 :cached-1
|
|
|
|
|
|
64 :cached-2
|
|
|
|
|
|
65 :cached-3
|
|
|
|
|
|
66 :cached-4
|
2016-10-28 09:41:38 +00:00
|
|
|
|
72 :cached-5
|
|
|
|
|
|
73 :cached-6
|
|
|
|
|
|
74 :cached-7
|
2016-04-12 17:52:15 +00:00
|
|
|
|
67 :cached-sm
|
|
|
|
|
|
68 :cached-md
|
|
|
|
|
|
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
;;; DEPRECATED (only support thawing)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
5 :reader-lg2 ; == :reader-lg, used only for back-compatible thawing
|
|
|
|
|
|
1 :reader-depr1 ; v0.9.2 for +64k support
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
11 :str-depr1 ; ''
|
2020-07-24 20:50:05 +00:00
|
|
|
|
22 :map-depr1 ; v0.9.0 for more efficient thaw
|
|
|
|
|
|
12 :kw-depr1 ; v2.0.0-alpha5 for str consistecy
|
|
|
|
|
|
27 :map-depr2 ; v2.11 for count/2
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
29 :sorted-map-depr1 ; ''
|
2020-07-24 20:50:05 +00:00
|
|
|
|
4 :boolean-depr1 ; v2.12 for switch to true/false ids
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
46 :serializable-uq-sm ; Unquarantined
|
|
|
|
|
|
50 :serializable-uq-md ; ''
|
|
|
|
|
|
6 :serializable-uq-lg ; ''; unrealistic, future removal candidate
|
2016-04-07 05:49:26 +00:00
|
|
|
|
})
|
2013-10-31 06:15:22 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(comment
|
|
|
|
|
|
(defn- get-free-byte-ids [ids-map]
|
|
|
|
|
|
(reduce (fn [acc in] (if-not (ids-map in) (conj acc in) acc))
|
|
|
|
|
|
[] (range 0 Byte/MAX_VALUE)))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2016-10-28 09:41:38 +00:00
|
|
|
|
(count (get-free-byte-ids type-ids)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private defids []
|
|
|
|
|
|
`(do
|
|
|
|
|
|
~@(map
|
|
|
|
|
|
(fn [[id# name#]]
|
|
|
|
|
|
(let [name# (str "id-" (name name#))
|
|
|
|
|
|
sym# (with-meta (symbol name#)
|
|
|
|
|
|
{:const true :private true})]
|
|
|
|
|
|
`(def ~sym# (byte ~id#))))
|
|
|
|
|
|
type-ids)))
|
|
|
|
|
|
|
|
|
|
|
|
(comment (macroexpand '(defids)))
|
|
|
|
|
|
|
|
|
|
|
|
(defids)
|
|
|
|
|
|
|
|
|
|
|
|
;;;; Ns imports (for convenience of lib consumers)
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(do
|
|
|
|
|
|
(enc/defalias compress compression/compress)
|
|
|
|
|
|
(enc/defalias decompress compression/decompress)
|
|
|
|
|
|
(enc/defalias snappy-compressor compression/snappy-compressor)
|
|
|
|
|
|
(enc/defalias lzma2-compressor compression/lzma2-compressor)
|
|
|
|
|
|
(enc/defalias lz4-compressor compression/lz4-compressor)
|
|
|
|
|
|
(enc/defalias lz4hc-compressor compression/lz4hc-compressor)
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(enc/defalias encrypt encryption/encrypt)
|
|
|
|
|
|
(enc/defalias decrypt encryption/decrypt)
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
|
|
|
|
|
(enc/defalias aes128-gcm-encryptor encryption/aes128-gcm-encryptor)
|
|
|
|
|
|
(enc/defalias aes128-cbc-encryptor encryption/aes128-cbc-encryptor)
|
|
|
|
|
|
(enc/defalias aes128-encryptor encryption/aes128-gcm-encryptor) ; Default
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(enc/defalias freezable? utils/freezable?))
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
;;;; Dynamic config
|
|
|
|
|
|
;; See also `nippy.tools` ns for further dynamic config support
|
|
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
;; For back compatibility (nb Timbre's Carmine appender)
|
|
|
|
|
|
(enc/defonce ^:dynamic *final-freeze-fallback* "DEPRECATED: prefer `*freeze-fallback`." nil)
|
|
|
|
|
|
(enc/defonce ^:dynamic *freeze-fallback* "(fn [data-output x])->freeze, nil => default" nil)
|
2016-07-16 12:09:38 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(enc/defonce ^:dynamic *custom-readers* "{<hash-or-byte-id> (fn [data-input])->read}" nil)
|
2016-07-24 08:41:31 +00:00
|
|
|
|
(enc/defonce ^:dynamic *auto-freeze-compressor*
|
2016-04-07 05:49:26 +00:00
|
|
|
|
"(fn [byte-array])->compressor used by `(freeze <x> {:compressor :auto}),
|
|
|
|
|
|
nil => default"
|
|
|
|
|
|
nil)
|
|
|
|
|
|
|
2020-07-25 08:15:27 +00:00
|
|
|
|
(enc/defonce ^:dynamic *incl-metadata?* "Include metadata when freezing/thawing?" true)
|
|
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
;;;; Java Serializable config
|
|
|
|
|
|
;; Unfortunately quite a bit of complexity to do this safely
|
|
|
|
|
|
|
|
|
|
|
|
(def default-freeze-serializable-allowlist
|
|
|
|
|
|
"Allows *any* class-name to be frozen using Java's Serializable interface.
|
|
|
|
|
|
This is generally safe since RCE risk is present only when thawing.
|
|
|
|
|
|
See also `*freeze-serializable-allowlist*`."
|
|
|
|
|
|
#{"*"})
|
|
|
|
|
|
|
|
|
|
|
|
(def default-thaw-serializable-allowlist
|
|
|
|
|
|
"A set of common safe class-names to allow to be frozen using Java's
|
|
|
|
|
|
Serializable interface. PRs welcome for additions.
|
|
|
|
|
|
See also `*thaw-serializable-allowlist*`."
|
2020-07-25 09:07:12 +00:00
|
|
|
|
#{"[I" "[F" "[Z" "[B" "[C" "[D" "[S" "[J"
|
|
|
|
|
|
|
|
|
|
|
|
"java.lang.Throwable"
|
|
|
|
|
|
"java.lang.Exception"
|
|
|
|
|
|
"java.lang.RuntimeException"
|
|
|
|
|
|
"java.lang.ArithmeticException"
|
|
|
|
|
|
"java.lang.IllegalArgumentException"
|
|
|
|
|
|
"java.lang.NullPointerException"
|
|
|
|
|
|
"java.lang.IndexOutOfBoundsException"
|
|
|
|
|
|
|
|
|
|
|
|
"java.net.URI"
|
|
|
|
|
|
"java.util.UUID"
|
|
|
|
|
|
"java.util.Date"
|
|
|
|
|
|
#_"java.time.*" ; Safe?
|
|
|
|
|
|
"clojure.lang.ExceptionInfo"
|
|
|
|
|
|
"clojure.lang.ArityException"})
|
2020-07-25 07:38:26 +00:00
|
|
|
|
|
2020-08-25 20:07:33 +00:00
|
|
|
|
(defn- split-class-names>set [s] (when (string? s) (if (= s "") #{} (set (mapv str/trim (str/split s #"[,:]"))))))
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(split-class-names>set "")
|
|
|
|
|
|
(split-class-names>set "foo, bar:baz"))
|
|
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(comment (.getName (.getSuperclass (.getClass (java.util.concurrent.TimeoutException.)))))
|
|
|
|
|
|
|
|
|
|
|
|
(let [ids
|
|
|
|
|
|
{:legacy {:base {:prop "taoensso.nippy.serializable-whitelist-base" :env "TAOENSSO_NIPPY_SERIALIZABLE_WHITELIST_BASE"}
|
|
|
|
|
|
:add {:prop "taoensso.nippy.serializable-whitelist-add" :env "TAOENSSO_NIPPY_SERIALIZABLE_WHITELIST_ADD"}}
|
|
|
|
|
|
:freeze {:base {:prop "taoensso.nippy.freeze-serializable-allowlist-base" :env "TAOENSSO_NIPPY_FREEZE_SERIALIZABLE_ALLOWLIST_BASE"}
|
|
|
|
|
|
:add {:prop "taoensso.nippy.freeze-serializable-allowlist-add" :env "TAOENSSO_NIPPY_FREEZE_SERIALIZABLE_ALLOWLIST_ADD"}}
|
|
|
|
|
|
:thaw {:base {:prop "taoensso.nippy.thaw-serializable-allowlist-base" :env "TAOENSSO_NIPPY_THAW_SERIALIZABLE_ALLOWLIST_BASE"}
|
|
|
|
|
|
:add {:prop "taoensso.nippy.thaw-serializable-allowlist-add" :env "TAOENSSO_NIPPY_THAW_SERIALIZABLE_ALLOWLIST_ADD"}}}]
|
|
|
|
|
|
|
|
|
|
|
|
(defn- init-allowlist [action default]
|
|
|
|
|
|
(let [allowlist-base
|
|
|
|
|
|
(or
|
|
|
|
|
|
(when-let [s (or
|
|
|
|
|
|
(enc/get-sys-val (get-in ids [action :base :prop]) (get-in ids [action :base :env]))
|
|
|
|
|
|
(enc/get-sys-val (get-in ids [:legacy :base :prop]) (get-in ids [:legacy :base :env])))]
|
|
|
|
|
|
(split-class-names>set s))
|
|
|
|
|
|
default)
|
|
|
|
|
|
|
|
|
|
|
|
allowlist-add
|
|
|
|
|
|
(when-let [s (or
|
|
|
|
|
|
(enc/get-sys-val (get-in ids [action :add :prop]) (get-in ids [action :add :env]))
|
|
|
|
|
|
(enc/get-sys-val (get-in ids [:legacy :add :prop]) (get-in ids [:legacy :add :env])))]
|
|
|
|
|
|
(split-class-names>set s))]
|
|
|
|
|
|
|
|
|
|
|
|
(if (and allowlist-base allowlist-add)
|
|
|
|
|
|
(into (enc/have set? allowlist-base) allowlist-add)
|
|
|
|
|
|
(do allowlist-base)))))
|
|
|
|
|
|
|
|
|
|
|
|
(let [doc
|
|
|
|
|
|
"Used when attempting to <freeze/thaw> an object that:
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
- Does not implement Nippy's Freezable protocol.
|
|
|
|
|
|
- Does implement Java's Serializable interface.
|
|
|
|
|
|
|
|
|
|
|
|
In this case, Java's Serializable interface will be permitted iff
|
2020-09-11 11:40:25 +00:00
|
|
|
|
`(<allowlist> <class-name>)` predicate call returns true.
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
This is a security measure to prevent possible Remote Code Execution
|
|
|
|
|
|
(RCE) when thawing malicious payloads. See [1] for details.
|
2020-08-25 20:07:33 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
If `freeze` encounters a disallowed Serialized class, it will throw.
|
|
|
|
|
|
If `thaw` encounters a disallowed Serialized class, it will:
|
2020-08-25 20:07:33 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
- Throw if it's not possible to safely quarantine the object
|
|
|
|
|
|
(object was frozen with Nippy < v2.15.0-final).
|
2020-08-25 20:07:33 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
- Otherwise it will return a safely quarantined object of form
|
|
|
|
|
|
`{:nippy/unthawable {:class-name <> :content <quarantined-ba>}}`.
|
|
|
|
|
|
- Quarantined objects may be manually unquarantined with
|
|
|
|
|
|
`read-quarantined-serializable-object-unsafe!`.
|
2020-07-25 07:38:26 +00:00
|
|
|
|
|
2020-09-11 11:40:25 +00:00
|
|
|
|
There are 2x allowlists:
|
|
|
|
|
|
- `*freeze-serializable-allowlist*` ; Checked when freezing
|
|
|
|
|
|
- `*thaw-serializable-allowlist*` ; Checked when thawing
|
2020-08-25 21:26:19 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Example values:
|
2020-09-11 11:40:25 +00:00
|
|
|
|
- `(fn allow-class? [class-name] true)` ; Arbitrary predicate fn
|
|
|
|
|
|
- `#{\"java.lang.Throwable\", \"clojure.lang.*\"}` ; Set of class-names
|
2020-08-25 21:26:19 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Note that class-names in sets may contain \"*\" wildcards.
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Default allowlist values are:
|
2020-09-11 11:40:25 +00:00
|
|
|
|
- default-freeze-serializable-allowlist ; `{\"*\"}` => allow any class
|
2020-09-10 16:42:25 +00:00
|
|
|
|
- default-thaw-serializable-allowlist ; A set of common safe classes
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Allowlist values may be overridden with `binding`, `alter-var-root`, or:
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
- `taoensso.nippy.<freeze/thaw>-serializable-allowlist-base` JVM property
|
|
|
|
|
|
- `taoensso.nippy.<freeze/thaw>-serializable-allowlist-add` JVM property
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
- `TAOENSSO_NIPPY_<FREEZE/THAW>_SERIALIZABLE_ALLOWLIST_BASE` env var
|
|
|
|
|
|
- `TAOENSSO_NIPPY_<FREEZE/THAW>_SERIALIZABLE_ALLOWLIST_ADD` env var
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
If present, these will be read as comma-separated lists of class names
|
|
|
|
|
|
and formed into sets. Each initial allowlist value will then be:
|
|
|
|
|
|
(into (or <?base> <default>) <?additions>).
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
I.e. you can use:
|
|
|
|
|
|
- The \"base\" property/var to replace Nippy's default allowlists.
|
|
|
|
|
|
- The \"add\" property/var to add to Nippy's default allowlists.
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
See also `taoensso.encore/compile-str-filter`, a util to help
|
|
|
|
|
|
easily build more advanced predicate functions.
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Upgrading from an older version of Nippy and unsure whether you've been
|
|
|
|
|
|
using Nippy's Serializable support? Here's a snippet to ALLOW and RECORD
|
|
|
|
|
|
any class requesting Nippy's Serializable fallback:
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-07-25 07:38:26 +00:00
|
|
|
|
;; Deref for set of all class names that made use of Nippy's Serializable support:
|
|
|
|
|
|
(defonce observed-serializables_ (atom #{}))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(let [f (fn allow-class? [class-name]
|
|
|
|
|
|
(swap! observed-serializables_ conj class-name) ; Record class name
|
|
|
|
|
|
true ; Allow any class
|
|
|
|
|
|
)]
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(alter-var-root #'*freeze-serializable-allowlist* (fn [_] f))
|
|
|
|
|
|
(alter-var-root #'*thaw-serializable-allowlist* (fn [_] f)))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Thanks to Timo Mihaljov (@solita-timo-mihaljov) for an excellent report
|
|
|
|
|
|
identifying this vulnerability.
|
2020-08-25 20:07:33 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
[1] https://github.com/ptaoussanis/nippy/issues/130"]
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(enc/defonce ^{:dynamic true :doc doc} *freeze-serializable-allowlist* (init-allowlist :freeze default-freeze-serializable-allowlist))
|
|
|
|
|
|
(enc/defonce ^{:dynamic true :doc doc} *thaw-serializable-allowlist* (init-allowlist :thaw default-thaw-serializable-allowlist)))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-11 11:40:25 +00:00
|
|
|
|
(comment
|
|
|
|
|
|
;; Deref for set of all class names that made use of Nippy's Serializable support:
|
|
|
|
|
|
(defonce observed-serializables_ (atom #{}))
|
|
|
|
|
|
|
|
|
|
|
|
(let [f (fn allow-class? [class-name]
|
|
|
|
|
|
(swap! observed-serializables_ conj class-name) ; Record class name
|
|
|
|
|
|
true ; Allow any class
|
|
|
|
|
|
)]
|
|
|
|
|
|
|
|
|
|
|
|
(alter-var-root #'*freeze-serializable-allowlist* (fn [_] f))
|
|
|
|
|
|
(alter-var-root #'*thaw-serializable-allowlist* (fn [_] f)))
|
|
|
|
|
|
|
|
|
|
|
|
(comment @observed-serializables_) ; Call/log after some time
|
|
|
|
|
|
(comment
|
|
|
|
|
|
;; If you're satisfied that the recorded classes are safe, you can merge them
|
|
|
|
|
|
;; into Nippy's default allowlist:
|
|
|
|
|
|
(alter-var-root #'thaw-serializable-allowlist*
|
|
|
|
|
|
(fn [_] (into default-thaw-serializable-allowlist observed-serializables_)))))
|
|
|
|
|
|
|
2020-08-28 12:03:47 +00:00
|
|
|
|
(let [fn? fn?
|
|
|
|
|
|
compile (enc/fmemoize (fn [x] (enc/compile-str-filter x)))
|
2020-09-10 16:42:25 +00:00
|
|
|
|
conform?* (fn [x cn] ((compile x) cn)) ; Uncached because input domain possibly infinite
|
2020-08-28 12:03:47 +00:00
|
|
|
|
conform?
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(fn [x cn]
|
2020-08-28 12:03:47 +00:00
|
|
|
|
(if (fn? x)
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(x cn) ; Intentionally uncached, can be handy
|
|
|
|
|
|
(conform?* x cn)))]
|
2020-08-28 12:03:47 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(defn- freeze-serializable-allowed? [class-name] (conform? *freeze-serializable-allowlist* class-name))
|
|
|
|
|
|
(defn- thaw-serializable-allowed? [class-name] (conform? *thaw-serializable-allowlist* class-name)))
|
2020-08-25 21:26:19 +00:00
|
|
|
|
|
|
|
|
|
|
(comment
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(enc/qb 1e6 (freeze-serializable-allowed? "foo")) ; 119.92
|
|
|
|
|
|
(binding [*freeze-serializable-allowlist* #{"foo.*" "bar"}]
|
|
|
|
|
|
(freeze-serializable-allowed? "foo.bar")))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2013-06-16 05:51:30 +00:00
|
|
|
|
;;;; Freezing
|
2013-10-23 18:28:58 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do
|
|
|
|
|
|
(defmacro write-id [out id] `(.writeByte ~out ~id))
|
|
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private sm-count? [n] `(<= ~n 127))
|
|
|
|
|
|
(defmacro ^:private md-count? [n] `(<= ~n 32767))
|
|
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private write-sm-count [out n] `(.writeByte ~out ~n))
|
|
|
|
|
|
(defmacro ^:private write-md-count [out n] `(.writeShort ~out ~n))
|
|
|
|
|
|
(defmacro ^:private write-lg-count [out n] `(.writeInt ~out ~n))
|
|
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private read-sm-count [in] `(.readByte ~in))
|
|
|
|
|
|
(defmacro ^:private read-md-count [in] `(.readShort ~in))
|
|
|
|
|
|
(defmacro ^:private read-lg-count [in] `(.readInt ~in)))
|
2014-01-23 07:30:56 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
; We extend `IFreezable1` to supported types:
|
|
|
|
|
|
(defprotocol IFreezable1 (-freeze-without-meta! [x data-output]))
|
|
|
|
|
|
(defprotocol IFreezable2 (-freeze-with-meta! [x data-output]))
|
|
|
|
|
|
(extend-protocol IFreezable2 ; Must be a separate protocol
|
|
|
|
|
|
clojure.lang.IMeta
|
|
|
|
|
|
(-freeze-with-meta! [x ^DataOutput data-output]
|
2020-07-25 08:15:27 +00:00
|
|
|
|
(let [m (when *incl-metadata?* (.meta x))]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(when m
|
|
|
|
|
|
(write-id data-output id-meta)
|
|
|
|
|
|
(-freeze-without-meta! m data-output)))
|
|
|
|
|
|
(-freeze-without-meta! x data-output))
|
|
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
nil (-freeze-with-meta! [x data-output] (-freeze-without-meta! x data-output))
|
|
|
|
|
|
Object (-freeze-with-meta! [x data-output] (-freeze-without-meta! x data-output)))
|
2016-07-16 12:09:38 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-bytes-sm [^DataOutput out ^bytes ba]
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(let [len (alength ba)]
|
2016-04-13 04:57:50 +00:00
|
|
|
|
;; (byte len)
|
|
|
|
|
|
(write-sm-count out len)
|
|
|
|
|
|
(.write out ba 0 len)))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-bytes-md [^DataOutput out ^bytes ba]
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(let [len (alength ba)]
|
2016-04-13 04:57:50 +00:00
|
|
|
|
;; (short len)
|
|
|
|
|
|
(write-md-count out len)
|
|
|
|
|
|
(.write out ba 0 len)))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-bytes-lg [^DataOutput out ^bytes ba]
|
|
|
|
|
|
(let [len (alength ba)]
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-lg-count out len)
|
|
|
|
|
|
(.write out ba 0 len)))
|
2015-09-29 13:10:09 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-bytes [^DataOutput out ^bytes ba]
|
|
|
|
|
|
(let [len (alength ba)]
|
|
|
|
|
|
(if (zero? len)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-bytes-0)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
|
|
|
|
|
(do (write-id out id-bytes-sm)
|
|
|
|
|
|
(write-sm-count out len))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-bytes-md)
|
|
|
|
|
|
(write-md-count out len))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-bytes-lg)
|
|
|
|
|
|
(write-lg-count out len)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(.write out ba 0 len)))))
|
|
|
|
|
|
|
|
|
|
|
|
(defn- write-biginteger [out ^BigInteger n] (write-bytes-lg out (.toByteArray n)))
|
|
|
|
|
|
|
2020-09-11 15:47:26 +00:00
|
|
|
|
(defn- write-str-sm [^DataOutput out ^String s] (write-bytes-sm out (.getBytes s charset)))
|
|
|
|
|
|
(defn- write-str-md [^DataOutput out ^String s] (write-bytes-md out (.getBytes s charset)))
|
|
|
|
|
|
(defn- write-str-lg [^DataOutput out ^String s] (write-bytes-lg out (.getBytes s charset)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-str [^DataOutput out ^String s]
|
|
|
|
|
|
(if (identical? s "")
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-str-0)
|
2016-04-14 06:07:59 +00:00
|
|
|
|
(let [ba (.getBytes s charset)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
len (alength ba)]
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
|
|
|
|
|
(do (write-id out id-str-sm)
|
|
|
|
|
|
(write-sm-count out len))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-str-md)
|
|
|
|
|
|
(write-md-count out len))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-str-lg)
|
|
|
|
|
|
(write-lg-count out len)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(.write out ba 0 len))))
|
|
|
|
|
|
|
|
|
|
|
|
(defn- write-kw [^DataOutput out kw]
|
|
|
|
|
|
(let [s (if-let [ns (namespace kw)] (str ns "/" (name kw)) (name kw))
|
2016-04-14 06:07:59 +00:00
|
|
|
|
ba (.getBytes s charset)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
len (alength ba)]
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
|
|
|
|
|
(do (write-id out id-kw-sm)
|
|
|
|
|
|
(write-sm-count out len))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-kw-md)
|
|
|
|
|
|
(write-lg-count out len))
|
|
|
|
|
|
|
|
|
|
|
|
;; :else ; Unrealistic
|
|
|
|
|
|
;; (do (write-id out id-kw-lg)
|
|
|
|
|
|
;; (write-lg-count out len))
|
|
|
|
|
|
|
|
|
|
|
|
:else (throw (ex-info "Keyword too long" {:full-name s})))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.write out ba 0 len)))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-sym [^DataOutput out s]
|
|
|
|
|
|
(let [s (if-let [ns (namespace s)] (str ns "/" (name s)) (name s))
|
2016-04-14 06:07:59 +00:00
|
|
|
|
ba (.getBytes s charset)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
len (alength ba)]
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
|
|
|
|
|
(do (write-id out id-sym-sm)
|
|
|
|
|
|
(write-sm-count out len))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-sym-md)
|
|
|
|
|
|
(write-lg-count out len))
|
|
|
|
|
|
|
|
|
|
|
|
;; :else ; Unrealistic
|
|
|
|
|
|
;; (do (write-id out id-sym-lg)
|
|
|
|
|
|
;; (write-lg-count out len))
|
|
|
|
|
|
|
|
|
|
|
|
:else (throw (ex-info "Symbol too long" {:full-name s})))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(.write out ba 0 len)))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-long [^DataOutput out ^long n]
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(zero? n)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-long-zero)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(> n 0)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(<= n 127 #_Byte/MAX_VALUE)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-sm)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeByte out n))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(<= n 32767 #_Short/MAX_VALUE)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-md)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeShort out n))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(<= n 2147483647 #_Integer/MAX_VALUE)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-lg)
|
|
|
|
|
|
(.writeInt out n))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-xl)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeLong out n)))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(>= n -128 #_Byte/MIN_VALUE)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-sm)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeByte out n))
|
2014-01-23 07:30:56 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(>= n -32768 #_Short/MIN_VALUE)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-md)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeShort out n))
|
|
|
|
|
|
|
|
|
|
|
|
(>= n -2147483648 #_Integer/MIN_VALUE)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-lg)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeInt out n))
|
|
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-long-xl)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeLong out n)))))
|
|
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private -run! [proc coll] `(do (reduce #(~proc %2) nil ~coll) nil))
|
|
|
|
|
|
(defmacro ^:private -run-kv! [proc m] `(do (reduce-kv #(~proc %2 %3) nil ~m) nil))
|
|
|
|
|
|
|
|
|
|
|
|
(defn- write-vec [^DataOutput out v]
|
2016-03-08 04:13:46 +00:00
|
|
|
|
(let [cnt (count v)]
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(if (zero? cnt)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-vec-0)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? cnt)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(== cnt 2) (write-id out id-vec-2)
|
|
|
|
|
|
(== cnt 3) (write-id out id-vec-3)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-vec-sm)
|
|
|
|
|
|
(write-sm-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? cnt)
|
|
|
|
|
|
(do (write-id out id-vec-md)
|
|
|
|
|
|
(write-md-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-vec-lg)
|
|
|
|
|
|
(write-lg-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-run! (fn [in] (-freeze-with-meta! in out)) v)))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defn- write-kvs
|
|
|
|
|
|
([^DataOutput out id-lg coll]
|
|
|
|
|
|
(let [cnt (count coll)]
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-lg)
|
|
|
|
|
|
(write-lg-count out cnt)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(-run-kv!
|
|
|
|
|
|
(fn [k v]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-freeze-with-meta! k out)
|
|
|
|
|
|
(-freeze-with-meta! v out))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
coll)))
|
|
|
|
|
|
|
|
|
|
|
|
([^DataOutput out id-empty id-sm id-md id-lg coll]
|
|
|
|
|
|
(let [cnt (count coll)]
|
|
|
|
|
|
(if (zero? cnt)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-empty)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? cnt)
|
|
|
|
|
|
(do (write-id out id-sm)
|
|
|
|
|
|
(write-sm-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? cnt)
|
|
|
|
|
|
(do (write-id out id-md)
|
|
|
|
|
|
(write-md-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-lg)
|
|
|
|
|
|
(write-lg-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(-run-kv!
|
|
|
|
|
|
(fn [k v]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-freeze-with-meta! k out)
|
|
|
|
|
|
(-freeze-with-meta! v out))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
coll))))))
|
|
|
|
|
|
|
|
|
|
|
|
(defn- write-counted-coll
|
|
|
|
|
|
([^DataOutput out id-lg coll]
|
|
|
|
|
|
(let [cnt (count coll)]
|
|
|
|
|
|
;; (assert (counted? coll))
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-lg)
|
|
|
|
|
|
(write-lg-count out cnt)
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-run! (fn [in] (-freeze-with-meta! in out)) coll)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
([^DataOutput out id-empty id-sm id-md id-lg coll]
|
|
|
|
|
|
(let [cnt (count coll)]
|
|
|
|
|
|
;; (assert (counted? coll))
|
|
|
|
|
|
(if (zero? cnt)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-empty)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? cnt)
|
|
|
|
|
|
(do (write-id out id-sm)
|
|
|
|
|
|
(write-sm-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? cnt)
|
|
|
|
|
|
(do (write-id out id-md)
|
|
|
|
|
|
(write-md-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-lg)
|
|
|
|
|
|
(write-lg-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-run! (fn [in] (-freeze-with-meta! in out)) coll))))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defn- write-uncounted-coll
|
|
|
|
|
|
([^DataOutput out id-lg coll]
|
|
|
|
|
|
;; (assert (not (counted? coll)))
|
|
|
|
|
|
(let [bas (ByteArrayOutputStream. 32)
|
|
|
|
|
|
sout (DataOutputStream. bas)
|
2016-07-16 12:09:38 +00:00
|
|
|
|
^long cnt (reduce (fn [^long cnt in] (-freeze-with-meta! in sout) (unchecked-inc cnt)) 0 coll)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
ba (.toByteArray bas)]
|
|
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-lg)
|
|
|
|
|
|
(write-lg-count out cnt)
|
|
|
|
|
|
(.write out ba)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
([^DataOutput out id-empty id-sm id-md id-lg coll]
|
|
|
|
|
|
(let [bas (ByteArrayOutputStream. 32)
|
|
|
|
|
|
sout (DataOutputStream. bas)
|
2016-07-16 12:09:38 +00:00
|
|
|
|
^long cnt (reduce (fn [^long cnt in] (-freeze-with-meta! in sout) (unchecked-inc cnt)) 0 coll)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
ba (.toByteArray bas)]
|
|
|
|
|
|
|
|
|
|
|
|
(if (zero? cnt)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-empty)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? cnt)
|
|
|
|
|
|
(do (write-id out id-sm)
|
|
|
|
|
|
(write-sm-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? cnt)
|
|
|
|
|
|
(do (write-id out id-md)
|
|
|
|
|
|
(write-md-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-lg)
|
|
|
|
|
|
(write-lg-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(.write out ba))))))
|
|
|
|
|
|
|
|
|
|
|
|
(defn- write-coll
|
|
|
|
|
|
([out id-lg coll]
|
|
|
|
|
|
(if (counted? coll)
|
|
|
|
|
|
(write-counted-coll out id-lg coll)
|
|
|
|
|
|
(write-uncounted-coll out id-lg coll)))
|
|
|
|
|
|
|
|
|
|
|
|
([out id-empty id-sm id-md id-lg coll]
|
|
|
|
|
|
(if (counted? coll)
|
|
|
|
|
|
(write-counted-coll out id-empty id-sm id-md id-lg coll)
|
|
|
|
|
|
(write-uncounted-coll out id-empty id-sm id-md id-lg coll))))
|
|
|
|
|
|
|
|
|
|
|
|
;; Micro-optimization:
|
|
|
|
|
|
;; As (write-kvs out id-map-0 id-map-sm id-map-md id-map-lg x)
|
|
|
|
|
|
(defn- write-map [^DataOutput out m]
|
|
|
|
|
|
(let [cnt (count m)]
|
|
|
|
|
|
(if (zero? cnt)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-map-0)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? cnt)
|
|
|
|
|
|
(do (write-id out id-map-sm)
|
|
|
|
|
|
(write-sm-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? cnt)
|
|
|
|
|
|
(do (write-id out id-map-md)
|
|
|
|
|
|
(write-md-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-map-lg)
|
|
|
|
|
|
(write-lg-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(-run-kv!
|
|
|
|
|
|
(fn [k v]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-freeze-with-meta! k out)
|
|
|
|
|
|
(-freeze-with-meta! v out))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
m)))))
|
|
|
|
|
|
|
|
|
|
|
|
;; Micro-optimization:
|
|
|
|
|
|
;; As (write-counted-coll out id-set-0 id-set-sm id-set-md id-set-lg x)
|
|
|
|
|
|
(defn- write-set [^DataOutput out s]
|
|
|
|
|
|
(let [cnt (count s)]
|
|
|
|
|
|
(if (zero? cnt)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id out id-set-0)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(do
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? cnt)
|
|
|
|
|
|
(do (write-id out id-set-sm)
|
|
|
|
|
|
(write-sm-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? cnt)
|
|
|
|
|
|
(do (write-id out id-set-md)
|
|
|
|
|
|
(write-md-count out cnt))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-set-lg)
|
|
|
|
|
|
(write-lg-count out cnt)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-run! (fn [in] (-freeze-with-meta! in out)) s)))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(defn- write-objects [^DataOutput out ^objects ary]
|
|
|
|
|
|
(let [len (alength ary)]
|
|
|
|
|
|
(write-id out id-objects-lg)
|
|
|
|
|
|
(write-lg-count out len)
|
|
|
|
|
|
(-run! (fn [in] (-freeze-with-meta! in out)) ary)))
|
|
|
|
|
|
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(defn- write-serializable [^DataOutput out x ^String class-name]
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(when-debug (println (str "write-serializable: " (type x))))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(let [class-name-ba (.getBytes class-name charset)
|
|
|
|
|
|
len (alength class-name-ba)]
|
|
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(do (write-id out id-serializable-q-sm)
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(write-bytes-sm out class-name-ba))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-serializable-q-md)
|
|
|
|
|
|
(write-bytes-md out class-name-ba))
|
|
|
|
|
|
|
|
|
|
|
|
;; :else ; Unrealistic
|
|
|
|
|
|
;; (do (write-id out id-serializable-q-lg)
|
|
|
|
|
|
;; (write-bytes-md out class-name-ba))
|
2016-07-26 05:20:40 +00:00
|
|
|
|
|
2016-03-08 04:13:46 +00:00
|
|
|
|
:else
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(throw
|
|
|
|
|
|
(ex-info "Serializable class name too long"
|
|
|
|
|
|
{:class-name class-name})))
|
2016-03-08 04:13:46 +00:00
|
|
|
|
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
;; Legacy: write object directly to out.
|
|
|
|
|
|
;; (.writeObject (ObjectOutputStream. out) x)
|
|
|
|
|
|
|
|
|
|
|
|
;; Quarantined: write object to ba, then ba to out.
|
|
|
|
|
|
;; We'll have object length during thaw, allowing us to skip readObject.
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(let [quarantined-ba (ByteArrayOutputStream.)]
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(.writeObject (ObjectOutputStream. (DataOutputStream. quarantined-ba)) x)
|
|
|
|
|
|
(write-bytes out (.toByteArray quarantined-ba)))))
|
2015-09-29 13:10:09 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- write-readable [^DataOutput out x]
|
|
|
|
|
|
(when-debug (println (str "write-readable: " (type x))))
|
|
|
|
|
|
(let [edn (enc/pr-edn x)
|
2016-04-14 06:07:59 +00:00
|
|
|
|
edn-ba (.getBytes ^String edn charset)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
len (alength edn-ba)]
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
|
|
|
|
|
(do (write-id out id-reader-sm)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(write-bytes-sm out edn-ba))
|
|
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-reader-md)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(write-bytes-md out edn-ba))
|
|
|
|
|
|
|
|
|
|
|
|
:else
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(do (write-id out id-reader-lg)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(write-bytes-lg out edn-ba)))))
|
|
|
|
|
|
|
|
|
|
|
|
(defn try-write-serializable [out x]
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(when (and (instance? Serializable x) (not (fn? x)))
|
2016-06-17 05:03:07 +00:00
|
|
|
|
(try
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(let [class-name (.getName (class x))] ; Reflect
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(when (freeze-serializable-allowed? class-name)
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(write-serializable out x class-name)
|
|
|
|
|
|
true))
|
2016-06-17 05:03:07 +00:00
|
|
|
|
(catch Throwable _ nil))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defn try-write-readable [out x]
|
|
|
|
|
|
(when (utils/readable? x)
|
2016-06-17 05:03:07 +00:00
|
|
|
|
(try
|
|
|
|
|
|
(write-readable out x)
|
|
|
|
|
|
true
|
|
|
|
|
|
(catch Throwable _ nil))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defn- try-pr-edn [x]
|
|
|
|
|
|
(try
|
|
|
|
|
|
(enc/pr-edn x)
|
|
|
|
|
|
(catch Throwable _
|
|
|
|
|
|
(try
|
|
|
|
|
|
(str x)
|
2020-09-10 10:08:28 +00:00
|
|
|
|
(catch Throwable _
|
|
|
|
|
|
:nippy/unprintable)))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defn write-unfreezable [out x]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-freeze-without-meta!
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unfreezable
|
|
|
|
|
|
{:type (type x)
|
|
|
|
|
|
:content (try-pr-edn x)}}
|
2016-04-07 05:49:26 +00:00
|
|
|
|
out))
|
|
|
|
|
|
|
|
|
|
|
|
(defn throw-unfreezable [x]
|
2020-09-10 10:08:28 +00:00
|
|
|
|
(let [t (type x)]
|
|
|
|
|
|
(throw
|
|
|
|
|
|
(ex-info (str "Unfreezable type: " t)
|
|
|
|
|
|
{:type t
|
|
|
|
|
|
:as-str (try-pr-edn x)}))))
|
2013-04-14 07:44:06 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
;; Public `-freeze-with-meta!` with different arg order
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn freeze-to-out!
|
2016-07-18 04:30:38 +00:00
|
|
|
|
"Serializes arg (any Clojure data type) to a DataOutput.
|
|
|
|
|
|
This is a low-level util: in most cases you'll want `freeze` instead."
|
2016-07-16 12:09:38 +00:00
|
|
|
|
[^DataOutput data-output x] (-freeze-with-meta! x data-output))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private freezer [type & body]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
`(extend-type ~type IFreezable1
|
|
|
|
|
|
(~'-freeze-without-meta! [~'x ~(with-meta 'out {:tag 'DataOutput})]
|
2015-09-28 09:38:48 +00:00
|
|
|
|
~@body)))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defmacro ^:private id-freezer [type id & body]
|
2016-07-16 12:09:38 +00:00
|
|
|
|
`(extend-type ~type IFreezable1
|
|
|
|
|
|
(~'-freeze-without-meta! [~'x ~(with-meta 'out {:tag 'DataOutput})]
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(write-id ~'out ~id)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
~@body)))
|
|
|
|
|
|
|
2016-04-12 17:52:15 +00:00
|
|
|
|
;;;; Caching ; Experimental
|
|
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
;; Nb: don't use an auto initialValue; can cause thread-local state to
|
|
|
|
|
|
;; accidentally hang around with the use of `freeze-to-out!`, etc.
|
|
|
|
|
|
;; Safer to require explicit activation through `with-cache`.
|
2016-07-18 04:30:38 +00:00
|
|
|
|
(def ^ThreadLocal -cache-proxy
|
|
|
|
|
|
"{[<x> <meta>] <idx>} for freezing, {<idx> <x-with-meta>} for thawing."
|
|
|
|
|
|
(proxy [ThreadLocal] []))
|
2016-07-16 12:09:38 +00:00
|
|
|
|
|
|
|
|
|
|
(defmacro ^:private with-cache
|
2016-07-18 04:30:38 +00:00
|
|
|
|
"Experimental, subject to change.
|
|
|
|
|
|
Executes body with support for freezing/thawing cached values.
|
|
|
|
|
|
|
|
|
|
|
|
This is a low-level util: you won't need to use this yourself unless
|
|
|
|
|
|
you're using `freeze-to-out!` or `thaw-from-in!` (also low-level utils).
|
|
|
|
|
|
|
|
|
|
|
|
See also `cache`."
|
2016-07-16 12:09:38 +00:00
|
|
|
|
[& body]
|
|
|
|
|
|
`(try
|
2020-09-10 10:03:33 +00:00
|
|
|
|
(.set -cache-proxy (volatile! nil))
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(do ~@body)
|
|
|
|
|
|
(finally (.remove -cache-proxy))))
|
|
|
|
|
|
|
2016-10-28 03:35:21 +00:00
|
|
|
|
(deftype Cached [val])
|
2016-04-14 04:23:31 +00:00
|
|
|
|
(defn cache
|
2016-07-18 04:30:38 +00:00
|
|
|
|
"Experimental, subject to change.
|
|
|
|
|
|
|
|
|
|
|
|
Wraps value so that future writes of the same wrapped value with same
|
|
|
|
|
|
metadata will be efficiently encoded as references to this one.
|
2016-07-16 12:09:38 +00:00
|
|
|
|
|
2016-04-14 04:23:31 +00:00
|
|
|
|
(freeze [(cache \"foo\") (cache \"foo\") (cache \"foo\")])
|
|
|
|
|
|
will incl. a single \"foo\", plus 2x single-byte references to \"foo\"."
|
|
|
|
|
|
[x]
|
2016-10-28 03:35:21 +00:00
|
|
|
|
(if (instance? Cached x) x (Cached. x)))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
|
|
|
|
|
(comment (cache "foo"))
|
|
|
|
|
|
|
2016-10-28 03:35:21 +00:00
|
|
|
|
(freezer Cached
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(let [x-val (.-val x)]
|
|
|
|
|
|
(if-let [cache_ (.get -cache-proxy)]
|
2016-05-07 04:18:19 +00:00
|
|
|
|
(let [cache @cache_
|
2016-07-17 08:21:26 +00:00
|
|
|
|
k #_x-val [x-val (meta x-val)]
|
|
|
|
|
|
?idx (get cache k)
|
2016-05-07 04:18:19 +00:00
|
|
|
|
^int idx (or ?idx
|
|
|
|
|
|
(let [idx (count cache)]
|
2020-09-10 10:03:33 +00:00
|
|
|
|
(vswap! cache_ assoc k idx)
|
2016-05-07 04:18:19 +00:00
|
|
|
|
idx))
|
2016-04-14 04:23:31 +00:00
|
|
|
|
|
|
|
|
|
|
first-occurance? (nil? ?idx)]
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? idx)
|
2016-05-07 04:18:19 +00:00
|
|
|
|
(case (int idx)
|
2016-10-28 09:41:38 +00:00
|
|
|
|
0 (do (write-id out id-cached-0) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
1 (do (write-id out id-cached-1) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
2 (do (write-id out id-cached-2) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
3 (do (write-id out id-cached-3) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
4 (do (write-id out id-cached-4) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
5 (do (write-id out id-cached-5) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
6 (do (write-id out id-cached-6) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
7 (do (write-id out id-cached-7) (when first-occurance? (-freeze-with-meta! x-val out)))
|
|
|
|
|
|
|
|
|
|
|
|
(do
|
|
|
|
|
|
(write-id out id-cached-sm)
|
|
|
|
|
|
(write-sm-count out idx)
|
|
|
|
|
|
(when first-occurance? (-freeze-with-meta! x-val out))))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(md-count? idx)
|
2016-10-28 09:41:38 +00:00
|
|
|
|
(do
|
|
|
|
|
|
(write-id out id-cached-md)
|
|
|
|
|
|
(write-md-count out idx)
|
|
|
|
|
|
(when first-occurance? (-freeze-with-meta! x-val out)))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
2016-04-14 04:23:31 +00:00
|
|
|
|
:else
|
|
|
|
|
|
;; (throw (ex-info "Max cache size exceeded" {:idx idx}))
|
2016-07-17 08:21:26 +00:00
|
|
|
|
(-freeze-with-meta! x-val out) ; Just freeze uncached
|
2016-04-14 04:23:31 +00:00
|
|
|
|
))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
2016-07-17 08:21:26 +00:00
|
|
|
|
(-freeze-with-meta! x-val out))))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
|
|
|
|
|
(declare thaw-from-in!)
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(def ^:private thaw-cached
|
|
|
|
|
|
(let [not-found (Object.)]
|
|
|
|
|
|
(fn [idx in]
|
|
|
|
|
|
(if-let [cache_ (.get -cache-proxy)]
|
|
|
|
|
|
(let [v (get @cache_ idx not-found)]
|
|
|
|
|
|
(if (identical? v not-found)
|
|
|
|
|
|
(let [x (thaw-from-in! in)]
|
2020-09-10 10:03:33 +00:00
|
|
|
|
(vswap! cache_ assoc idx x)
|
2016-07-16 12:09:38 +00:00
|
|
|
|
x)
|
|
|
|
|
|
v))
|
|
|
|
|
|
(throw (ex-info "No cache_ established, can't thaw. See `with-cache`."
|
|
|
|
|
|
{}))))))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
2016-07-17 08:21:26 +00:00
|
|
|
|
(comment
|
|
|
|
|
|
(thaw (freeze [(cache "foo") (cache "foo") (cache "foo")]))
|
|
|
|
|
|
(let [v1 (with-meta [] {:id :v1})
|
|
|
|
|
|
v2 (with-meta [] {:id :v2})]
|
|
|
|
|
|
(mapv meta
|
|
|
|
|
|
(thaw (freeze [(cache v1) (cache v2) (cache v1) (cache v2)])))))
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
|
|
|
|
|
;;;;
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(id-freezer nil id-nil)
|
|
|
|
|
|
(id-freezer (type '()) id-list-0)
|
|
|
|
|
|
(id-freezer Character id-char (.writeChar out (int x)))
|
|
|
|
|
|
(id-freezer Byte id-byte (.writeByte out x))
|
|
|
|
|
|
(id-freezer Short id-short (.writeShort out x))
|
|
|
|
|
|
(id-freezer Integer id-integer (.writeInt out x))
|
|
|
|
|
|
(id-freezer BigInt id-bigint (write-biginteger out (.toBigInteger x)))
|
|
|
|
|
|
(id-freezer BigInteger id-biginteger (write-biginteger out x))
|
|
|
|
|
|
(id-freezer Pattern id-regex (write-str out (str x)))
|
|
|
|
|
|
(id-freezer Float id-float (.writeFloat out x))
|
|
|
|
|
|
(id-freezer BigDecimal id-bigdec
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(write-biginteger out (.unscaledValue x))
|
|
|
|
|
|
(.writeInt out (.scale x)))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(id-freezer Ratio id-ratio
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(write-biginteger out (.numerator x))
|
|
|
|
|
|
(write-biginteger out (.denominator x)))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(id-freezer Date id-date (.writeLong out (.getTime x)))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
|
|
|
|
|
(id-freezer URI id-uri
|
|
|
|
|
|
(write-str out (.toString x)))
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(id-freezer UUID id-uuid
|
2015-09-29 09:02:46 +00:00
|
|
|
|
(.writeLong out (.getMostSignificantBits x))
|
|
|
|
|
|
(.writeLong out (.getLeastSignificantBits x)))
|
2013-08-06 20:55:27 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(freezer Boolean (if x (write-id out id-true) (write-id out id-false)))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(freezer (Class/forName "[B") (write-bytes out x))
|
|
|
|
|
|
(freezer (Class/forName "[Ljava.lang.Object;") (write-objects out x))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(freezer String (write-str out x))
|
|
|
|
|
|
(freezer Keyword (write-kw out x))
|
|
|
|
|
|
(freezer Symbol (write-sym out x))
|
|
|
|
|
|
(freezer Long (write-long out x))
|
|
|
|
|
|
(freezer Double
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(if (zero? ^double x)
|
|
|
|
|
|
(write-id out id-double-zero)
|
|
|
|
|
|
(do (write-id out id-double)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(.writeDouble out x))))
|
|
|
|
|
|
|
|
|
|
|
|
(freezer PersistentQueue (write-counted-coll out id-queue x))
|
|
|
|
|
|
(freezer PersistentTreeSet (write-counted-coll out id-sorted-set x))
|
|
|
|
|
|
(freezer PersistentTreeMap (write-kvs out id-sorted-map x))
|
|
|
|
|
|
(freezer APersistentVector (write-vec out x))
|
|
|
|
|
|
(freezer APersistentSet (write-set out x))
|
|
|
|
|
|
(freezer APersistentMap (write-map out x))
|
|
|
|
|
|
(freezer PersistentList (write-counted-coll out id-list-0 id-list-sm id-list-md id-list-lg x))
|
|
|
|
|
|
(freezer LazySeq (write-uncounted-coll out id-seq-0 id-seq-sm id-seq-md id-seq-lg x))
|
|
|
|
|
|
(freezer ISeq (write-coll out id-seq-0 id-seq-sm id-seq-md id-seq-lg x))
|
|
|
|
|
|
(freezer IRecord
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(let [class-name (.getName (class x)) ; Reflect
|
|
|
|
|
|
class-name-ba (.getBytes class-name charset)
|
|
|
|
|
|
len (alength class-name-ba)]
|
|
|
|
|
|
(enc/cond
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(sm-count? len)
|
|
|
|
|
|
(do (write-id out id-record-sm)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(write-bytes-sm out class-name-ba))
|
|
|
|
|
|
|
|
|
|
|
|
(md-count? len)
|
|
|
|
|
|
(do (write-id out id-record-md)
|
|
|
|
|
|
(write-bytes-md out class-name-ba))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
;; :else ; Unrealistic
|
|
|
|
|
|
;; (do (write-id out id-record-lg)
|
|
|
|
|
|
;; (write-bytes-md out class-name-ba))
|
2016-07-26 05:20:40 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
:else
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(throw
|
|
|
|
|
|
(ex-info "Record class name too long"
|
|
|
|
|
|
{:class-name class-name})))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(-freeze-without-meta! (into {} x) out)))
|
2018-10-06 07:54:28 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(freezer IType
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(let [aclass (class x)
|
|
|
|
|
|
class-name (.getName aclass)]
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(write-id out id-type)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(write-str out class-name)
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(let [basis-method (.getMethod aclass "getBasis" nil)
|
|
|
|
|
|
basis (.invoke basis-method nil nil)]
|
|
|
|
|
|
(-run!
|
|
|
|
|
|
(fn [b]
|
|
|
|
|
|
(let [^Field cfield (.getField aclass (name b))]
|
|
|
|
|
|
(let [fvalue (.get cfield x)]
|
|
|
|
|
|
(-freeze-without-meta! fvalue out))))
|
|
|
|
|
|
basis))))
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(freezer Object
|
|
|
|
|
|
(when-debug (println (str "freeze-fallback: " (type x))))
|
|
|
|
|
|
(if-let [ff *freeze-fallback*]
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(if-not (identical? ff :write-unfreezable)
|
|
|
|
|
|
(ff out x) ; Modern approach with ff
|
|
|
|
|
|
(or ; Legacy approach with ff
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(try-write-serializable out x)
|
|
|
|
|
|
(try-write-readable out x)
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(write-unfreezable out x)))
|
2016-04-16 04:24:57 +00:00
|
|
|
|
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
;; Without ff
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(or
|
|
|
|
|
|
(try-write-serializable out x)
|
|
|
|
|
|
(try-write-readable out x)
|
2016-04-16 04:24:57 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(when-let [fff *final-freeze-fallback*] (fff out x) true) ; Deprecated
|
2016-04-16 04:24:57 +00:00
|
|
|
|
|
2016-06-17 05:25:31 +00:00
|
|
|
|
(throw-unfreezable x))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
;;;;
|
2013-08-07 09:19:11 +00:00
|
|
|
|
|
2013-06-13 15:40:44 +00:00
|
|
|
|
(def ^:private head-meta-id (reduce-kv #(assoc %1 %3 %2) {} head-meta))
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(def ^:private get-head-ba
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(enc/memoize_
|
|
|
|
|
|
(fn [head-meta]
|
|
|
|
|
|
(when-let [meta-id (get head-meta-id (assoc head-meta :version head-version))]
|
|
|
|
|
|
(enc/ba-concat head-sig (byte-array [meta-id]))))))
|
2013-06-13 15:40:44 +00:00
|
|
|
|
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(defn- wrap-header [data-ba head-meta]
|
|
|
|
|
|
(if-let [head-ba (get-head-ba head-meta)]
|
2015-09-29 07:30:25 +00:00
|
|
|
|
(enc/ba-concat head-ba data-ba)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(throw (ex-info (str "Unrecognized header meta: " head-meta)
|
2014-04-05 11:30:28 +00:00
|
|
|
|
{:head-meta head-meta}))))
|
2013-06-13 15:40:44 +00:00
|
|
|
|
|
2014-04-05 11:30:28 +00:00
|
|
|
|
(comment (wrap-header (.getBytes "foo") {:compressor-id :lz4
|
|
|
|
|
|
:encryptor-id nil}))
|
2013-06-13 05:12:28 +00:00
|
|
|
|
|
2020-07-25 08:19:00 +00:00
|
|
|
|
(defn- call-with-bindings
|
|
|
|
|
|
"Allow opts to override config bindings."
|
2020-09-10 16:42:25 +00:00
|
|
|
|
[action opts f]
|
2020-07-25 08:19:00 +00:00
|
|
|
|
(if (empty? opts)
|
|
|
|
|
|
(f)
|
|
|
|
|
|
(let [opt->bindings
|
|
|
|
|
|
(fn [bindings id var]
|
|
|
|
|
|
(let [v (get opts id :default)]
|
|
|
|
|
|
(if (identical? v :default)
|
|
|
|
|
|
(do bindings)
|
|
|
|
|
|
(assoc bindings var v))))
|
|
|
|
|
|
|
|
|
|
|
|
bindings
|
|
|
|
|
|
(-> nil
|
|
|
|
|
|
(opt->bindings :freeze-fallback #'*freeze-fallback*)
|
|
|
|
|
|
(opt->bindings :auto-freeze-compressor #'*auto-freeze-compressor*)
|
2020-07-25 08:15:27 +00:00
|
|
|
|
(opt->bindings :custom-readers #'*custom-readers*)
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(opt->bindings :incl-metadata? #'*incl-metadata?*)
|
|
|
|
|
|
(opt->bindings :serializable-allowlist
|
|
|
|
|
|
(case action
|
|
|
|
|
|
:freeze #'*freeze-serializable-allowlist*
|
|
|
|
|
|
:thaw #'*thaw-serializable-allowlist*)))]
|
2020-07-25 08:19:00 +00:00
|
|
|
|
|
|
|
|
|
|
(if-not bindings
|
|
|
|
|
|
(f) ; Common case
|
|
|
|
|
|
(try
|
|
|
|
|
|
(push-thread-bindings bindings)
|
|
|
|
|
|
(f)
|
|
|
|
|
|
(finally
|
|
|
|
|
|
(pop-thread-bindings)))))))
|
|
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(enc/qb 1e4
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(call-with-bindings :freeze {} (fn [] *freeze-fallback*))
|
|
|
|
|
|
(call-with-bindings :freeze {:freeze-fallback "foo"} (fn [] *freeze-fallback*))))
|
2020-07-25 08:19:00 +00:00
|
|
|
|
|
2016-04-14 03:43:09 +00:00
|
|
|
|
(defn fast-freeze
|
|
|
|
|
|
"Like `freeze` but:
|
|
|
|
|
|
- Writes data without a Nippy header
|
|
|
|
|
|
- Drops all support for compression and encryption
|
|
|
|
|
|
- Must be thawed with `fast-thaw`
|
|
|
|
|
|
|
2020-07-25 08:19:00 +00:00
|
|
|
|
Equivalent to (but a little faster than) `freeze` with opts:
|
|
|
|
|
|
- :compressor nil
|
|
|
|
|
|
- :encryptor nil
|
|
|
|
|
|
- :no-header? true"
|
2016-04-14 05:02:27 +00:00
|
|
|
|
[x]
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(let [baos (ByteArrayOutputStream. 64)
|
|
|
|
|
|
dos (DataOutputStream. baos)]
|
|
|
|
|
|
(with-cache (-freeze-with-meta! x dos))
|
|
|
|
|
|
(.toByteArray baos)))
|
2016-04-14 03:43:09 +00:00
|
|
|
|
|
2013-06-13 05:12:28 +00:00
|
|
|
|
(defn freeze
|
2014-04-05 11:30:28 +00:00
|
|
|
|
"Serializes arg (any Clojure data type) to a byte array. To freeze custom
|
2020-09-10 16:42:25 +00:00
|
|
|
|
types, extend the Clojure reader or see `extend-freeze`."
|
2020-09-10 11:35:55 +00:00
|
|
|
|
|
2016-04-14 05:02:27 +00:00
|
|
|
|
([x] (freeze x nil))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
([x {:as opts
|
2020-09-10 16:42:25 +00:00
|
|
|
|
:keys [compressor encryptor password serializable-allowlist incl-metadata?]
|
2016-04-14 05:02:27 +00:00
|
|
|
|
:or {compressor :auto
|
2020-09-10 16:42:25 +00:00
|
|
|
|
encryptor aes128-gcm-encryptor}}]
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(call-with-bindings :freeze opts
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(fn []
|
|
|
|
|
|
|
|
|
|
|
|
(let [;; Intentionally undocumented:
|
|
|
|
|
|
no-header? (or (get opts :no-header?)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(get opts :skip-header?))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
encryptor (when password encryptor)
|
|
|
|
|
|
baos (ByteArrayOutputStream. 64)
|
|
|
|
|
|
dos (DataOutputStream. baos)]
|
|
|
|
|
|
|
|
|
|
|
|
(if (and (nil? compressor) (nil? encryptor))
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(do ; Optimized case
|
|
|
|
|
|
(when-not no-header? ; Avoid `wrap-header`'s array copy:
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(let [head-ba (get-head-ba {:compressor-id nil :encryptor-id nil})]
|
|
|
|
|
|
(.write dos head-ba 0 4)))
|
|
|
|
|
|
(with-cache (-freeze-with-meta! x dos))
|
|
|
|
|
|
(.toByteArray baos))
|
|
|
|
|
|
|
|
|
|
|
|
(do
|
|
|
|
|
|
(with-cache (-freeze-with-meta! x dos))
|
|
|
|
|
|
(let [ba (.toByteArray baos)
|
|
|
|
|
|
|
|
|
|
|
|
compressor
|
|
|
|
|
|
(if (identical? compressor :auto)
|
|
|
|
|
|
(if no-header?
|
|
|
|
|
|
lz4-compressor
|
|
|
|
|
|
(if-let [fc *auto-freeze-compressor*]
|
|
|
|
|
|
(fc ba)
|
|
|
|
|
|
;; Intelligently enable compression only if benefit
|
|
|
|
|
|
;; is likely to outweigh cost:
|
|
|
|
|
|
(when (> (alength ba) 8192) lz4-compressor)))
|
|
|
|
|
|
|
|
|
|
|
|
(if (fn? compressor)
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(compressor ba) ; Assume compressor selector fn
|
|
|
|
|
|
compressor ; Assume compressor
|
2020-07-24 17:38:16 +00:00
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
ba (if compressor (compress compressor ba) ba)
|
|
|
|
|
|
ba (if encryptor (encrypt encryptor password ba) ba)]
|
|
|
|
|
|
|
|
|
|
|
|
(if no-header?
|
|
|
|
|
|
ba
|
|
|
|
|
|
(wrap-header ba
|
|
|
|
|
|
{:compressor-id
|
|
|
|
|
|
(when-let [c compressor]
|
|
|
|
|
|
(or (compression/standard-header-ids
|
|
|
|
|
|
(compression/header-id c))
|
|
|
|
|
|
:else))
|
|
|
|
|
|
|
|
|
|
|
|
:encryptor-id
|
|
|
|
|
|
(when-let [e encryptor]
|
|
|
|
|
|
(or (encryption/standard-header-ids
|
|
|
|
|
|
(encryption/header-id e))
|
|
|
|
|
|
:else))}))))))))))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
|
|
|
|
|
;;;; Thawing
|
|
|
|
|
|
|
2020-07-24 20:46:30 +00:00
|
|
|
|
(declare ^:private read-bytes)
|
|
|
|
|
|
(defn- read-bytes-sm [^DataInput in] (read-bytes in (read-sm-count in)))
|
|
|
|
|
|
(defn- read-bytes-md [^DataInput in] (read-bytes in (read-md-count in)))
|
|
|
|
|
|
(defn- read-bytes-lg [^DataInput in] (read-bytes in (read-lg-count in)))
|
2020-07-24 12:20:11 +00:00
|
|
|
|
(defn- read-bytes
|
2020-07-24 20:46:30 +00:00
|
|
|
|
([^DataInput in len] (let [ba (byte-array len)] (.readFully in ba 0 len) ba))
|
|
|
|
|
|
([^DataInput in ]
|
2020-07-24 12:20:11 +00:00
|
|
|
|
(enc/case-eval (.readByte in)
|
|
|
|
|
|
id-bytes-0 (byte-array 0)
|
|
|
|
|
|
id-bytes-sm (read-bytes in (read-sm-count in))
|
|
|
|
|
|
id-bytes-md (read-bytes in (read-md-count in))
|
|
|
|
|
|
id-bytes-lg (read-bytes in (read-lg-count in)))))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(defn- read-str-sm [^DataInput in] (String. ^bytes (read-bytes in (read-sm-count in)) charset))
|
|
|
|
|
|
(defn- read-str-md [^DataInput in] (String. ^bytes (read-bytes in (read-md-count in)) charset))
|
|
|
|
|
|
(defn- read-str-lg [^DataInput in] (String. ^bytes (read-bytes in (read-lg-count in)) charset))
|
|
|
|
|
|
(defn- read-str
|
2020-07-24 20:46:30 +00:00
|
|
|
|
([^DataInput in len] (String. ^bytes (read-bytes in len) charset))
|
|
|
|
|
|
([^DataInput in ]
|
|
|
|
|
|
(enc/case-eval (.readByte in)
|
|
|
|
|
|
id-str-0 ""
|
|
|
|
|
|
id-str-sm (String. ^bytes (read-bytes in (read-sm-count in)) charset)
|
|
|
|
|
|
id-str-md (String. ^bytes (read-bytes in (read-md-count in)) charset)
|
|
|
|
|
|
id-str-lg (String. ^bytes (read-bytes in (read-lg-count in)) charset))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-14 05:02:27 +00:00
|
|
|
|
(defn- read-biginteger [^DataInput in] (BigInteger. ^bytes (read-bytes in (.readInt in))))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defmacro ^:private editable? [coll] `(instance? clojure.lang.IEditableCollection ~coll))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- read-into [to ^DataInput in ^long n]
|
|
|
|
|
|
(if (and (editable? to) (> n 10))
|
2016-03-04 05:39:14 +00:00
|
|
|
|
(persistent!
|
|
|
|
|
|
(enc/reduce-n (fn [acc _] (conj! acc (thaw-from-in! in)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(transient to) n))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(enc/reduce-n (fn [acc _] (conj acc (thaw-from-in! in))) to n)))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(defn- read-objects [^objects ary ^DataInput in]
|
|
|
|
|
|
(enc/reduce-n
|
|
|
|
|
|
(fn [^objects ary i]
|
|
|
|
|
|
(aset ary i (thaw-from-in! in))
|
|
|
|
|
|
ary)
|
|
|
|
|
|
ary (alength ary)))
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- read-kvs-into [to ^DataInput in ^long n]
|
|
|
|
|
|
(if (and (editable? to) (> n 10))
|
2016-03-04 05:39:14 +00:00
|
|
|
|
(persistent!
|
|
|
|
|
|
(enc/reduce-n (fn [acc _] (assoc! acc (thaw-from-in! in) (thaw-from-in! in)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(transient to) n))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2016-03-04 05:39:14 +00:00
|
|
|
|
(enc/reduce-n (fn [acc _] (assoc acc (thaw-from-in! in) (thaw-from-in! in)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
to n)))
|
2015-09-29 07:36:23 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- read-kvs-depr1 [to ^DataInput in] (read-kvs-into to in (quot (.readInt in) 2)))
|
2015-04-17 12:33:55 +00:00
|
|
|
|
|
2015-05-29 07:13:35 +00:00
|
|
|
|
(def ^:private class-method-sig (into-array Class [IPersistentMap]))
|
2013-08-02 08:20:14 +00:00
|
|
|
|
|
2016-04-13 17:13:33 +00:00
|
|
|
|
(defn- read-custom! [in prefixed? type-id]
|
2015-06-01 04:07:50 +00:00
|
|
|
|
(if-let [custom-reader (get *custom-readers* type-id)]
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(try
|
|
|
|
|
|
(custom-reader in)
|
|
|
|
|
|
(catch Exception e
|
|
|
|
|
|
(throw
|
|
|
|
|
|
(ex-info
|
2016-04-13 17:13:33 +00:00
|
|
|
|
(str "Reader exception for custom type id: " type-id)
|
|
|
|
|
|
{:type-id type-id
|
|
|
|
|
|
:prefixed? prefixed?} e))))
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(throw
|
|
|
|
|
|
(ex-info
|
2016-04-13 17:13:33 +00:00
|
|
|
|
(str "No reader provided for custom type id: " type-id)
|
|
|
|
|
|
{:type-id type-id
|
|
|
|
|
|
:prefixed? prefixed?}))))
|
2012-12-04 06:16:29 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(defn- read-edn [edn]
|
|
|
|
|
|
(try
|
|
|
|
|
|
(enc/read-edn {:readers *data-readers*} edn)
|
|
|
|
|
|
(catch Exception e
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unthawable
|
|
|
|
|
|
{:type :reader
|
|
|
|
|
|
:cause :exception
|
|
|
|
|
|
|
|
|
|
|
|
:content edn
|
|
|
|
|
|
:exception e}})))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(defn- read-object [^DataInput in class-name]
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(try
|
|
|
|
|
|
(let [content (.readObject (ObjectInputStream. in))]
|
|
|
|
|
|
(try
|
|
|
|
|
|
(let [class (Class/forName class-name)] (cast class content))
|
|
|
|
|
|
(catch Exception e
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unthawable
|
|
|
|
|
|
{:type :serializable
|
|
|
|
|
|
:cause :exception
|
|
|
|
|
|
|
|
|
|
|
|
:class-name class-name
|
|
|
|
|
|
:content content
|
|
|
|
|
|
:exception e}})))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(catch Exception e
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unthawable
|
|
|
|
|
|
{:type :serializable
|
|
|
|
|
|
:cause :exception
|
|
|
|
|
|
|
|
|
|
|
|
:class-name class-name
|
|
|
|
|
|
:content nil
|
|
|
|
|
|
:exception e}})))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-09-10 11:47:23 +00:00
|
|
|
|
(defn read-quarantined-serializable-object-unsafe!
|
|
|
|
|
|
"Given a quarantined Serializable object like
|
|
|
|
|
|
{:nippy/unthawable {:class-name <> :content <quarantined-ba>}}, reads and
|
2020-09-10 16:42:25 +00:00
|
|
|
|
returns the object WITHOUT regard for `*thaw-serializable-allowlist*`.
|
2020-09-10 11:47:23 +00:00
|
|
|
|
|
|
|
|
|
|
**MAY BE UNSAFE!** Don't call this unless you absolutely trust the payload
|
|
|
|
|
|
to not contain any malicious code.
|
|
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
See `*thaw-serializable-allowlist*` for more info."
|
2020-09-10 11:47:23 +00:00
|
|
|
|
[m]
|
|
|
|
|
|
(when-let [m (get m :nippy/unthawable)]
|
|
|
|
|
|
(let [{:keys [class-name content]} m]
|
|
|
|
|
|
(when (and class-name content)
|
|
|
|
|
|
(read-object
|
|
|
|
|
|
(DataInputStream. (ByteArrayInputStream. content))
|
|
|
|
|
|
class-name)))))
|
|
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(read-quarantined-serializable-object-unsafe!
|
|
|
|
|
|
(thaw (freeze (java.util.concurrent.Semaphore. 1)))))
|
|
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(defn- read-serializable-q
|
|
|
|
|
|
"Quarantined => object serialized to ba, then ba written to output stream.
|
2020-09-10 16:42:25 +00:00
|
|
|
|
Has length prefix => can skip `readObject` in event of allowlist failure."
|
2020-07-24 20:50:05 +00:00
|
|
|
|
[^DataInput in class-name]
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(let [quarantined-ba (read-bytes in)]
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(if (thaw-serializable-allowed? class-name)
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(read-object (DataInputStream. (ByteArrayInputStream. quarantined-ba)) class-name)
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unthawable
|
|
|
|
|
|
{:type :serializable
|
|
|
|
|
|
:cause :quarantined
|
|
|
|
|
|
|
|
|
|
|
|
:class-name class-name
|
|
|
|
|
|
:content quarantined-ba}})))
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(defn- read-serializable-uq
|
|
|
|
|
|
"Unquarantined => object serialized directly to output stream.
|
2020-09-10 16:42:25 +00:00
|
|
|
|
No length prefix => cannot skip `readObject` in event of allowlist failure."
|
2020-07-24 20:50:05 +00:00
|
|
|
|
[^DataInput in class-name]
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(if (thaw-serializable-allowed? class-name)
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
(read-object in class-name)
|
|
|
|
|
|
(throw ; No way to skip bytes, so best we can do is throw
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(ex-info "Cannot thaw object: `*thaw-serializable-allowlist*` check failed. See docstring for details."
|
[BREAKING] [Security] Fix RCE vulnerability
Fix a Remote Code Execution (RCE) vulnerability identified in an
excellent report by Timo Mihaljov (@solita-timo-mihaljov).
You are vulnerable iff both:
1. You are using Nippy to serialize and deserialize data from an
UNTRUSTED SOURCE.
2. You have a vulnerable ("gadget") class on your classpath.
Notably Clojure <= 1.8 includes such a class [1].
Many other libraries do too, some examples at [2].
To prevent this risk, a Serialization whitelist has been added.
Any classes not *explicitly* authorized by the whitelist to use
Serialization will NOT be permitted to.
The default whitelist is EMPTY, meaning this is a BREAKING
change iff you make use of Nippy's Serialization support. In
this case, you'll need to update the whitelist for your needs.
For more info see the `*serializable-whitelist*` docstring.
[1] https://clojure.atlassian.net/browse/CLJ-2204
[2] https://github.com/frohoff/ysoserial
Further info below provided by Timo:
------------------------------------
Deserialization vulnerabilities are exploited by constructing objects of classes
whose constructors perform some action that's useful to the attacker. A class like
this is called a gadget, and a collection of such classes that can be combined to
reach the attacker's goal is called a gadget chain.
There are three prerequisites for exploiting a deserialization vulnerability:
1) The attacker must be able to control the deserialized data, for example,
by gaining write access to the data store where trusted parties serialize
data or by exploiting some other vulnerability on the other end of a
communications channel.
2) The deserializer must construct objects of classes specified in the
serialized data. In other words, the attacker must have full control over
which classes get instantiated.
3) The classpath must contain gadgets that can be combined into a gadget chain.
The vulnerable code is in Nippy's function `read-serializable`, which calls the
`readObject` method of `ObjectInputStream`.
I have only tested the PoC with the latest stable version, 2.14.0, but looking at
Nippy's Git history, I believe all versions starting with the following commit
are vulnerable:
commit 9448d2b3cec5c28a1c7594dec00c01d66bb61a8b
[Thu Oct 24 13:47:25 2013 +0700]
For a user to be affected, they must:
1) use Nippy to serialize untrusted input, and
2) have a gadget chain on their classpath.
I suspect (but haven't verified) that using Nippy's encryption feature prevents
exploitation in some cases, but if it's used to encrypt the communications between
two systems, one compromised endpoint could send encrypted but
attacker-controlled data to the other.
Ysoserial [4] contains a list of some Java libraries with known gadget chains.
If any of those libraries can be found on the user's classpath, they are known
to be vulnerable. (Ysoserial's list is not exhaustive, so even if a user doesn't
have these particular libraries on their classpath, they may still have some
other gadget chains loaded.)
Unfortunately Clojure versions before 1.9 contained a gadget chain in the
standard library [5][6], so all Nippy users running Clojure 1.8 or earlier
are vulnerable. (Note that users of later Clojure versions may or may not
be vulnerable, depending on whether they have gadget chains from other
libraries on their classpath.)
[4] https://github.com/frohoff/ysoserial
[5] https://groups.google.com/forum/#!msg/clojure/WaL3hHzsevI/7zHU-L7LBQAJ
[6] https://clojure.atlassian.net/browse/CLJ-2204
2020-07-23 10:33:05 +00:00
|
|
|
|
{:class-name class-name}))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
(defn- read-record [in class-name]
|
|
|
|
|
|
(let [content (thaw-from-in! in)]
|
|
|
|
|
|
(try
|
2017-03-07 15:08:16 +00:00
|
|
|
|
(let [class (clojure.lang.RT/classForName class-name)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
method (.getMethod class "create" class-method-sig)]
|
|
|
|
|
|
(.invoke method class (into-array Object [content])))
|
|
|
|
|
|
(catch Exception e
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unthawable
|
|
|
|
|
|
{:type :record
|
|
|
|
|
|
:cause :exception
|
|
|
|
|
|
|
|
|
|
|
|
:class-name class-name
|
|
|
|
|
|
:content content
|
|
|
|
|
|
:exception e}}))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(defn- read-type [in class-name]
|
|
|
|
|
|
(try
|
|
|
|
|
|
(let [aclass (clojure.lang.RT/classForName class-name)
|
|
|
|
|
|
nbasis
|
|
|
|
|
|
(let [basis-method (.getMethod aclass "getBasis" nil)
|
|
|
|
|
|
basis (.invoke basis-method nil nil)]
|
|
|
|
|
|
(count basis))
|
|
|
|
|
|
|
|
|
|
|
|
cvalues (object-array nbasis)]
|
|
|
|
|
|
|
|
|
|
|
|
(enc/reduce-n
|
|
|
|
|
|
(fn [_ i] (aset cvalues i (thaw-from-in! in)))
|
|
|
|
|
|
nil nbasis)
|
|
|
|
|
|
|
|
|
|
|
|
(let [ctors (.getConstructors aclass)
|
|
|
|
|
|
^Constructor ctor (aget ctors 0) ; Impl. detail? Ref. https://goo.gl/XWmckR
|
|
|
|
|
|
]
|
|
|
|
|
|
(.newInstance ctor cvalues)))
|
|
|
|
|
|
|
|
|
|
|
|
(catch Exception e
|
2020-09-10 10:08:28 +00:00
|
|
|
|
{:nippy/unthawable
|
|
|
|
|
|
{:type :type
|
|
|
|
|
|
:cause :exception
|
|
|
|
|
|
|
|
|
|
|
|
:class-name class-name
|
|
|
|
|
|
:exception e}})))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
|
2015-09-29 07:36:23 +00:00
|
|
|
|
(defn thaw-from-in!
|
|
|
|
|
|
"Deserializes a frozen object from given DataInput to its original Clojure
|
2016-07-18 04:30:38 +00:00
|
|
|
|
data type.
|
|
|
|
|
|
|
|
|
|
|
|
This is a low-level util: in most cases you'll want `thaw` instead."
|
2015-09-29 07:36:23 +00:00
|
|
|
|
[^DataInput data-input]
|
|
|
|
|
|
(let [in data-input
|
|
|
|
|
|
type-id (.readByte in)]
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(when-debug (println (str "thaw-from-in!: " type-id)))
|
2013-12-06 11:40:13 +00:00
|
|
|
|
(try
|
2015-09-29 07:30:25 +00:00
|
|
|
|
(enc/case-eval type-id
|
2013-12-06 11:40:13 +00:00
|
|
|
|
|
2020-07-24 20:50:05 +00:00
|
|
|
|
id-reader-sm (read-edn (read-str in (read-sm-count in)))
|
|
|
|
|
|
id-reader-md (read-edn (read-str in (read-md-count in)))
|
|
|
|
|
|
id-reader-lg (read-edn (read-str in (read-lg-count in)))
|
|
|
|
|
|
id-reader-lg2 (read-edn (read-str in (read-lg-count in)))
|
|
|
|
|
|
id-record-sm (read-record in (read-str in (read-sm-count in)))
|
|
|
|
|
|
id-record-md (read-record in (read-str in (read-md-count in)))
|
|
|
|
|
|
id-record-lg (read-record in (read-str in (read-lg-count in)))
|
|
|
|
|
|
|
|
|
|
|
|
id-serializable-q-sm (read-serializable-q in (read-str in (read-sm-count in)))
|
|
|
|
|
|
id-serializable-q-md (read-serializable-q in (read-str in (read-md-count in)))
|
|
|
|
|
|
|
|
|
|
|
|
id-serializable-uq-sm (read-serializable-uq in (read-str in (read-sm-count in)))
|
|
|
|
|
|
id-serializable-uq-md (read-serializable-uq in (read-str in (read-md-count in)))
|
|
|
|
|
|
id-serializable-uq-lg (read-serializable-uq in (read-str in (read-lg-count in)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
id-type (read-type in (thaw-from-in! in))
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
id-nil nil
|
|
|
|
|
|
id-true true
|
|
|
|
|
|
id-false false
|
|
|
|
|
|
id-char (.readChar in)
|
|
|
|
|
|
id-meta (let [m (thaw-from-in! in)]
|
2020-07-25 08:15:27 +00:00
|
|
|
|
(if *incl-metadata?*
|
|
|
|
|
|
(with-meta (thaw-from-in! in) m)
|
|
|
|
|
|
(do (thaw-from-in! in))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-cached-0 (thaw-cached 0 in)
|
2016-04-12 17:52:15 +00:00
|
|
|
|
id-cached-1 (thaw-cached 1 in)
|
|
|
|
|
|
id-cached-2 (thaw-cached 2 in)
|
|
|
|
|
|
id-cached-3 (thaw-cached 3 in)
|
|
|
|
|
|
id-cached-4 (thaw-cached 4 in)
|
2016-10-28 09:41:38 +00:00
|
|
|
|
id-cached-5 (thaw-cached 5 in)
|
|
|
|
|
|
id-cached-6 (thaw-cached 6 in)
|
|
|
|
|
|
id-cached-7 (thaw-cached 7 in)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-cached-sm (thaw-cached (read-sm-count in) in)
|
|
|
|
|
|
id-cached-md (thaw-cached (read-md-count in) in)
|
2016-04-12 17:52:15 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
id-bytes-0 (byte-array 0)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-bytes-sm (read-bytes in (read-sm-count in))
|
|
|
|
|
|
id-bytes-md (read-bytes in (read-md-count in))
|
|
|
|
|
|
id-bytes-lg (read-bytes in (read-lg-count in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
id-objects-lg (read-objects (object-array (read-lg-count in)) in)
|
|
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
id-str-0 ""
|
2020-07-24 20:50:05 +00:00
|
|
|
|
id-str-sm (read-str in (read-sm-count in))
|
|
|
|
|
|
id-str-md (read-str in (read-md-count in))
|
|
|
|
|
|
id-str-lg (read-str in (read-lg-count in))
|
|
|
|
|
|
|
|
|
|
|
|
id-kw-sm (keyword (read-str in (read-sm-count in)))
|
|
|
|
|
|
id-kw-md (keyword (read-str in (read-md-count in)))
|
|
|
|
|
|
id-kw-lg (keyword (read-str in (read-lg-count in)))
|
|
|
|
|
|
|
|
|
|
|
|
id-sym-sm (symbol (read-str in (read-sm-count in)))
|
|
|
|
|
|
id-sym-md (symbol (read-str in (read-md-count in)))
|
|
|
|
|
|
id-sym-lg (symbol (read-str in (read-lg-count in)))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
id-regex (re-pattern (thaw-from-in! in))
|
|
|
|
|
|
|
|
|
|
|
|
id-vec-0 []
|
|
|
|
|
|
id-vec-2 [(thaw-from-in! in) (thaw-from-in! in)]
|
|
|
|
|
|
id-vec-3 [(thaw-from-in! in) (thaw-from-in! in) (thaw-from-in! in)]
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-vec-sm (read-into [] in (read-sm-count in))
|
|
|
|
|
|
id-vec-md (read-into [] in (read-md-count in))
|
|
|
|
|
|
id-vec-lg (read-into [] in (read-lg-count in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
id-set-0 #{}
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-set-sm (read-into #{} in (read-sm-count in))
|
|
|
|
|
|
id-set-md (read-into #{} in (read-md-count in))
|
|
|
|
|
|
id-set-lg (read-into #{} in (read-lg-count in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
id-map-0 {}
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-map-sm (read-kvs-into {} in (read-sm-count in))
|
|
|
|
|
|
id-map-md (read-kvs-into {} in (read-md-count in))
|
|
|
|
|
|
id-map-lg (read-kvs-into {} in (read-lg-count in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-queue (read-into (PersistentQueue/EMPTY) in (read-lg-count in))
|
|
|
|
|
|
id-sorted-set (read-into (sorted-set) in (read-lg-count in))
|
|
|
|
|
|
id-sorted-map (read-kvs-into (sorted-map) in (read-lg-count in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
id-list-0 '()
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-list-sm (into '() (rseq (read-into [] in (read-sm-count in))))
|
|
|
|
|
|
id-list-md (into '() (rseq (read-into [] in (read-md-count in))))
|
|
|
|
|
|
id-list-lg (into '() (rseq (read-into [] in (read-lg-count in))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
id-seq-0 (lazy-seq nil)
|
2016-04-13 04:57:50 +00:00
|
|
|
|
id-seq-sm (or (seq (read-into [] in (read-sm-count in))) (lazy-seq nil))
|
|
|
|
|
|
id-seq-md (or (seq (read-into [] in (read-md-count in))) (lazy-seq nil))
|
|
|
|
|
|
id-seq-lg (or (seq (read-into [] in (read-lg-count in))) (lazy-seq nil))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
id-byte (.readByte in)
|
|
|
|
|
|
id-short (.readShort in)
|
|
|
|
|
|
id-integer (.readInt in)
|
|
|
|
|
|
id-long-zero 0
|
|
|
|
|
|
id-long-sm (long (.readByte in))
|
|
|
|
|
|
id-long-md (long (.readShort in))
|
|
|
|
|
|
id-long-lg (long (.readInt in))
|
|
|
|
|
|
id-long-xl (.readLong in)
|
|
|
|
|
|
|
|
|
|
|
|
id-bigint (bigint (read-biginteger in))
|
|
|
|
|
|
id-biginteger (read-biginteger in)
|
|
|
|
|
|
|
|
|
|
|
|
id-float (.readFloat in)
|
2016-04-12 17:52:15 +00:00
|
|
|
|
id-double-zero 0.0
|
2016-04-07 05:49:26 +00:00
|
|
|
|
id-double (.readDouble in)
|
2016-04-14 05:02:27 +00:00
|
|
|
|
id-bigdec (BigDecimal. ^BigInteger (read-biginteger in) (.readInt in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
|
|
|
|
|
|
id-ratio (clojure.lang.Ratio.
|
|
|
|
|
|
(read-biginteger in)
|
|
|
|
|
|
(read-biginteger in))
|
|
|
|
|
|
|
|
|
|
|
|
id-date (Date. (.readLong in))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
id-uri (URI. (thaw-from-in! in))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
id-uuid (UUID. (.readLong in) (.readLong in))
|
|
|
|
|
|
|
|
|
|
|
|
;; Deprecated ------------------------------------------------------
|
2016-07-26 05:20:40 +00:00
|
|
|
|
id-boolean-depr1 (.readBoolean in)
|
|
|
|
|
|
id-sorted-map-depr1 (read-kvs-depr1 (sorted-map) in)
|
|
|
|
|
|
id-map-depr2 (read-kvs-depr1 {} in)
|
|
|
|
|
|
id-reader-depr1 (read-edn (.readUTF in))
|
|
|
|
|
|
id-str-depr1 (.readUTF in)
|
|
|
|
|
|
id-kw-depr1 (keyword (.readUTF in))
|
|
|
|
|
|
id-map-depr1 (apply hash-map
|
|
|
|
|
|
(enc/repeatedly-into [] (* 2 (.readInt in))
|
|
|
|
|
|
(fn [] (thaw-from-in! in))))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
;; -----------------------------------------------------------------
|
|
|
|
|
|
|
2016-04-13 17:13:33 +00:00
|
|
|
|
id-prefixed-custom (read-custom! in :prefixed (.readShort in))
|
|
|
|
|
|
|
|
|
|
|
|
(if (neg? type-id)
|
|
|
|
|
|
(read-custom! in nil type-id) ; Unprefixed custom type
|
|
|
|
|
|
(throw
|
|
|
|
|
|
(ex-info
|
|
|
|
|
|
(str "Unrecognized type id (" type-id "). Data frozen with newer Nippy version?")
|
|
|
|
|
|
{:type-id type-id}))))
|
2013-12-06 11:40:13 +00:00
|
|
|
|
|
|
|
|
|
|
(catch Exception e
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(throw (ex-info (str "Thaw failed against type-id: " type-id)
|
2014-04-05 11:30:28 +00:00
|
|
|
|
{:type-id type-id} e))))))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(let [head-sig head-sig] ; Not ^:const
|
|
|
|
|
|
(defn- try-parse-header [^bytes ba]
|
|
|
|
|
|
(let [len (alength ba)]
|
|
|
|
|
|
(when (> len 4)
|
|
|
|
|
|
(let [-head-sig (java.util.Arrays/copyOf ba 3)]
|
|
|
|
|
|
(when (java.util.Arrays/equals -head-sig ^bytes head-sig)
|
|
|
|
|
|
;; Header appears to be well-formed
|
|
|
|
|
|
(let [meta-id (aget ba 3)
|
|
|
|
|
|
data-ba (java.util.Arrays/copyOfRange ba 4 len)]
|
|
|
|
|
|
[data-ba (get head-meta meta-id {:unrecognized-meta? true})])))))))
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
|
|
|
|
|
(defn- get-auto-compressor [compressor-id]
|
|
|
|
|
|
(case compressor-id
|
|
|
|
|
|
nil nil
|
|
|
|
|
|
:snappy snappy-compressor
|
|
|
|
|
|
:lzma2 lzma2-compressor
|
|
|
|
|
|
:lz4 lz4-compressor
|
|
|
|
|
|
:no-header (throw (ex-info ":auto not supported on headerless data." {}))
|
2020-07-24 17:38:16 +00:00
|
|
|
|
:else (throw (ex-info ":auto not supported for non-standard compressors." {}))
|
|
|
|
|
|
(do (throw (ex-info (str "Unrecognized :auto compressor id: " compressor-id)
|
|
|
|
|
|
{:compressor-id compressor-id})))))
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
|
|
|
|
|
(defn- get-auto-encryptor [encryptor-id]
|
|
|
|
|
|
(case encryptor-id
|
2020-07-24 17:38:16 +00:00
|
|
|
|
nil nil
|
|
|
|
|
|
:aes128-gcm-sha512 aes128-gcm-encryptor
|
|
|
|
|
|
:aes128-cbc-sha512 aes128-cbc-encryptor
|
|
|
|
|
|
:no-header (throw (ex-info ":auto not supported on headerless data." {}))
|
|
|
|
|
|
:else (throw (ex-info ":auto not supported for non-standard encryptors." {}))
|
|
|
|
|
|
(do (throw (ex-info (str "Unrecognized :auto encryptor id: " encryptor-id)
|
|
|
|
|
|
{:encryptor-id encryptor-id})))))
|
2013-06-13 15:40:44 +00:00
|
|
|
|
|
2015-10-06 06:32:43 +00:00
|
|
|
|
(def ^:private err-msg-unknown-thaw-failure
|
|
|
|
|
|
"Decryption/decompression failure, or data unfrozen/damaged.")
|
|
|
|
|
|
|
|
|
|
|
|
(def ^:private err-msg-unrecognized-header
|
|
|
|
|
|
"Unrecognized (but apparently well-formed) header. Data frozen with newer Nippy version?")
|
|
|
|
|
|
|
2016-04-14 03:43:09 +00:00
|
|
|
|
(defn fast-thaw
|
|
|
|
|
|
"Like `thaw` but:
|
|
|
|
|
|
- Drops all support for compression and encryption
|
|
|
|
|
|
- Supports only data frozen with `fast-freeze`
|
|
|
|
|
|
|
2020-07-25 08:19:00 +00:00
|
|
|
|
Equivalent to (but a little faster than) `thaw` with opts:
|
|
|
|
|
|
- :compressor nil
|
|
|
|
|
|
- :encryptor nil
|
|
|
|
|
|
- :no-header? true"
|
|
|
|
|
|
|
2016-04-14 03:43:09 +00:00
|
|
|
|
[^bytes ba]
|
|
|
|
|
|
(let [dis (DataInputStream. (ByteArrayInputStream. ba))]
|
2016-04-14 04:23:31 +00:00
|
|
|
|
(with-cache (thaw-from-in! dis))))
|
2016-04-14 03:43:09 +00:00
|
|
|
|
|
2013-06-13 05:12:28 +00:00
|
|
|
|
(defn thaw
|
2016-03-10 13:55:51 +00:00
|
|
|
|
"Deserializes a frozen Nippy byte array to its original Clojure data type.
|
|
|
|
|
|
To thaw custom types, extend the Clojure reader or see `extend-thaw`.
|
2015-10-06 06:12:25 +00:00
|
|
|
|
|
|
|
|
|
|
** By default, supports data frozen with Nippy v2+ ONLY **
|
|
|
|
|
|
Add `{:v1-compatibility? true}` option to support thawing of data frozen with
|
|
|
|
|
|
legacy versions of Nippy.
|
2014-04-05 11:30:28 +00:00
|
|
|
|
|
|
|
|
|
|
Options include:
|
2015-10-06 06:12:25 +00:00
|
|
|
|
:v1-compatibility? - support data frozen by legacy versions of Nippy?
|
2015-10-06 08:04:19 +00:00
|
|
|
|
:compressor - :auto (checks header, default) an ICompressor, or nil
|
|
|
|
|
|
:encryptor - :auto (checks header, default), an IEncryptor, or nil"
|
2015-09-26 04:31:49 +00:00
|
|
|
|
|
|
|
|
|
|
([ba] (thaw ba nil))
|
|
|
|
|
|
([^bytes ba
|
2020-07-24 17:38:16 +00:00
|
|
|
|
{:as opts
|
2020-07-25 08:19:00 +00:00
|
|
|
|
:keys [v1-compatibility? compressor encryptor password
|
2020-09-10 16:42:25 +00:00
|
|
|
|
serializable-allowlist incl-metadata?]
|
2015-10-06 06:12:25 +00:00
|
|
|
|
:or {compressor :auto
|
2020-07-24 17:38:16 +00:00
|
|
|
|
encryptor :auto}}]
|
2015-09-26 04:31:49 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(assert (not (get opts :headerless-meta))
|
2015-09-26 04:31:49 +00:00
|
|
|
|
":headerless-meta `thaw` opt removed in Nippy v2.7+")
|
|
|
|
|
|
|
2020-09-10 16:42:25 +00:00
|
|
|
|
(call-with-bindings :thaw opts
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(fn []
|
|
|
|
|
|
|
|
|
|
|
|
(let [v2+? (not v1-compatibility?)
|
|
|
|
|
|
no-header? (get opts :no-header?) ; Intentionally undocumented
|
|
|
|
|
|
ex (fn ex
|
|
|
|
|
|
([ msg] (ex nil msg))
|
|
|
|
|
|
([e msg] (throw (ex-info (str "Thaw failed: " msg)
|
|
|
|
|
|
{:opts (assoc opts
|
|
|
|
|
|
:compressor compressor
|
|
|
|
|
|
:encryptor encryptor)}
|
|
|
|
|
|
e))))
|
|
|
|
|
|
|
|
|
|
|
|
thaw-data
|
|
|
|
|
|
(fn [data-ba compressor-id encryptor-id ex-fn]
|
|
|
|
|
|
(let [compressor (if (identical? compressor :auto)
|
|
|
|
|
|
(get-auto-compressor compressor-id)
|
|
|
|
|
|
compressor)
|
|
|
|
|
|
encryptor (if (identical? encryptor :auto)
|
|
|
|
|
|
(get-auto-encryptor encryptor-id)
|
|
|
|
|
|
encryptor)]
|
|
|
|
|
|
|
|
|
|
|
|
(when (and encryptor (not password))
|
|
|
|
|
|
(ex "Password required for decryption."))
|
|
|
|
|
|
|
|
|
|
|
|
(try
|
|
|
|
|
|
(let [ba data-ba
|
|
|
|
|
|
ba (if encryptor (decrypt encryptor password ba) ba)
|
|
|
|
|
|
ba (if compressor (decompress compressor ba) ba)
|
|
|
|
|
|
dis (DataInputStream. (ByteArrayInputStream. ba))]
|
|
|
|
|
|
|
|
|
|
|
|
(with-cache (thaw-from-in! dis)))
|
|
|
|
|
|
|
|
|
|
|
|
(catch Exception e (ex-fn e)))))
|
|
|
|
|
|
|
|
|
|
|
|
;; Hackish + can actually segfault JVM due to Snappy bug,
|
|
|
|
|
|
;; Ref. http://goo.gl/mh7Rpy - no better alternatives, unfortunately
|
|
|
|
|
|
thaw-v1-data
|
|
|
|
|
|
(fn [data-ba ex-fn]
|
|
|
|
|
|
(thaw-data data-ba :snappy nil
|
|
|
|
|
|
(fn [_] (thaw-data data-ba nil nil (fn [_] (ex-fn nil))))))]
|
|
|
|
|
|
|
|
|
|
|
|
(if no-header?
|
|
|
|
|
|
(if v2+?
|
|
|
|
|
|
(thaw-data ba :no-header :no-header (fn [e] (ex e err-msg-unknown-thaw-failure)))
|
|
|
|
|
|
(thaw-data ba :no-header :no-header
|
|
|
|
|
|
(fn [e] (thaw-v1-data ba (fn [_] (ex e err-msg-unknown-thaw-failure))))))
|
|
|
|
|
|
|
|
|
|
|
|
;; At this point we assume that we have a header iff we have v2+ data
|
|
|
|
|
|
(if-let [[data-ba {:keys [compressor-id encryptor-id unrecognized-meta?]
|
|
|
|
|
|
:as head-meta}] (try-parse-header ba)]
|
|
|
|
|
|
|
|
|
|
|
|
;; A well-formed header _appears_ to be present (it's possible though
|
|
|
|
|
|
;; unlikely that this is a fluke and data is actually headerless):
|
|
|
|
|
|
(if v2+?
|
|
|
|
|
|
(if unrecognized-meta?
|
|
|
|
|
|
(ex err-msg-unrecognized-header)
|
|
|
|
|
|
(thaw-data data-ba compressor-id encryptor-id
|
|
|
|
|
|
(fn [e] (ex e err-msg-unknown-thaw-failure))))
|
|
|
|
|
|
|
|
|
|
|
|
(if unrecognized-meta?
|
|
|
|
|
|
(thaw-v1-data ba (fn [_] (ex err-msg-unrecognized-header)))
|
|
|
|
|
|
(thaw-data data-ba compressor-id encryptor-id
|
|
|
|
|
|
(fn [e] (thaw-v1-data ba (fn [_] (ex e err-msg-unknown-thaw-failure)))))))
|
|
|
|
|
|
|
|
|
|
|
|
;; Well-formed header definitely not present
|
|
|
|
|
|
(if v2+?
|
|
|
|
|
|
(ex err-msg-unknown-thaw-failure)
|
|
|
|
|
|
(thaw-v1-data ba (fn [_] (ex err-msg-unknown-thaw-failure)))))))))))
|
2015-10-06 06:32:43 +00:00
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(thaw (freeze "hello"))
|
|
|
|
|
|
(thaw (freeze "hello" {:compressor nil}))
|
|
|
|
|
|
(thaw (freeze "hello" {:password [:salted "p"]})) ; ex: no pwd
|
|
|
|
|
|
(thaw (freeze "hello") {:password [:salted "p"]}))
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2013-08-02 08:20:14 +00:00
|
|
|
|
;;;; Custom types
|
|
|
|
|
|
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(defn- assert-custom-type-id [custom-type-id]
|
|
|
|
|
|
(assert (or (keyword? custom-type-id)
|
|
|
|
|
|
(and (integer? custom-type-id) (<= 1 custom-type-id 128)))))
|
|
|
|
|
|
|
2014-07-06 06:47:38 +00:00
|
|
|
|
(defn- coerce-custom-type-id
|
2020-07-25 10:54:50 +00:00
|
|
|
|
"* +ive byte id -> -ive byte id (for unprefixed custom types)
|
|
|
|
|
|
* Keyword id -> Short hash id (for prefixed custom types)"
|
2015-04-19 03:48:01 +00:00
|
|
|
|
[custom-type-id]
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(assert-custom-type-id custom-type-id)
|
|
|
|
|
|
(if-not (keyword? custom-type-id)
|
2015-04-19 03:48:01 +00:00
|
|
|
|
(int (- ^long custom-type-id))
|
2015-10-06 08:04:19 +00:00
|
|
|
|
(let [^int hash-id (hash custom-type-id)
|
2014-07-04 13:05:18 +00:00
|
|
|
|
short-hash-id (if (pos? hash-id)
|
|
|
|
|
|
(mod hash-id Short/MAX_VALUE)
|
|
|
|
|
|
(mod hash-id Short/MIN_VALUE))]
|
|
|
|
|
|
;; Make sure hash ids can't collide with byte ids (unlikely anyway):
|
|
|
|
|
|
(assert (not (<= -128 short-hash-id -1))
|
|
|
|
|
|
"Custom type id hash collision; please choose a different id")
|
2020-07-25 10:54:50 +00:00
|
|
|
|
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(int short-hash-id))))
|
|
|
|
|
|
|
|
|
|
|
|
(comment (coerce-custom-type-id 77)
|
|
|
|
|
|
(coerce-custom-type-id :foo/bar))
|
|
|
|
|
|
|
2013-08-02 08:20:14 +00:00
|
|
|
|
(defmacro extend-freeze
|
2014-04-05 11:30:28 +00:00
|
|
|
|
"Extends Nippy to support freezing of a custom type (ideally concrete) with
|
2014-07-04 13:05:18 +00:00
|
|
|
|
given id of form:
|
2020-07-25 10:54:50 +00:00
|
|
|
|
|
|
|
|
|
|
* Keyword - 2 byte overhead, keywords hashed to 16 bit id
|
|
|
|
|
|
* ℕ∈[1, 128] - 0 byte overhead
|
2014-07-04 13:05:18 +00:00
|
|
|
|
|
2016-07-16 12:09:38 +00:00
|
|
|
|
NB: be careful about extending to interfaces, Ref. http://goo.gl/6gGRlU.
|
|
|
|
|
|
|
|
|
|
|
|
(defrecord MyRec [data])
|
|
|
|
|
|
(extend-freeze MyRec :foo/my-type [x data-output] ; Keyword id
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(.writeUTF [data-output] (:data x)))
|
|
|
|
|
|
;; or
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(extend-freeze MyRec 1 [x data-output] ; Byte id
|
2014-01-22 07:14:26 +00:00
|
|
|
|
(.writeUTF [data-output] (:data x)))"
|
2020-07-25 10:54:50 +00:00
|
|
|
|
|
2014-01-22 07:14:26 +00:00
|
|
|
|
[type custom-type-id [x out] & body]
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(assert-custom-type-id custom-type-id)
|
2017-12-21 09:08:24 +00:00
|
|
|
|
(let [write-id-form
|
|
|
|
|
|
(if (keyword? custom-type-id)
|
|
|
|
|
|
;; Prefixed [const byte id][cust hash id][payload]:
|
|
|
|
|
|
`(do (write-id ~out ~id-prefixed-custom)
|
|
|
|
|
|
(.writeShort ~out ~(coerce-custom-type-id custom-type-id)))
|
|
|
|
|
|
;; Unprefixed [cust byte id][payload]:
|
|
|
|
|
|
`(write-id ~out ~(coerce-custom-type-id custom-type-id)))]
|
|
|
|
|
|
|
|
|
|
|
|
`(extend-type ~type IFreezable1
|
|
|
|
|
|
(~'-freeze-without-meta! [~x ~(with-meta out {:tag 'java.io.DataOutput})]
|
|
|
|
|
|
~write-id-form
|
|
|
|
|
|
~@body))))
|
2013-08-02 08:20:14 +00:00
|
|
|
|
|
|
|
|
|
|
(defmacro extend-thaw
|
2014-07-04 13:05:18 +00:00
|
|
|
|
"Extends Nippy to support thawing of a custom type with given id:
|
|
|
|
|
|
(extend-thaw :foo/my-type [data-input] ; Keyword id
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(MyRec. (.readUTF data-input)))
|
2014-07-04 13:05:18 +00:00
|
|
|
|
;; or
|
|
|
|
|
|
(extend-thaw 1 [data-input] ; Byte id
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(MyRec. (.readUTF data-input)))"
|
2014-01-22 07:14:26 +00:00
|
|
|
|
[custom-type-id [in] & body]
|
2014-07-04 13:05:18 +00:00
|
|
|
|
(assert-custom-type-id custom-type-id)
|
2015-06-01 04:09:59 +00:00
|
|
|
|
`(do
|
|
|
|
|
|
(when (contains? *custom-readers* ~(coerce-custom-type-id custom-type-id))
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(println (str "Warning: resetting Nippy thaw for custom type with id: "
|
2015-06-01 04:09:59 +00:00
|
|
|
|
~custom-type-id)))
|
2020-09-11 08:22:31 +00:00
|
|
|
|
(alter-var-root #'*custom-readers*
|
2015-06-01 04:09:59 +00:00
|
|
|
|
(fn [m#]
|
|
|
|
|
|
(assoc m#
|
|
|
|
|
|
~(coerce-custom-type-id custom-type-id)
|
|
|
|
|
|
(fn [~(with-meta in {:tag 'java.io.DataInput})]
|
|
|
|
|
|
~@body))))))
|
|
|
|
|
|
|
|
|
|
|
|
(comment
|
2015-06-01 04:53:55 +00:00
|
|
|
|
*custom-readers*
|
2016-07-16 12:09:38 +00:00
|
|
|
|
(defrecord MyRec [data])
|
|
|
|
|
|
(extend-freeze MyRec 1 [x out] (.writeUTF out (:data x)))
|
|
|
|
|
|
(extend-thaw 1 [in] (MyRec. (.readUTF in)))
|
|
|
|
|
|
(thaw (freeze (MyRec. "Joe"))))
|
2013-08-02 08:20:14 +00:00
|
|
|
|
|
2013-06-12 18:14:46 +00:00
|
|
|
|
;;;; Stress data
|
2012-07-06 19:12:59 +00:00
|
|
|
|
|
2013-10-24 06:33:54 +00:00
|
|
|
|
(defrecord StressRecord [data])
|
2020-09-11 08:22:31 +00:00
|
|
|
|
(deftype StressType [data]
|
|
|
|
|
|
Object (equals [a b] (= (.-data a) (.-data ^StressType b))))
|
|
|
|
|
|
|
2015-09-28 09:25:43 +00:00
|
|
|
|
(def stress-data "Reference data used for tests & benchmarks"
|
2016-04-07 05:49:26 +00:00
|
|
|
|
{:bytes (byte-array [(byte 1) (byte 2) (byte 3)])
|
|
|
|
|
|
:nil nil
|
|
|
|
|
|
:true true
|
|
|
|
|
|
:false false
|
|
|
|
|
|
:char \ಬ
|
|
|
|
|
|
:str-short "ಬಾ ಇಲ್ಲಿ ಸಂಭವಿಸ"
|
|
|
|
|
|
:str-long (apply str (range 1000))
|
|
|
|
|
|
:kw :keyword
|
|
|
|
|
|
:kw-ns ::keyword
|
|
|
|
|
|
:sym 'foo
|
|
|
|
|
|
:sym-ns 'foo/bar
|
|
|
|
|
|
:regex #"^(https?:)?//(www\?|\?)?"
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
|
|
|
|
|
;;; Try reflect real-world data:
|
|
|
|
|
|
:lotsa-small-numbers (vec (range 200))
|
|
|
|
|
|
:lotsa-small-keywords (->> (java.util.Locale/getISOLanguages)
|
|
|
|
|
|
(mapv keyword))
|
|
|
|
|
|
:lotsa-small-strings (->> (java.util.Locale/getISOCountries)
|
2016-04-07 05:49:26 +00:00
|
|
|
|
(mapv #(.getDisplayCountry (java.util.Locale. "en" %))))
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
:queue (enc/queue [:a :b :c :d :e :f :g])
|
|
|
|
|
|
:queue-empty (enc/queue)
|
2015-09-29 09:02:46 +00:00
|
|
|
|
:sorted-set (sorted-set 1 2 3 4 5)
|
|
|
|
|
|
:sorted-map (sorted-map :b 2 :a 1 :d 4 :c 3)
|
|
|
|
|
|
|
|
|
|
|
|
:list (list 1 2 3 4 5 (list 6 7 8 (list 9 10)))
|
|
|
|
|
|
:list-quoted '(1 2 3 4 5 (6 7 8 (9 10)))
|
|
|
|
|
|
:list-empty (list)
|
|
|
|
|
|
:vector [1 2 3 4 5 [6 7 8 [9 10]]]
|
|
|
|
|
|
:vector-empty []
|
|
|
|
|
|
:map {:a 1 :b 2 :c 3 :d {:e 4 :f {:g 5 :h 6 :i 7}}}
|
|
|
|
|
|
:map-empty {}
|
|
|
|
|
|
:set #{1 2 3 4 5 #{6 7 8 #{9 10}}}
|
|
|
|
|
|
:set-empty #{}
|
|
|
|
|
|
:meta (with-meta {:a :A} {:metakey :metaval})
|
2016-04-07 05:49:26 +00:00
|
|
|
|
:nested [#{{1 [:a :b] 2 [:c :d] 3 [:e :f]} [] #{:a :b}}
|
|
|
|
|
|
#{{1 [:a :b] 2 [:c :d] 3 [:e :f]} [] #{:a :b}}
|
|
|
|
|
|
[1 [1 2 [1 2 3 [1 2 3 4 [1 2 3 4 5]]]]]]
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-04-07 05:49:26 +00:00
|
|
|
|
:lazy-seq (repeatedly 1000 rand)
|
2015-09-29 09:02:46 +00:00
|
|
|
|
:lazy-seq-empty (map identity '())
|
|
|
|
|
|
|
|
|
|
|
|
:byte (byte 16)
|
|
|
|
|
|
:short (short 42)
|
|
|
|
|
|
:integer (int 3)
|
|
|
|
|
|
:long (long 3)
|
|
|
|
|
|
:bigint (bigint 31415926535897932384626433832795)
|
|
|
|
|
|
|
|
|
|
|
|
:float (float 3.14)
|
|
|
|
|
|
:double (double 3.14)
|
|
|
|
|
|
:bigdec (bigdec 3.1415926535897932384626433832795)
|
|
|
|
|
|
|
|
|
|
|
|
:ratio 22/7
|
2020-07-24 17:38:16 +00:00
|
|
|
|
:uri (URI. "https://clojure.org/reference/data_structures")
|
2015-09-29 09:02:46 +00:00
|
|
|
|
:uuid (java.util.UUID/randomUUID)
|
|
|
|
|
|
:date (java.util.Date.)
|
2020-07-24 17:38:16 +00:00
|
|
|
|
:objects (object-array [1 "two" {:data "data"}])
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
2016-06-10 04:18:55 +00:00
|
|
|
|
:stress-record (StressRecord. "data")
|
2020-07-24 17:38:16 +00:00
|
|
|
|
:stress-type (StressType. "data")
|
2015-09-29 09:02:46 +00:00
|
|
|
|
|
|
|
|
|
|
;; Serializable
|
|
|
|
|
|
:throwable (Throwable. "Yolo")
|
|
|
|
|
|
:exception (try (/ 1 0) (catch Exception e e))
|
|
|
|
|
|
:ex-info (ex-info "ExInfo" {:data "data"})})
|
2013-06-12 18:14:46 +00:00
|
|
|
|
|
2014-01-21 07:33:35 +00:00
|
|
|
|
(def stress-data-comparable
|
2015-09-28 09:25:43 +00:00
|
|
|
|
"Reference data with stuff removed that breaks roundtrip equality"
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(dissoc stress-data :bytes :throwable :exception :ex-info :regex :objects))
|
2014-01-21 07:33:35 +00:00
|
|
|
|
|
|
|
|
|
|
(def stress-data-benchable
|
|
|
|
|
|
"Reference data with stuff removed that breaks reader or other utils we'll
|
2015-09-28 09:25:43 +00:00
|
|
|
|
be benching against"
|
2016-04-13 04:57:50 +00:00
|
|
|
|
(dissoc stress-data
|
|
|
|
|
|
:bytes :throwable :exception :ex-info :queue :queue-empty
|
2020-07-24 17:38:16 +00:00
|
|
|
|
:byte :stress-record :stress-type :regex :objects))
|
2014-01-21 07:33:35 +00:00
|
|
|
|
|
2014-02-14 16:06:53 +00:00
|
|
|
|
;;;; Tools
|
|
|
|
|
|
|
2015-09-28 09:25:43 +00:00
|
|
|
|
(defn inspect-ba "Alpha - subject to change"
|
2015-09-29 16:06:33 +00:00
|
|
|
|
([ba ] (inspect-ba ba nil))
|
|
|
|
|
|
([ba thaw-opts]
|
|
|
|
|
|
(when (enc/bytes? ba)
|
|
|
|
|
|
(let [[first2bytes nextbytes] (enc/ba-split ba 2)
|
|
|
|
|
|
?known-wrapper
|
2020-07-24 20:50:05 +00:00
|
|
|
|
(enc/cond
|
2016-04-14 06:07:59 +00:00
|
|
|
|
(enc/ba= first2bytes (.getBytes "\u0000<" charset)) :carmine/bin
|
|
|
|
|
|
(enc/ba= first2bytes (.getBytes "\u0000>" charset)) :carmine/clj)
|
2015-09-29 16:06:33 +00:00
|
|
|
|
|
|
|
|
|
|
unwrapped-ba (if ?known-wrapper nextbytes ba)
|
|
|
|
|
|
[data-ba ?nippy-header] (or (try-parse-header unwrapped-ba)
|
|
|
|
|
|
[unwrapped-ba :no-header])]
|
|
|
|
|
|
|
|
|
|
|
|
{:?known-wrapper ?known-wrapper
|
|
|
|
|
|
:?header ?nippy-header
|
|
|
|
|
|
:thawable? (try (thaw unwrapped-ba thaw-opts) true
|
|
|
|
|
|
(catch Exception _ false))
|
|
|
|
|
|
:unwrapped-ba unwrapped-ba
|
|
|
|
|
|
:data-ba data-ba
|
|
|
|
|
|
:unwrapped-len (alength ^bytes unwrapped-ba)
|
|
|
|
|
|
:ba-len (alength ^bytes ba)
|
|
|
|
|
|
:data-len (alength ^bytes data-ba)}))))
|
|
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(inspect-ba (freeze "hello"))
|
|
|
|
|
|
(seq (:data-ba (inspect-ba (freeze "hello")))))
|
2016-04-16 04:24:57 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(defn freeze-to-string
|
|
|
|
|
|
"Convenience util: like `freeze`, but returns a Base64-encoded string.
|
|
|
|
|
|
See also `thaw-from-string`."
|
|
|
|
|
|
([x ] (freeze-to-string x nil))
|
|
|
|
|
|
([x freeze-opts]
|
|
|
|
|
|
(let [ba (freeze x freeze-opts)]
|
|
|
|
|
|
(.encodeToString (java.util.Base64/getEncoder)
|
|
|
|
|
|
ba))))
|
|
|
|
|
|
|
|
|
|
|
|
(defn thaw-from-string
|
|
|
|
|
|
"Convenience util: like `thaw`, but takes a Base64-encoded string.
|
|
|
|
|
|
See also `freeze-to-string`."
|
|
|
|
|
|
([s ] (thaw-from-string s nil))
|
|
|
|
|
|
([^String s thaw-opts]
|
|
|
|
|
|
(let [ba (.decode (java.util.Base64/getDecoder) s)]
|
|
|
|
|
|
(thaw ba thaw-opts))))
|
2017-02-13 16:35:18 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(comment (thaw-from-string (freeze-to-string {:a :A :b [:B1 :B2]})))
|
2017-02-13 16:35:18 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(defn freeze-to-file
|
|
|
|
|
|
"Convenience util: like `freeze`, but writes to `(clojure.java.io/file <file>)`
|
|
|
|
|
|
and returns the byte array written.
|
|
|
|
|
|
See also `thaw-from-file`."
|
2017-02-13 16:35:18 +00:00
|
|
|
|
([file x ] (freeze-to-file file x nil))
|
|
|
|
|
|
([file x freeze-opts]
|
|
|
|
|
|
(let [^bytes ba (freeze x freeze-opts)]
|
|
|
|
|
|
(with-open [out (jio/output-stream (jio/file file))]
|
|
|
|
|
|
(.write out ba))
|
|
|
|
|
|
ba)))
|
|
|
|
|
|
|
|
|
|
|
|
(defn thaw-from-file
|
2020-07-24 17:38:16 +00:00
|
|
|
|
"Convenience util: like `thaw`, but reads from `(clojure.java.io/file <file>)`.
|
2017-02-13 16:35:18 +00:00
|
|
|
|
|
|
|
|
|
|
To thaw from a resource on classpath (e.g in Leiningen `resources` dir):
|
2020-07-24 17:38:16 +00:00
|
|
|
|
(thaw-from-file (clojure.java.io/resource \"my-resource-name.npy\"))
|
2017-02-13 16:35:18 +00:00
|
|
|
|
|
2020-07-24 17:38:16 +00:00
|
|
|
|
See also `freeze-to-file`."
|
2017-02-13 16:35:18 +00:00
|
|
|
|
([file ] (thaw-from-file file nil))
|
|
|
|
|
|
([file thaw-opts]
|
|
|
|
|
|
(let [file (jio/file file),
|
|
|
|
|
|
ba (byte-array (.length file))]
|
|
|
|
|
|
(with-open [in (DataInputStream. (jio/input-stream file))]
|
|
|
|
|
|
(.readFully in ba))
|
|
|
|
|
|
|
|
|
|
|
|
(thaw ba thaw-opts))))
|
|
|
|
|
|
|
|
|
|
|
|
(comment
|
|
|
|
|
|
(freeze-to-file "foo.npy" "hello, world!")
|
|
|
|
|
|
(thaw-from-file "foo.npy")
|
|
|
|
|
|
(thaw-from-file (jio/resource "foo.npy")))
|
|
|
|
|
|
|
2016-04-16 04:24:57 +00:00
|
|
|
|
;;;; Deprecated
|
|
|
|
|
|
|
2020-09-11 08:22:31 +00:00
|
|
|
|
(enc/deprecated
|
|
|
|
|
|
(def freeze-fallback-as-str "DEPRECATED, use `write-unfreezable`" write-unfreezable)
|
|
|
|
|
|
(defn set-freeze-fallback! "DEPRECATED, just use `alter-var-root`" [x] (alter-var-root #'*freeze-fallback* (constantly x)))
|
|
|
|
|
|
(defn set-auto-freeze-compressor! "DEPRECATED, just use `alter-var-root`" [x] (alter-var-root #'*auto-freeze-compressor* (constantly x)))
|
|
|
|
|
|
(defn swap-custom-readers! "DEPRECATED, just use `alter-var-root`" [f] (alter-var-root #'*custom-readers* f)))
|