深入解析WordPress-5.0.0 RCE漏洞复现过程中的图片写入问题

1. 原因

深入解析WordPress-5.0.0 RCE漏洞复现过程中的图片写入问题

复现分析WordPress-5.0.0 RCE时,在写图片的过程中,根据图片的dirname创建一个目录,然后根据basename写入图片。在目录创建成功的前提下,文件应该是可以写入的。但事实并非如此。在这个过程中,我必须在写目标图片之前先写一张辅助图片。其实这个辅助图片并不是很重要,重要的是这个辅助图片的目录的创建。

例如,该过程中需要写入的目标文件为:

首先你需要写一个

为什么会发生这种情况?假设直接写入目标文件,过程中会先创建目录:

其实这个过程并没有创建目录,因为判断已经是directoryalready了,下一步写图片是Imagick::writeImage,这里就会出现问题。文件路径无效。错误。因为目录/var/www/html/wordpress/wp-content/uploads/2019/03/1.jpg?这里不存在,这涉及到系统调用。由于系统的差异,系统处理函数的处理方式也不同。不同的。

例如,如果 Imagick::writeImage 写入 ./1?/../1.png, ./1? kali下,这个目录下会报错。具体系统调用如下:

首先判断文件的状态,然后调用openat打开文件,文件不存在。 AT_FDCWD 表示打开的文件位置是相对于当前目录的。这是我在做的过程中遇到的情况。

但在WORDPRESS IMAGE远程代码执行漏洞分析文章中,甚至是另一篇文章中。没有提到写图两次。是因为window和linux的区别吗?关于这个问题,我对mkdir进行了探索。我发现它实际上很有趣。

2. PHP内核&&系统差异:mkdir2.1 Linux &&PHP 7.3.2-3

