open_basedir仅限制部分文件函数的目录访问,不能防目录遍历;需配合disable_functions、allow_url_fopen=Off及realpath白名单校验才有效。
open_basedir 是什么,它真能防目录遍历吗
open_basedir 是 php 的一个配置项,作用是限制 php 脚本只能访问指定目录及其子目录。但它不是“防目录遍历”的银弹——它只在 fopen、file_get_contents、include 等文件操作函数中生效,对 system、exec、shell_exec 等系统调用完全无效。
常见错误现象:开了 open_basedir = /var/www/html/,但攻击者仍通过 system("cat /etc/passwd") 读取敏感文件。
- 它不拦截命令执行类函数,也不影响 CGI 模式下的 PATH 查找
- 如果配置值末尾没加
/(比如写成/var/www/html),PHP 会允许访问/var/www/html_test这类同前缀路径 - 多个路径用冒号分隔(Linux)或分号(Windows),任意一个路径匹配即放行,别漏掉
/tmp这类常被忽略的临时目录
怎么配 open_basedir 才真正收紧权限
核心原则:最小化 + 显式结尾 + 配合其他机制。不要只依赖它挡遍历。
- 路径必须以
/结尾,例如/var/www/html/,避免路径前缀绕过 - 把运行时必需的额外目录都列全:
/var/www/html/:/tmp/:/usr/share/php/(注意冒号分隔) - 禁用
allow_url_fopen = Off,否则file_get_contents("php://filter/...")或远程协议可能绕过限制 - 搭配
disable_functions关掉高危函数:disable_functions = system,exec,passthru,shell_exec,proc_open
示例配置片段(php.ini):
open_basedir = /var/www/html/:/tmp/:/var/log/php/ allow_url_fopen = Off disable_functions = system,exec,passthru,shell_exec,proc_open,pcntl_exec
为什么 realpath() + 白名单比 open_basedir 更可靠
当业务需要动态拼接文件路径(比如用户传 filename=report.pdf),仅靠 open_basedir 不够——它不校验路径是否真的落在白名单内,只管最终访问时的物理路径。
典型漏洞场景:include($_GET['page'] . '.php'),即使开了 open_basedir,攻击者仍可传 page=../../etc/passwd%00(PHP 5.3.4+ 已修复 null byte 截断,但仍有其他绕过手段)。
- 务必先用
realpath()获取绝对路径,再用strpos()或str_starts_with()(PHP 8.0+)判断是否在白名单根目录下 - 不要用
basename()做过滤——它只截文件名,不解决路径穿越 - 对上传文件的保存路径,必须重命名且限定扩展名,不能直接信任原始文件名
简短校验示例:
$target = $_GET['file'] ?? '';
$full_path = realpath('/var/www/html/' . $target);
if ($full_path === false || strpos($full_path, '/var/www/html/') !== 0) {
die('Access denied');
}
容易被忽略的 open_basedir 生效盲区
很多团队以为加了 open_basedir 就一劳永逸,结果上线后仍出问题。关键盲区在于运行上下文和继承关系。
- Apache 的
php_admin_value open_basedir会被 .htaccess 覆盖,而 Nginx 的fastcgi_param PHP_VALUE又可能被恶意请求注入(需禁用php_admin_flag allow_url_fopen类参数) - CLI 模式下默认不启用
open_basedir,除非显式配置;Web 和 CLI 的 php.ini 可能不同 - Docker 容器里若挂载了宿主机目录(如
-v /etc:/mnt/etc),而open_basedir又包含/mnt/,等于间接开放了宿主机敏感路径
验证是否生效最直接的方法:写个探针脚本执行 echo ini_get('open_basedir');,别只信配置文件。










