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访问。