CVE-2019-2725、CNVD-C-2019-48814
WebLogic是美国Oracle公司出品的一个Application Server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
此漏洞存在于 wls-wsat.war 和 bea_wls9_async_response.war 中的多个路由中。攻击者可利用该漏洞在未授权的情况下远程执行命令。
在漏洞分析之前,我想先说两个和这个漏洞有关的前置知识:SOAP和Context Propagation
SOAP全称Simple Object Access Protocol(简单对象访问协议),是一种XML协议,且通常基于HTTP。用于应用之间的通信和数据交换。
SOAP的消息是一个XML文档,它包含以下元素:
<soapenv:Envelope>
<!-- omit... -->
</soapenv:Envelope>
<soapenv:Header>
<!-- omit... -->
<soapenv:Header>
<soap:Body>
<GetSomeInfo>
<SomeInfoId>omit...</SomeInfoId>
</GetSomeInfo>
</soap:Body>
这些元素的结构如下图所示:
我们把这些元素合在一起,得到下面的例子:
<soapenv:Envelope>
<soapenv:Header>
<!-- omit... -->
<soapenv:Header>
<soap:Body>
<GetSomeInfo>
<SomeInfoId>omit...</SomeInfoId>
</GetSomeInfo>
</soap:Body>
</soapenv:Envelope>
更多有关信息可参考 SOAP Web Services Tutorial
Context propagation,我在这里翻译成上下文传递,它允许开发者将信息与应用程序相关联,在每一个请求中携带上下文信息。上下文传递的常见用例是将信息 从外界传递到应用程序来取代信息整合到应用程序的一部分 ,它的优势是保持应用程序本身干净,无需多余的API。上下文传递也通常被称作Work Areas、Work Contexts、和Application Transactions。
上下文传递经常用于应用程序的诊断和监控、应用程序的事务处理和负载均衡等。
可以使用如下例子创建上下文传递的应用:
服务端:
package examples.workarea;
// omit...
import weblogic.workarea.WorkContextMap;
import weblogic.workarea.WorkContext;
// omit...
@WebService(name="WorkAreaPortType",
serviceName="WorkAreaService",
targetNamespace="http://example.org")
@WLHttpTransport(contextPath="workarea",
serviceUri="WorkAreaService",
portName="WorkAreaPort")
public class WorkAreaImpl {
public final static String SESSION_ID = "session_id_key";
@WebMethod()
public String sayHello(String message) {
try {
WorkContextMap map = (WorkContextMap) new InitialContext().lookup("java:comp/WorkContextMap");
WorkContext localwc = map.get(SESSION_ID);
System.out.println("local context: " + localwc);
System.out.println("sayHello: " + message);
return "Here is the message: '" + message + "'";
} catch (Throwable t) {
return "error";
}
}
}
客户端:
package examples.workarea.client;
// omit...
import weblogic.workarea.WorkContextMap;
import weblogic.workarea.WorkContext;
import weblogic.workarea.PrimitiveContextFactory;
import weblogic.workarea.PropagationMode;
import weblogic.workarea.PropertyReadOnlyException;
// omit...
public class Main {
public final static String SESSION_ID= "session_id_key";
public static void main(String[] args)
throws ServiceException, RemoteException, NamingException, PropertyReadOnlyException{
WorkAreaService service = new WorkAreaService_Impl(args[0] + "?WSDL");
WorkAreaPortType port = service.getWorkAreaPort();
WorkContextMap map = (WorkContextMap)new InitialContext().lookup("java:comp/WorkContextMap");
WorkContext stringContext = PrimitiveContextFactory.create("A String Context");
// Put a string context
map.put(SESSION_ID, stringContext, PropagationMode.SOAP);
try {
String result = null;
result = port.sayHello("Hi there!");
System.out.println( "Got result: " + result );
} catch (RemoteException e) {
throw e;
}
}
}
更多有关信息可参考 Developing Applications With WebLogic Server
有了上面的前置知识,我们来详细分析Payload传入Weblogic中到底发生了什么。此次漏洞分析我使用了Java反编译工具JD-GUI配合IDEA远程调试。
以路由 /wls-wsat/CoordinatorPortType 为例,我们就从这个路由的定义入手。
反编译 wls-wsat.war 包,查看其中的 WEB-INF/web.xml 文件,可发现 /CoordinatorPortType 路由的定义位置:
可以看到其对应的 servlet-class 是 weblogic.wsee.wstx.wsat.v10.endpoint.CoordinatorPortTypePortImpl 。跟入其中,发现此接口是SOAP的实现。
其实现的接口如下图:
这样我们确认了 /wls-wsat/CoordinatorPortType 是SOAP Web Service
然后使用IDEA运行远程调试,将断点放在 WLSServletAdapter.class 128行, handle() 方法,执行Payload:
可发现请求已经到达其 HttpServletResponse 参数中。
方法调用了它的父类的 handle() 方法,父类的 handle() 方法又继续调用父类 handle() 方法,调用堆栈如下:
来到 HttpAdapter.class 152行的 handle() 方法,
这里主要代码为从队列中取出了一个对象并将其强制转换成 HttpAdapter.HttpToolkit 。
跟入队列的 take() 方法,因为队列为空,调用了 this.create() :
在 Adapter.class 中可看到 create() 函数调用了 Adapter.this.createToolkit() :
最终在 Toolkit 类中创建了 codec 和 head :
我们来看下 codec 和 head 分别是什么:
这里留意 codec 和 head 中的 tube ,稍后会用到。
然后回到 HttpAdapter.class ,调用 tk.handle(connection) ,随后使用刚才的 codec 解码器解码数据包,其调用了 xmlSoapCodec.decode() 方法:
然后在 StreamSOAPCodec.class 中解码SOAP XML:
解析的流程忽略不说,再次回到 HttpAdapter.class 的535行:
此处用到了刚才的 head ,调用其 process() 方法。
根据上面的分析,我们知道了 this.head 是 WSEndpointImpl.class 的对象,其中包含一个成员变量 tube , tube 是 WseeServerTube ,在 WSEndpointImpl.class 的299行创建了一个纤程,然后在303行调用纤程的 runSync() 方法,将 this.tube 传入。纤程中的 runSync() 这样做是为了以同步的方式实现纤程间的异步调用。参数 this.tube 定义了在纤程中要执行的操作,在这里即 WseeServerTube 。
我们跳过一些与纤程相关的操作,来到 WorkContextTube 的子类 WorkContextServerTube.class ,这个类可以看出是用来处理上下文传递(Context Propagation)信息的,来到43行的 readHeaderOld ,顾名思义,是用来读取SOAP中的Header的。
跟入方法,发现此时 var4 正是真正要执行的Payload:
到112行new了一个 WorkContextXmlInputAdapter 类的对象,补丁就在这个类中。113行调用 this.receive() ,继续跟入:
在 WorkContextLocalMap.class 中165行, receiveRequest() 方法,将输入的上下文作为参数调用了 WorkContextEntryImpl.readEntry() 方法, readEntry() 方法又调用 WorkContextXmlInputAdapter.class 的 readUTF() , readUTF() 调用了 this.xmlDecoder.readObject() ,完成了第一次反序列化。
反序列化后反射出Payload中的类的实例,具体反射过程不再跟踪,可以关注 Statement.class 中的 invokeInternal() 。反射后,调用其中的 readObject() ,完成第二次反序列化,触发命令执行:
此漏洞经历了两次反序列化,整个调用堆栈非常长。类似漏洞的调用堆栈网上已经有很多,可参考 Weblogic XMLDecoder RCE分析 。
在这里我觉得画一个简单的流程图去描述其调用过程会更清晰一些。
刚刚在漏洞分析中已经提到了补丁所在位置(WorkContextXmlInputAdapter.class),这个漏洞根本上也是补丁的绕过。我们用刚刚发布的4月份CPU和针对这个漏洞的新的补丁包作对比,发现只多了一个判断分支(左边为4月份CPU中已存在的补丁,右边为新的补丁):
从漏洞上看,在调用 WorkContextXmlInputAdapter 类构造方法时,调用 validate() 方法来校验XML中的每一个标签名。如果包含可能被恶意利用的标签名则抛出错误。是黑名单修补的方式。
https://www.oracle.com/technetwork/security-advisory/alert-cve-2019-2725-5466295.html
/_async/* 与 /wls-wsat/* 路径的URL访问。