+6

Phân tích gadget CommonsCollections1 trong ysoserial

Để khai thác lỗ hổng Deserialization, ngoài việc phải kiểm soát được giá trị đầu vào để đưa vào hàm thực hiện deserialize, ta cũng cần phải có một Object mà khi nó được deserialize, nó sẽ gọi tới một loạt các Object khác, tới cuối cùng gọi tới hàm đại loại như Runtime.exec để thực thi lệnh do attacker điều khiển, người ta gọi đó là gadget chain.

Lúc mới biết tới lỗ hổng này, mình nghĩ rằng 1 Object chỉ cần được viết đại loại như sau:

import java.io.IOException;
public class CommandExecutor {
    public CommandExecutor(String command) {
        String command = "<command here>";
        try {
            Process process = Runtime.getRuntime().exec(command);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Sau đó, ta có 1 hàm Serialize(new CommandExecutor("whoami")); và gửi cái Object thu được lên server là sẽ thành công, và mình luôn đi theo lối suy nghĩ đó, không xem các gadget ở trong tool ysoserial hoạt động như thế nào, do vậy lúc đó không hề khai thác được bất kì một lỗ hổng nào liên quan đến Deserialization.

Đến một ngày có một người anh khai sáng cho mình về lỗ hổng này thì mình mới bắt đầu tìm hiểu xem cách các gadget được xây dựng nên, do vậy mình sẽ phân tích một gadget tiêu biểu trong ysoserial để làm tư liệu cho các bạn mới tìm hiểu về lỗi này.

1. Phân tích

Đây là chain của gadget này, lấy từ ysoserial: image.png

Để dễ dàng tìm hiểu, ta bắt đầu từ giữa của gadget, hàm LazyMap.get() trước. Hãy tưởng tượng rằng ta có thể gọi đến hàm get của lớp LazyMap với đầu vào là key image.png

Điều gì sẽ xảy ra tiếp theo? Object tên key là đầu vào của hàm get() sẽ được đưa vào hàm transform() của biến factory - là một Object Transformer image.png

Transformer này lại chỉ là một interface, tức là lúc trước khi chương trình được chạy, ta không thể biết được biến factory này thực sự là một instance của lớp nào image.png

Ở trong payload sử dụng ChainedTransformer, hàm transform() của nó trông như sau: image.png

Theo mô tả thì ChainedTransformer được sử dụng làm cầu nối để nối các Transformer khác lại với nhau image.png

Hàm transform() nhận đầu vào là Object, tương ứng với giá trị key phía trên, lại được đưa vào một hàm transform của một mảng instance khác của Transformer image.png

Gadget đã sử dụng 2 instance là ConstantTransformerInvokerTransformer.

Và đây là đoạn mã khai thác từ ysoserial: image.png

Đoạn mã này đã làm những gì? Đầu tiên nó tạo 1 mảng các Transformer. Mảng này đóng vai trò của iTransformers phía trên.

Với new ConstantTransformer(Runtime.class), ta sẽ có ConstantTransformer.transformer() trả về Runtime.class image.png

Với mỗi InvokeTransformer tiếp theo, nó sẽ trả về một method tương ứng với class. image.png

Ví dụ: new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), Thì một list các class là String, Class[], args là getRuntime và method là getMethod, do vậy kết quả return sẽ là getMethod.invoke(Runtime, "getRuntime"), điều này bằng với Runtime.getMethod("getRuntime", null), đây là một kĩ thuật Reflection trong Java.

Tóm tắt lại:

1. ConstantTransformer.transform() --> Runtime
2. InvokerTransformer.transform() --> getMethod.invoke(Runtime, "getRuntime") == Runtime.getMethod("getRuntime",null)
3. InvokerTransformer.transform() --> invoke.invoke(Runtime.getMethod("getRuntime",null), null) == Runtime.getMethod("getRuntime",null).invoke(null, null)
4. InvokerTransformer.transform() --> exec.invoke(Runtime.getMethod("getRuntime",null).invoke(null, null), args) == Runtime.getMethod("getRuntime",null).invoke(null, null).exec(args)

Như vậy đến cuối cùng ta có thể chạy được lệnh tùy ý.

Trở lại phần phía trên, theo payload trong ysoserial, bắt đầu với AnnotationInvocationHandler.readObject(), hàm này khi được gọi sẽ gọi tới hàm Map(Proxy).entrySet() rồi lại gọi AnnotationInvocationHandler.invoke() image.png

Điều này thực sự khá là lạ vì trong hàm entrySet() không có cách nào có thể gọi tới hàm invoke đó cả.

Thủ thuật ở đây rất thú vị image.png

Tải trọng đã tạo một proxy, dưới đây là các hàm liên quan trong ysoserial

    public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
        return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
    }
    
    public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
        return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
    }

    public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
        final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
        allIfaces[ 0 ] = iface;
        if ( ifaces.length > 0 ) {
            System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
        }
        return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
    }

với image.png Kết quả cuối cùng sẽ tạo thành một variable như sau: Map mapProxy = (Map) Proxy.newProxyInstance(AValidClass.class.getClassLoader(), new Class[] { Map.class }, new AnnotationInvocationHandler(lazyMap));

Đi vào hàm Proxy.newProxyInstance(), ta có thể thấy image.png

Một dynamic proxy Object đã được khởi tạo từ LazyMap với một AnnotationInvocationHandler. Như vậy từ Map(Proxy).entrySet() đã trở thành AnnotationInvocationHandler.invoke(mapProxy, "entrySet"). Và hàm invoke() ở đây đã thực hiện gọi đến LazyMap.get() (với việc vượt qua một số switch/case phía trước) image.png

2. Các thông tin khác

Việc thực hiện Deserialization thường sẽ gây ra các Exception không mong muốn, nhưng thực tế các class đều đã được gọi nên cuối cùng thì command sẽ được thực thi trước khi Exception xảy ra.

Có thể chặn các gadget bằng cách blacklist 1 trong những class tham gia vào trong gadget. Điều này có thể bị bypass bằng cách attacker tìm con đường vòng, không thông qua class đó nhưng vẫn có thể gọi tới class phía sau.

Sử dụng whitelist có hạn chế là sẽ ảnh hưởng khá nhiều tới hệ thống và phải cẩn trọng trong việc sử dụng whitelist, tránh bị thiếu các class cần thiết.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí