记一次排查PHP上传目录配置的经历

这是一篇发表在『代码审计』公众号的杂文,关注我的公众号:white-hat-note,查看更多类似文章。

最近在本地调试上传的文件,我发现了一个非常不好的”特征“,大家猜猜是什么:

image-20200710153654547.png

是的,临时文件居然被放在了Windows系统目录下,对于有轻微”洁癖“的我来说是不可以接受的,我想知道这是什么原因。

首先,我本地的PHP环境是这样的:

  • Windows 10
  • PHPStudy 8.1 集成环境
  • PHP 7.3.4
  • Apache 2.4.39

那么,我们来找找隐藏在这个“不正常”的表象背后的原因吧!

PHP中的临时文件目录从哪来?

首先,我们先来看看文件上传时,临时目录应该从哪来。这是PHP文档中对upload_tmp_dir这个配置项的说明:

The temporary directory used for storing files when doing file upload. Must be writable by whatever user PHP is running as. If not specified PHP will use the system's default.

If the directory specified here is not writable, PHP falls back to the system default temporary directory. If open_basedir is on, then the system default directory must be allowed for an upload to succeed.

PHP的配置文件中,upload_tmp_dir用来指定文件上传时的临时目录;如果这个值没有配置,则使用系统默认的临时目录。

打开PHPStudy的配置文件,发现这个选项没有配置:

image-20200710155046332.png

那么,应该是使用了系统默认的临时目录。我们再执行如下脚本看看临时目录是什么:

<?php
var_dump(sys_get_temp_dir());

仍然是C:\Windows

image-20200710160230792.png

我们下载PHP的源码,看看究竟sys_get_temp_dir是怎么获取临时文件的:

/* {{{ proto string sys_get_temp_dir()
   Returns directory path used for temporary files */
PHP_FUNCTION(sys_get_temp_dir)
{
    if (zend_parse_parameters_none() == FAILURE) {
        return;
    }
    RETURN_STRING((char *)php_get_temporary_directory());
}
/* }}} */

看看php_get_temporary_directory方法呢:

PHPAPI const char* php_get_temporary_directory(void)
{
    /* Did we determine the temporary directory already? */
    if (PG(php_sys_temp_dir)) {
        return PG(php_sys_temp_dir);
    }

    /* Is there a temporary directory "sys_temp_dir" in .ini defined? */
    {
        char *sys_temp_dir = PG(sys_temp_dir);
        if (sys_temp_dir) {
            size_t len = strlen(sys_temp_dir);
            if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) {
                PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1);
                return PG(php_sys_temp_dir);
            } else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) {
                PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len);
                return PG(php_sys_temp_dir);
            }
        }
    }

#ifdef PHP_WIN32
    /* We can't count on the environment variables TEMP or TMP,
     * and so must make the Win32 API call to get the default
     * directory for temporary files.  Note this call checks
     * the environment values TMP and TEMP (in order) first.
     */
    {
        wchar_t sTemp[MAXPATHLEN];
        char *tmp;
        size_t len = GetTempPathW(MAXPATHLEN, sTemp);

        if (!len) {
            return NULL;
        }

        if (NULL == (tmp = php_win32_ioutil_conv_w_to_any(sTemp, len, &len))) {
            return NULL;
        }

        PG(php_sys_temp_dir) = estrndup(tmp, len - 1);

        free(tmp);
        return PG(php_sys_temp_dir);
    }
#else
// ...
#endif
}

这个代码就比较明白了,我简单翻译一下:

  • 首先查看php.ini中是否有定义sys_temp_dir这个配置项,如果有则返回之
  • 如果当前环境是WIN32,则调用系统API(GetTempPathW)来获取临时目录

我们查看配置文件中的sys_temp_dir,发现也没有进行配置:

image-20200710161109166.png

所以,可以确定C:\Windows这个值来自于Windows API GetTempPathW。

我们打开MSDN,查找这个API:https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw

这里说到了GetTempPath函数是根据如下顺序来获取当前系统配置的临时文件的:

image-20200710161457843.png

果然,最后的fallback选项就是Windows目录,我们找到了一半原因。

为什么PHP获取不到我配置的环境变量

那么,是什么原因导致GetTempPath没有获取到前3个结果呢?

GetTempPath的前三个结果都来自于环境变量,我们打开Windows配置环境变量的页面看看:

image-20200710162006623.png

无论是用户的环境变量,还是系统的环境变量,TMP和TEMP都是存在的。也就是说,并不是因为我没有配置环境变量。

我们尝试在CLI中获取临时目录,发现此时是可以的:

image-20200710162513386.png

那么,原因应该是出在Apache上。

找到了2013年的一个issue,说到了类似的问题:https://bugs.php.net/bug.php?id=64410,看起来PHP官方并没有重视这个问题。大概原因应该是Apache没有给PHP传递TEMP环境变量,然后PHP-CGI自己也没设置这个环境变量,就导致最后获取到的是fallback的一个值。

如何解决这个问题

那么,如何解决这个问题呢?

前面的issue中也提到了一个方法,就是让Apache传递TEMP环境变量即可。

所以,我们打开Apache的配置文件,首先检查一下我们使用的是哪个Mod来执行的PHP:

image-20200710195851567.png

fcgid_module,所以我们参考官方文档,使用FcgidInitialEnv来指定fastcgi传输给PHP进程的环境变量:

<IfModule fcgid_module>
    FcgidInitialEnv TEMP "C:\windows\Temp"
</IfModule>

当然,如果不想这么复杂,也可以直接修改PHP的配置文件php.ini,将sys_temp_dir设置成需要的值即可。这个方法缺点是,如果是phpstudy这样的多PHP切换环境,需要修改多个配置:

sys_temp_dir = C:\windows\Temp

我们再查看PHP的sys_get_temp_dir结果,正常了:

image-20200710200235941.png

赞赏

喜欢这篇文章?打赏1元

评论

captcha