一、漏洞描述
4月17日,国家信息安全漏洞共享平台(CNVD)公开了Weblogic反序列化远程代码执行漏洞(CNVD-C-2019-48814)。由于在反序列化处理输入信息的过程中存在缺陷,未经授权的攻击者可以发送精心构造的恶意 HTTP 请求,利用该漏洞获取服务器权限,实现远程代码执行。目前,POC已在野外公开(见参考链接)。官方紧急补丁(CVE-2019-2725)已于4月26日发布,请受影响主机及时修复漏洞。
受影响版本
OracleWebLogic Server 10.*
OracleWebLogic Server 12.1.3
影响组件:
bea_wls9_async_response.war
wsat.war
二、漏洞分析
根据国家信息安全漏洞共享平台(CNVD)漏洞公告,此漏洞存在于异步通讯服务,可通过访问路径/_async/AsyncResponseService,判断不安全组件是否开启。wls9_async_response.war包中的类由于使用注解方法调用了Weblogic原生处理Web服务的类,因此会受该漏洞影响:
为更好的理解漏洞成因,通过IDEA对WebLogic服务器远程动态调试(因为需要跟进原生类中的方法,需要在IDEA中指定WebLogic安装目录中的JDK文件夹),在ProcessBuilder类中打下断点,关键的调用栈过程如下所示:
调用栈非常深,下面解释一下几个关键的部分。首先是继承自HttpServlet的BaseWSServlet类,其中的service方法主要用于处理HTTP请求及其响应,通过HTTP协议发送的请求包封装在HttpServletRequest类的实例化对象var1中:
调用BaseWSServlet中定义的内部类AuthorizedInvoke的run()方法完成传入HTTP对象的权限验证过程:
若校验成功,则进入到SoapProcessor类的process方法中,通过调用HttpServletRequest类实例化对象var1的getMethod()方法获取HTTP请求类型,若为POST方法,则继续处理请求:
HTTP请求发送至SoapProcessor类的handlePost方法中:
privatevoid handlePost(BaseWSServlet var1, HttpServletRequest var2, HttpServletResponse var3) throws IOException {
assert var1.getPort() != null;
WsPort var4 = var1.getPort();
String var5 =var4.getWsdlPort().getBinding().getBindingType();
HttpServerTransport var6 = newHttpServerTransport(var2, var3);
WsSkel var7 = (WsSkel)var4.getEndpoint();
try {
Connection var8 = ConnectionFactory.instance().createServerConnection(var6, var5);
var7.invoke(var8, var4);
} catch (ConnectionExceptionvar9) {
this.sendError(var3, var9, "Failed to create connection");
} catch (Throwablevar10) {
this.sendError(var3, var10, "Unknown error");
}
}
为方便后续分析工作进行,在此先简单介绍一下SOAP协议内容及格式:SOAP(中文称之为简单对象访问协议),用于在WEB上交换结构化和固化的信息,是Web Service三要素之一,可以和现存的许多因特网协议和格式结合使用。下图展示SOAP消息封装的标准格式:
BaseWSServlet类实例化对象var1封装了基于HTTP协议的SOAP消息:
调用var1对象中定义的getPort()方法解析SOAP消息中的根元素Envelope(可把 XML 文档定义为 SOAP 消息),获取所调用服务的端口信息:
通过var4对象的getWsdlPort().getBinding().getBindingType()方法获取当前SOAP协议规范版本信息:
并将HttpServletRequest类的var2对象及HttpServletResponse类的对象var3传入到HttpServerTransport类构造函数中初始化实例对象var6统一处理后续HTTP请求及响应。
继续调用var4对象中getEndpoint()方法完成对SOAP消息中根元素Envelope解析并读取与其相关联的xmlns:soap命名空间,其后分别完成对SOAP Header元素和Body元素解析工作:
跟进WsSkel类中定义的invoke()方法,其中完成了ServerDispatcher类实例化过程,并调用setWsPort()方法指定服务请求地址,进入调试器查看WsPort对象var2的属性值,发现底层依靠HashMap数据结构保存请求服务的Address和URI,其中当前请求http://:7001/_async/AsyncResponseService服务:
在调试器中查看ServerDispatcher对象var5属性值,发现methodName属性中赋值了onAsyncDelivery方法名,在调用dispatch()方法时将调用上述服务中定义的该方法:
WorkAreaServerHandler类中的handleRequest()方法用于处理访问请求,通过WlMessageContext对象var2获取传入的MessageContext,调用var2对象的getHeaders()方法获取传入SOAP消息的Header元素,并最终将该元素传递到WorkAreaHeader对象var4中,可以在调试器中清晰看到元素内容的赋值:
新建WorkContextMapInterceptor对象var5,在其receiveRequest()方法中读入经WorkContextXmlInputAdapter适配器构造函数转换过后的var4对象字节数组输出流,经内部getMap() -> receiveRequest() -> readEntry() 方法处理后,将上述Content字段传入至WorkContextXmlInputAdapter类的readUTF()方法中:
readUTF()方法中调用WorkContextXmlInputAdapter类私有成员变量xmlDecoder的readObject()方法读取字节数组,经内部SAXParser类链式调用一系列解析器的parse()方法后,最终在com.sun.beans.ObjectHandler类定义的endElement()方法中完成XML文档元素解析过程,获取了有效类名oracle.toplink.internal.sessions.UnitOfWorkChangeSet:
在Security机制完成对类名权限校验后,利用Java反射机制,通过元类定义的newInstance()方法实现上述类的实例化过程:
同样通过反射包中的Constructor类调用构造器方法传入字节数组,为上述实例对象赋初值:
UnitOfWorkChangeSet对象完成初始化过程后,使用ByteArrayInputStream对象接收经构造函数传入的字节数组,再将ByteArrayInputStream对象byteIn转换为ObjectInputStream对象objectIn,并直接调用了objectIn对象的readObject()方法。由于WebLogic安装包中默认SDK为1.6版本,在JDK版本<=JDK7u21前提下存在Java原生类反序列化漏洞,使用ysoserial工具生成恶意序列化对象(以计算器程序为例),可在调试器中查看到当前所传入的序列化对象:
经readObject()方法反序列化恶意对象后通过在ProcessBuilder类的start()方法断点处查看command属性,发现成功传入“calc”字符串:
到此就完成了漏洞利用的全过程,下图演示了漏洞利用效果,通过反序列化漏洞成功运行了计算器程序:
三、补丁绕过
下面来分析下本次漏洞和以前所公布的CVE-2017-3506和CVE-2017-10271之间的关系,依旧从补丁diff着手,上述两个补丁都是在weblogic/wsee/workarea/WorkContextXmlInputAdapter.java中添加了validate方法。首先来看CVE-2017-3506补丁文件,其实现方法简单来说就是在调用startElement方法解析XML的过程中,如果解析到Element字段值为Object就抛出异常:
privatevoidvalidate(InputStream is){
WebLogicSAXParserFactoryfactory = new WebLogicSAXParserFactory();
try {
SAXParser parser =factory.newSAXParser();
parser.parse(is, newDefaultHandler() {
publicvoidstartElement(Stringuri, StringlocalName, String qName, Attributes attributes)throws SAXException {
if(qName.equalsIgnoreCase("object")) {
thrownewIllegalStateException("Invalid context type:object");
}
}
});
} catch(ParserConfigurationExceptionvar5) {
thrownewIllegalStateException("Parser Exception", var5);
} catch (SAXExceptionvar6) {
thrownewIllegalStateException("Parser Exception", var6);
} catch (IOExceptionvar7) {
thrownewIllegalStateException("Parser Exception", var7);
}
}
但上述这类采用黑名单的防护措施很快就被如下POC轻松绕过,因为其中不包含任何Object元素,但经XMLDecoder解析后依旧造成了远程代码执行:
<javaversion="1.4.0" class="java.beans.XMLDecoder">
<new class="java.lang.ProcessBuilder">
<string>calc</string><methodname="start" />
</new>
</java>
针对如上所示一系列bypass CVE-2017-3506补丁限制的POC的产生,官方在同年十月份发布了CVE-2017-10271补丁文件。和上述不同点在于本次更新中官方将object、new、method关键字继续加入到黑名单中,一旦解析XML元素过程中匹配到上述任意一个关键字就立即抛出运行时异常。但是针对void和array这两个元素是有选择性的抛异常,其中当解析到void元素后,还会进一步解析该元素中的属性名,若没有匹配上index关键字才会抛出异常。而针对array元素而言,在解析到该元素属性名匹配class关键字的前提下,还会解析该属性值,若没有匹配上byte关键字,才会抛出运行时异常:
publicvoidstartElement(String uri, String localName, String qName, Attributesattributes)throws SAXException {
if(qName.equalsIgnoreCase("object")) {
thrownewIllegalStateException("Invalid elementqName:object");
} elseif(qName.equalsIgnoreCase("new")) {
thrownewIllegalStateException("Invalid elementqName:new");
} elseif(qName.equalsIgnoreCase("method")) {
thrownewIllegalStateException("Invalid elementqName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass <attributes.getLength();++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))){
thrownewIllegalStateException("Invalid attribute forelementvoid:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 =attributes.getValue("class");
if(var9 != null &&!var9.equalsIgnoreCase("byte")) {
thrownewIllegalStateException("The value of class attribute is notvalid for array element.");
}
本次反序列化漏洞绕过以往补丁的关键点在于利用了Class元素指定任意类名,因为CVE-2017-10271补丁限制了带method属性的void元素,所以不能调用指定的方法,而只能调用完成类实例化过程的构造方法。在寻找利用链的过程中发现UnitOfWorkChangeSet类构造方法中直接调用了JDK原生类中的readObject()方法,并且其构造方法的接收参数恰好是字节数组,这就满足了上一个补丁中array标签的class属性值必须为byte的要求,再借助带index属性的void元素,完成向字节数组中赋值恶意序列化对象的过程,最终利用JDK 7u21反序列化漏洞造成了远程代码执行。通过巧妙的利用了void、array和Class这三个元素成功的打造了利用链,再次完美的绕过了CVE-2017-10271补丁限制,本次漏洞的发现进一步证明了依靠黑名单机制是一种不可靠的防护措施。
四、临时修复建议
官方目前已发布针对此漏洞的紧急修复补丁,可以采取以下4种方式进行防护。
及时打上官方CVE-2019-2725补丁包
官方已于4月26日公布紧急补丁包,下载地址如下:https://www.oracle.com/technetwork/security-advisory/alert-cve-2019-2725-5466295.html?from=timeline
升级本地JDK版本
因为Weblogic所采用的是其安装文件中默认1.6版本的JDK文件,属于存在反序列化漏洞的JDK版本,因此升级到JDK7u21以上版本可以避免由于Java原生类反序列化漏洞造成的远程代码执行。
配置URL访问控制策略
部署于公网的WebLogic服务器,可通过ACL禁止对/_async/*及/wls-wsat/*路径的访问。
删除不安全文件
删除wls9_async_response.war与wls-wsat.war文件及相关文件夹,并重启Weblogic服务。具体文件路径如下:
10.3.*版本:
12.1.3版本:
注:wls9_async_response.war及wls-wsat.war属于一级应用包,对其进行移除或更名操作可能造成未知的后果,Oracle官方不建议对其进行此类操作。若在直接删除此包的情况下应用出现问题,将无法得到Oracle产品部门的技术支持。请自行进行影响评估,并对此文件进行备份后,再执行此操作。