前段时间分析了JBoss 3873和4446端口下的反序列化,受影响的版本最晚已经是2011年发布的,而JBoss EAP 6.X及WildFly\JBoss AS 7.X等后续版本,它们反序列化相关的CVE就很少了。归根结底,是因为以上所说的后续版本弃用了原来的Remoting2协议,启用了Remoting3协议。本文以Remoting3的反序列化相关问题展开分析。
Remoting3是JBoss Remoting的下一代协议,它在具备上一代协议所具有的功能的同时,还引入了以下一些性功能。
通过抓包对比,正如官网介绍所说,Remoting3协议不同于Remoting2的几乎明文的Java序列化数据,并且在默认配置下需要先进行客户端的认证才可使用后续的EJB3服务。
前面所说的可拓展的传输协议,体现在JBoss上即为HTTP服务8080端口的。Remoting3协议支持两种EJB3服务监听模式:直接监听一个端口和复用HTTP服务的端口。前者是JBoss EAP 6.X和JBoss AS 7.X使用的模式,对应的scheme是remote://
,而后者是JBoss EAP 7.X和WildFly使用的模式,对应的scheme是http-remoting://
或http://
(视版本而定)。
端口复用这块从流量这看是比较简单,客户端先发送一个带Upgrade: jboss-remoting
头的HTTP请求,然后服务端返回101状态码切换协议,后续流量则与监听端口的模式无异。
Remoting3协议服务于EJB3,而EJB3与RMI类似,支持对象传参,涉及到对象参数必然会与序列化和反序列化扯上关系。要研究Remoting3协议的反序列化机制,首先得部署一个EJB3的服务。本文后续的分析均以WildFly 8.2.1.Final为例,下载相应的版本,在启动前先使用bin/add-user.sh
脚本添加一个用户。
将以下两个类编译为Jar包,并从9990端口登录到控制台,部署打包好的Jar包。
package com.illucit.ejbremote.server;import javax.ejb.Remote;
@Remote
public interface ExampleService {
public Object greet(Object object);
}
package com.illucit.ejbremote.server;import javax.ejb.Stateless;
@Stateless
public class ExampleServiceImpl implements ExampleService {
@Override
public Object greet(Object object) {
return object;
}
}
新建一个Java项目,并将WildFly的bin/client
目录下的jar包复制到项目的lib
目录,同时将以上两个类也加入到项目中。然后在resource
目录下添加jboss-ejb-client.properties
,填入以下配置内容。
remote.connections=default
remote.connection.default.host=192.168.78.132
remote.connection.default.port = 8080
remote.connection.default.username=<username>
remote.connection.default.password=<password>
最后添加以下类作为客户端,调用远程EJB3服务。
import com.illucit.ejbremote.server.ExampleService;import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Date;
import java.util.Hashtable;
public class Client {
public static void main(String[] args) throws Exception {
System.out.printf(String.valueOf(lookupExample().greet2(new Date())));
}
private static ExampleService lookupExample() throws NamingException {
final Hashtable<String, String> jndiProperties = new Hashtable();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
final Context context = new InitialContext(jndiProperties);
String url = "ejb:/ejb-remote-server/ExampleServiceImpl!" + ExampleService.class.getName();
System.out.println(url);
return (ExampleService) context.lookup(url);
}
}
![](屏幕截图 2022-06-24 180627.png)
首先用一个在服务端不存在的类作为参数,调用EJB3服务。在客户端抛出的异常信息的调用栈中,可以看到org.jboss.marshalling.AbstractObjectInput
的readObject
方法被调用。
在服务端该方法下断点,当前的类是AbstractObjectInput
的子类org.jboss.marshalling.river.RiverUnmarshaller
,反序列化调用自身的doReadObject
方法处理。
跟进到doReadObject
方法,代码中会有一个switch循环体,根据从输入流获取到的字节进入不同的分支。后续会被调用到的doReadNewObject
和doReadClassDescriptor
也会有一个switch循环体,想必就是官方所说的可拓展的打包策略。
通过控制输入流进入到switch循环判断的字节,从而可以控制返回类示例或者反序列化对象。笔者梳理了这些switch循环体的分支,整理了几个会返回反序列化对象的流程,他们无一例外的会直接readObject
反序列化或loadClass
反射类实例,此时他们的类加载器都是ModuleClassLoader
。
这是一个很特殊的类加载器,反序列化利用链中常见的类,比如UnicastRef
、CC链(程序中实际有用到这个依赖),甚至于原生反序列化链中的一些类都是无法加载的。
实测只有两个module路径下jar包里的部分类和jdk部分原生类可以被这个类加载器加载。在可以加载的类中能找到一些Sink类,比如ValueExpressionImpl
、MethodExpressionImpl
,在RichFaces的CVE-2018-12533中有用到这两个Sink类的反序列化链,但Source和Gadget类虽然有,但ModuleClassLoader
类加载器并不能加载。
反序列化这条路行不通,回看调用栈中处理消息的方法processMessage
。开头用反序列化获取appName
、moduleName
、distinctName
和beanName
,也就是客户端lookup
查询的url。虽说这种用法存在风险,在不知道部署了什么EJB3服务的情况下,也可以进行反序列化,但目前看来有难度,所以暂时也没有什么问题。
接着看后面的代码,有一个反射Method的操作,从EJB3部署信息获取到的ComponentView
,根据方法名和方法参数反射Method。回溯Method的来源,发现是与反序列化的locator
有关。这个locator
其实就是封装了客户端EJB3服务接口的EJBLocator
,那可以客户端使用恶意的EJB3服务接口,从而反射服务端类的方法?笔者也做了下尝试,但若想要反射的服务端类不是一个接口类,则会抛出一个内接口类的异常,即使是没有抛出异常,从部署信息获取ComponentView
这一步也没法获取服务端EJB3服务接口之外的内容。
Remoting3协议的分析原本是以挖掘协议漏洞为目的,但最终也没有发现什么太大的问题,不得不感叹现在JBoss\WildFly的版本比之前使用remoting2协议的版本安全性上升了一个台阶。翻看近年WildFly的CVE,有一个反序列化相关的CVE-2020-10740,没有验证机制使得可能通过EJB发起远程反序列化攻击,说的大概就是本文讨论的内容。查看在WildFly20.0发布的修复,仅仅是增加了黑名单验证。
除此之外,客户端还有个不大不小的问题,它的反序列化流程和服务端差不多,但客户端这里的类加载器就是普通的类加载器,可以用到部分的反序列化链,这场景就有点像RMI的反序列化服务端传来的恶意结果或异常。也许能用在反制、中间人攻击等场景。。
文中若有什么错误的地方,敬请师傅们斧正。
https://jbossremoting.jboss.org/remoting-3
https://paper.seebug.org/766/
https://github.com/illucIT/remote-ejb-example