Nginx / Lighttpd + PHP FastCGI的方式正在被越来越多的网站应用,其中让需要虚拟主机支持的用户最烦心的一件事情莫过于站点权限隔离。 目前无论是spawn-cgi或者是php-fpm的方式,都无法动态转变执行用户。尽管可以通过给不同网站以不同的用户身份执行FastCGI,但这也同样失去了FastCGI统一管理的优势,需要为每个网站保留足够的处理进程而不是整体规划。
Google搜之有两个比较广为流传的方法,其中最完美的莫过于直接修改PHP源代码,对打开目录进行鉴权(搜出来的资料最早是anxsoft.com提供的代码)。 方法是在php源代码目录中执行vi main/fopen_wrappers.c,并找到php_check_open_basedir_ex方法,在char *end;和pathbuf = estrdup(PG(open_basedir));之间插入以下的代码:
char path_copy[MAXPATHLEN]; int path_len; path_len = strlen(path); if (path_len >= MAXPATHLEN) { errno = EPERM; return -1; } if (path_len > 0 && path[path_len-1] == PHP_DIR_SEPARATOR) { memcpy(path_copy, path, path_len+1); while (path_len > 1 && path_copy[path_len-1] == PHP_DIR_SEPARATOR) path_len--; path_copy[path_len] = '/0'; path = (const char *)&path_copy; } char *env_doc_root; if (PG(doc_root)) { env_doc_root = estrdup(PG(doc_root)); } else { env_doc_root = sapi_getenv("DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT")-1 TSRMLS_CC); } if (env_doc_root) { int res_root = php_check_specific_open_basedir(env_doc_root, path TSRMLS_CC); efree(env_doc_root); if (res_root == 0) { return 0; } if (res_root == -2) { errno = EPERM; return -1; } }
以上是所有能找到的资料里代码最长也是考虑最完整的代码。 前段是用于去除传入的路径参数中最后的多个斜杠(/)对代码判断的影响。后段则是取得当前站点的文档根目录,并检查要打开的文件是否存在于这个目录下、是否有权限等。
编译后测试发现确实的解决了WebShell对同级目录的跨站访问。但运行某个基于Zend Framework的项目时则遇到了阻碍,无法读取application目录下的config.ini。原因是root目录位于application的同级目录html下。 其他一些项目也可能会有类似需求,需要访问root同级的upload或其他不开放的目录。这个需求很容易就能解决,参考检查DOCUMENT_ROOT的方式,优先检查另外一个SITE_ROOT的环境变量是否存在且有权,并在nginx的php fastcgi配置里加上一行即可:
fastcgi_param SITE_ROOT /web/zend.hly1980.cn/;
注:该网站的root为/web/zend.hly1980.cn/html。