漏洞编号
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
中的多个路由中。攻击者可利用该漏洞在未授权的情况下远程执行命令。
漏洞影响
- Oracle WebLogic Server10.3.6.0.0
- Oracle WebLogic Server12.1.3.0.0
环境搭建
- Windows Server 2012
- JDK 1.7.0_21
- Oracle WebLogic Server10.3.6.0.0
前置知识
在漏洞分析之前,我想先说两个和这个漏洞有关的前置知识:SOAP和Context Propagation
SOAP
SOAP全称Simple Object Access Protocol(简单对象访问协议),是一种XML协议,且通常基于HTTP。用于应用之间的通信和数据交换。
SOAP的消息是一个XML文档,它包含以下元素:
- 将XML文档标识为SOAP消息的Envelope元素,用于封装SOAP中的所有详细信息。
<soapenv:Envelope>
<!-- omit... -->
</soapenv:Envelope>
- 包含头信息的Header元素,头信息可以包含被调用应用需要使用的身份验证凭据,也可以包含SOAP消息中需要使用的复杂类型的定义。
<soapenv:Header>
<!-- omit... -->
<soapenv:Header>
- 包含调用和响应信息的Body元素,其中含有需要在Web服务和调用应用程序之间发送的实际数据
<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
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中的每一个标签名。如果包含可能被恶意利用的标签名则抛出错误。是黑名单修补的方式。
修复建议
- Oracle官方已推出安全更新,请参考以下官方安全通告下载并安装最新补丁:
https://www.oracle.com/technetwork/security-advisory/alert-cve-2019-2725-5466295.html
- 如果明确不使用wls-wsat.war和bea_wls9_async_response.war,建议删除并重启WebLogic。
- 通过访问策略控制禁止
/_async/*
与/wls-wsat/*
路径的URL访问。
参考
- http://www.cnvd.org.cn/webinfo/show/4989
- https://www.oracle.com/technetwork/security-advisory/alert-cve-2019-2725-5466295.html
- https://www.guru99.com/soap-simple-object-access-protocol.html
- https://docs.oracle.com/cd/E13222_01/wls/docs100/programming/context.html#wp1058673