mkdir('./1?/../1′,777,true);

mkdir('./1?/../1′,777, false);

当第三个参数$recursive为true时,目录可以被写入。先说一下这个参数的含义。 $recursive 用于循环创建目录。这是什么意思?当为 false 时,只能创建第一级目录,即目录连接器中的最后一个目录。当为 true 时,您可以创建多层目录,直到最后一个目录。例如./a/b/c 当abc不存在时,会通过系统函数mkdir循环创建目录,并创建abc。但是,如果为 false,a 处的目录将不存在,并且不会创建最后一个目录。 c.

然而,即使第一个 mkdir 为 true,1?未创建目录。这里我们从PHP内部mkdir执行状态和系统mkdir执行状态来探究。

2.1.1PHP_FUNTCION(mkdir)

PHP内部调整流程如下:

我们对分支发生的地方进行细分

/php-src/main/streams/plain_wrapper.c

2.1.1.1 $recursive =fasle

出现的分支是在$recursive的判断中。如果不需要创建循环,直接输入php_mkdir。

/php-src/ext/standard/file.c

跟进 php_mkdir_ex

首先它会检查open_basedir,然后它会进入VCWD_MKDIR。 VCWD_MKDIR 是一个宏命令,具有三个不同的定义:

一开始我并没有想太多。我按照gdb的流程,直接执行mkdir,会直接调用系统的_mkdir.mkdir(“./1?/../1″,01411)=-1ENOENT(Nosuch文件或目录)

会直接报错。正如所料,mkdir 在 Linux 系统下不允许以这种方式创建目录,并且会验证每个目录级别的有效性。回到分叉第一次出现的时候。

2.1.1.2 $递归=true

这里会输入expand_filepath_with_mode,这里其实很熟悉。我之前在查看路径处理时见过这个函数。它是一个扩展函数,将递归地扩展需要创建的目录。在此过程中,将首先评估相对目录和当前脚本执行目录。如果是绝对目录,则会被忽略。

其中,我们的相对目录是./1?/../1,它将变成/var/www/html/WordPress/wp-content/themes/4/5/6/./1?/../1

我当前所在的目录是 /var/www/html/WordPress/wp-content/themes/4/5/6

然后删除../,./,//。递归并向前移动相应的目录,将成为 /var/www/html/WordPress/wp-content/themes/4/5/6/1

然后将其传递给系统的 mkdir 函数。

该函数中win32和linux有不同的分支,但是在具体处理之前,win32判断目录名不能存在*,?

这里要注意!

附上strace,也验证了上诉分析过程。

1. mkdir(“/var/www/html/WordPress/wp-content/themes/4/5/6/1”,01411)=0

2.1.2 Linux 中的 Mkdir

在Linux中,简单的mkdir会逐层验证目录,然后创建一级目录。 mkdir还可以带参数-p,代表系统级循环的创建目录。

执行 mkdir -p 时:

strace-fe trace=mkdir mkdir-p./1?/../1

mkdir(“1?”,0777)=0

mkdir(“1”,0777)=0

我们可以看到它并没有像PHP内部那样展开然后处理。会根据输入目录逐层创建。

2.2 窗口&& PHP 7.0.12

这是我想探讨的一个重要问题。在我前面提到的文章中,作者在window下实验当$recursive为false时创建成功,结果恰恰相反。当他的解释是错误的时候,作者不会逐层判断,但事实真的是这样吗?

然后我也做了一个验证实验,在Windows上使用PHP 5.6来做这个测试,但是结果让我一头雾水。无论是 false 还是 true,都不会创建该目录。而且报错也很有趣,在 false 的情况下,不报错但是无法创建。如果为 true,则会报告无效路径错误。

是 php-cli 的问题吗?我又用cgi测试了一下,发现了同样的情况。有趣的是,我随后通过电子邮件联系了该文章的作者并询问其版本号。很快,我就得到了他的回复,php-7.0.12。

于是我下载了php-7.0.12源代码并用debug重新编译。这里我就省略1000字了……

编译完成后,我迫不及待地想尝试一下。和我的php5.6一模一样。无论是cli模式还是cgi模式,我都无法重现作者文章中的情况。问题是什么?

我们先来调整一下。网上基本上没有关于VS调试PHP的详细介绍,大部分都是Vscode。我不知道如何启动和调试,所以我不得不想一个attach方法。在mkdir前面写sleep(10),但这其实有点没用。您实际上无法捕获 PHP 内核初始化过程,但这里已经足够了。最好在 php_plain_files_mkdir 处中断并刷新页面。附加到已启动的 php-cgi。

2.2.1PHP_FUNCTION(mkdir)

2.2.1.1 $递归== false

我们先来分析一下假的情况。和以前一样。不同的是php_mkdir_ex中VCWD_MKDIR调用的函数不同。

这次转到另一个电话

跟进 virtual_mkdir

也称为 Virtual_file_ex。之前没有提到的一件事是,在expand扩展路径的过程中,这个函数实际上是在最后进入的。前面说过,处理时如果是win32,会判断路径是否存在*,?。如果存在的话,直接返回1,不会进入后续的写入路径。那篇文章的作者为什么能成功写假呢?

2.2.1.1 $recursive== true

前面说过,这里会进行expand过程,但是也会判断是否是*,?路径名中存在,会报Invalid Path错误。

2.2.2 窗口中的mkdir

这里因为没有执行,所以写入目录没有被执行。这里我们无法探究窗口系统mkdir函数是如何执行的。

3.进程安全和非线程安全

我们再梳理一下,现在分为三种不同的情况:

linux /真正可写

窗口/7.0.12:

1. false 可以写

2. 真/假都不能写

窗口中有两种情况。仔细浏览了window/false的情况后,我现在唯一没有考虑的是VCWD_MKDIR选择情况。前面的步骤遵循调试过程。这是唯一可能发生分叉的地方。我们再看一下它的两个宏定义:

如果不是那篇文章的作者,他一定会用第二个定义,所以我把第一个定义注释掉,换成第二个定义,然后重新编译。结果和文章作者是一样的。 。但这里有一个小小的区别。写入的目录是相对于php-cgi.exe解释器的,而不是WWW网站的根目录。看完下面的分析,应该会给你答案。 ,那么很明显现在问题出现在定义 VIRTUAL_DIR 时。当没有定义时,会转到第二个定义。让我看一下 VIRTUAL_DIR。

它在哪里定义的

/php-src/Zend/zend_virtual_cwd.h

熟悉PHP内核的朋友一定会熟悉ZTS,它是PHP线程安全的标志。用于处理使用线程处理并发请求的Web服务器,例如window下的IIS和worker_mpm模式下的apahce。生活在线程中的PHP需要考虑线程之间的读写,同时还要保证线程之间的安全,因此PHP需要提供自己的ZTS层来管理线程之间的操作。定义 ZTS 时,也定义了虚拟目录 (VIRTUAL_DIR)。为什么存在虚拟目录这个术语?其实很简单。从对应的virtual_file_ex可以看出,该函数的作用就是将相对路径替换为完整的绝对路径。举个很简单的例子,php脚本中写的相对路径必须是脚本特定的。脚本执行过程中,会输入对应的php。在内核中的php_execute_中,其中一个步骤是VCWD_CHDIR_FILE(filename),用于根据要执行的脚本的位置来切换当前目录。同样,这个宏定义有两个不同的功能,一个是在虚拟目录中切换目录,另一个是对错与线程安全环境中单线程切换目录的区别在于,当在线程中切换目录时-安全的环境,你不直接调用系统的_chdir,而是把脚本执行的目录存放在TSRMG中,并给出一个cwd_globals_id,然后想用的时候再用。检索,例如创建目录和写入文件。由于多线程环境下无法直接修改当前进程的目录,所以只能预定义一个变量来保存每个线程的当前目录。可见,在线程安全模式下,如果给出相对路径,就会出现当前目录和相对目录的拼接。并且所有在win32环境下都会检测目录中是否包含*,?。

4 结论总结

我注意到那篇文章的作者在 Windows 上使用了 phpstudy。我还检查了 phpstudy 是否有版本 7.0.12。有一个 php-7.0.12-nts+Apache 确实不是线程安全的。也证实了上面修改php7.0.12并重新编译的结果,但是一个很有趣的事情是,窗口系统调用API_mkdir具有与php内部路径扩展功能相同的路径扩展功能,即允许这样写。 /1?/。 ./1 可以写入当前目录下的文件夹1。这与Linux不同。 Linux的系统功能是一层层判断的。 php7.1之后,系统中创建目录的API由_mkdir改为CreateDirectoryW,但不变的是路径扩展功能依然存在。即使这样写:@@#@$@#$^%$&&**/@!#@!$!%/../../evil,也可以创建目录evil,也算是作为一个小技巧。但条件是当php在window下处于非线程安全模式并且PHP_FUNCTION(mkdir)的第三个参数为false时,可以这样写目录。也算是一个小提示吧。可以根据相应的应用特性来使用,PHP版本一般都是非线程安全的。在nginx下,PHP是多进程处理的,即不是线程安全的。 Apache只有worker_mpm是多线程的,一般不常用。一般是prefork_mpm + php_mod,甚至fastcgi也是多进程的。利用环境是很常见的。

你可能想看:
« 上一篇
下一篇 »

相关推荐

php实时推送,php实地培训

2026年10月05日

48阅读

php乱码出现问号的原因,php乱码出现问号的原因有哪些

2026年10月05日

40阅读

php一句话木马,一句话木马ctf

2026年10月05日

43阅读

php获取数组长度,php中如何获取数组的长度

2026年10月05日

44阅读

php面试题库及答案,php程序员面试真题库

2026年10月05日

42阅读

phpcontent-type修改,php源码二次修改需要多少钱

2026年10月05日

38阅读