0x01前言

菜的难受。。。周末的RCTF挂零,哭泣,对这官方给的writeup进行一下复现(给了docker好评)。

0x02正文

一.nextphp

上来给了一个webshell,?a=phpinfo();可以看到php的信息,但是尝试其他命令执行函数都失败了,应该都被禁用了,题目提示php7.4,那么这个新版本中添加了什么呢,我们从phpinfo();可以看到一些信息,dev,然后ffi是开启的,有preload.php预加载了。那么什么是预加载,什么是ffi呢,下面简短的介绍下。

preload预加载:

预加载功能是指在服务启动时,未运行任何应用程序代码之前,将一组PHP文件加载到内存中,甚至可以对框架进行预加载,以提高性能。如果对预加载代码进行修改,需要重启服务。通过修改php.ini中的opcache.preload 来选择预加载程序。使用方法如下:

preload.php文件中写入预加载的内容

通过phpinfo的信息我们看到加载了preload.php,并且知道了他的位置,那么我们就可以拿到?a=echo%20file_get_contents(“preload.php”);

看到就知道是要反序列化了,类A实现了Serializable这个接口,注意到接口函数中的unserialize()反序列化payload后,会执行run()函数,那么我们重点就是如何拼接出我们需要的命令,这里就需要利用的php7.4的新特性。

我们看下官方文档给的定义:

FFI is one of the features that made Python and LuaJIT very useful for fast prototyping. It allows calling C functions and using C data types from pure scripting language and therefore develop “system code” more productively. For PHP, FFI opens a way to write PHP extensions and bindings to C libraries in pure PHP.

大致意思就是利用FFI可以在php这样的脚本语言中调用c语言代码。

这里还有一点,Serialize这个接口的序列化serialize()函数会生成C格式C:ClassNameLen:”ClassName”:PayloadLen:{Payload}的,但是在反序列化的时候O格式也是可以被这个接口的unserialize函数反序列化的。官方文档是这么说的:

The __serialize() and __unserialize() methods reuse the O serialization format used by ordinary object serialization, as well as __sleep()/__wakeup(). This means that the data array returned by __serialize() will be stored as-if it represented object properties.

In principle, this makes existing strings serialized in O format fully interoperable with the new serialization mechanism, the data is just provided in a different way (for __wakeup() in properties, for __unserialize() as an explicit array). If a class has both __sleep() and __serialize(), then the latter will be preferred. If a class has both __wakeup() and __unserialize() then the latter will be preferred.

If a class both implements Serializable and __serialize()/__unserialize(), then serialization will prefer the new mechanism, while unserialization can make use of either, depending on whether the C (Serializable) or O (__unserialize) format is used. As such, old serialized strings encoded in C format can still be decoded, while new strings will be produced in O format.

那么我们就可以进行构造了

FFI::cdef创建一个FFI对象

FFI::cdef([string $cdef = “” [, string $lib = null]])

第一个参数是一个字符创,包含C语言中的一系列声明,第二个可选参数是要加载并与定义链接的共享库文件名。这里调用system()函数,从而执行run后,类中ret变量就可以利用system执行命令了.

最后我们就将flag利用/dev/tcp这个文件就行读出,我们vps上监听端口nc -lvnp port,最后拿到flag.

0x03总结

翻wp翻到去年北方姐姐的总结,看了后很羞愧,明明自己那么菜却还常常不努力,哎。