TongWeb ejbserver 反序列化漏洞分析
type
status
date
slug
summary
tags
category
icon
password
AI summary
前言
今天看到赛博发了一篇TongWeb的反序列化,TongWeb好久没爆洞了,赶紧夺开代码和环境看一眼 。根据官方通告可得知存在漏洞的是ejbserver接口,赶紧把环境搭起来。安装环境我放个链接,密码联系我获取吧
飞牛分享【TongWeb7.0.4.6_Enterprise_Linux.zip】,点击链接下载文件,App打开可转存到NAS:https://s.fnnas.net/s/9466bdc77b30401289

第一Sink点
根据补丁可以定位到com/tongweb/tongejb/server/httpd/ServerServlet.class


跟进this.ejbServer.service(in, out);
继续跟进this.server.service(inputStream, outputStream);
这里构建了ProtocolMetaData和ServerMetaData,会从输入流中读取
这里先关注ProtocolMetaData#readExternal()
读取前8字节,然后正则匹配协议头,如果不符合则抛出异常,所以我们需要
接下来看看ServerMetaData#readExternal(),这里采用了ois = new EjbObjectInputStream(cis);开始读取对象流了,并且序列化了
所以如下构造即可:
这里用URLDNS链验证:
第二Sink点
除了这里有readObject以外,在后面还有一个,这也就是为什么各家厂商复现的截图,有的响应是空的,有的响应是带序列化数据的。

这里需要写入一个requestType为oos.writeByte((byte)0);只要不是-1就行


这里需要requestType满足EJB_REQUEST,所以再写一个oos.writeByte((byte)0);


需要注意:如果我们要触发第二个sink的话,刚才的URI[]对象就要正常构造一个
继续跟进this.processEjbRequest(ois, clientProtocol);
这里又获取了一个值,得保证我们写入一个存在的值,这里随便写个23吧

后面这里this.deploymentId = (String) in.readObject();再次反序列化了,所以构造下面这个PoC就能打这个sink了

如何RCE?
学习Y4的链子
首先HashTable可以触发HashMap.equals(),下面来回顾一下
跟进reconstitutionPut(table, key, value);
当HashTable存放HashMap,并且HashMap存放的值为yy和zZ的时候,hashCode值相同。这样就能进入这个for循环,进而调用key.equals()
这里会调用m.get(key)。
根据https://xz.aliyun.com/news/14169文章提到的javax.swing.UIDefaults.TextAndMnemonicHashMap
当HashCode相同时,触发TextAndMnemonicHashMap.equals() -> TextAndMnemonicHashMap.get()
这里调用key.toString(); 如果TextAndMnemonicHashMap存放的key是com.tongweb.xbean.naming.context.ContextUtil$ReadOnlyBinding
那么就会触发ReadOnlyBinding.toString(),ReadOnlyBinding没重写toString(),那么调用父类的Binding.toString()
这里调用了getObject()
- 虽然代码是写在父类 Binding 里的,但 this.getObject() 这个调用遵循 Java 的多态(Polymorphism)规则。
- this 关键字指向的是当前运行时的实际对象。在我们的场景中,this 就是我们创建的那个 ReadOnlyBinding 的实例。
- JVM 会在运行时决定调用哪个 getObject() 方法。它会先检查 ReadOnlyBinding 是否重写了 getObject() 方法。
- 我们检查源码发现,ReadOnlyBinding 确实重写了 getObject() 方法:
- 因此,父类 Binding.toString() 中的 this.getObject() 调用,最终执行的是子类 ReadOnlyBinding 重写后的版本。
跟进ReadOnlyBinding.getObject()
ContextUtil.resolve 方法本质上是一个 JNDI (Java Naming and Directory Interface) 对象解析器。它的任务是根据一个“引用”(Reference)来查找并获取真正的对象。
在
NamingManager.getObjectInstance(reference, parsedName, nameCtx,nameCtx.getEnvironment());触发JNDI注入这里就引入了一个经典JNDI高版本不出网利用——https://tttang.com/archive/1405/
利用
org.apache.naming.factory.BeanFactory 打 javax.el.ELProcessor#eval核心原理是:
forceString是BeanFactory提供的一个特殊指令。它允许我们强制将一个属性名映射到任意一个方法名。通过forceString指令,我们可以设置ref.add(new StringRefAddr("forceString", "x=eval"));将属性 "x" 映射到方法 "eval",当为x赋值时,不会调用setX,而是调用eval,并把值作为参数。RefAddr 是干嘛的?简单来说,RefAddr 就像一个“键值对”,它存放在 Reference 这个“蓝图”里,用来提供创建对象所需的额外信息。RefAddr 有两个核心属性:
- type (String): 键,或者可以理解为“属性名”。
- content (Object): 值,或者可以理解为“属性值”。
StringRefAddr 是它的一个常用子类,专门用来存放字符串类型的值。你可以把 Reference 想象成一个完整的“购物清单”,而每一个 RefAddr 就是清单上的一行项目。例如,要配置一个数据库连接池,清单上可能会有:
- RefAddr(type="username", content="admin")
- RefAddr(type="password", content="password123")
- RefAddr(type="url", content="jdbc:mysql://localhost:3306/mydb")
JNDI触发时,会处理 ref 里的 RefAddr当 BeanFactory 拿到 Reference 这个蓝图后,它的主要工作就是:1、根据 className 创建一个空的对象实例(比如一个 ELProcessor 实例)。2、遍历 Reference 里的所有 RefAddr,然后根据这些“键值对”来配置和操作这个空对象。
Loading...