必须用realpath()解析用户路径并严格比对白名单根目录前缀,同时先urldecode()再清理编码绕过,禁止用户控制后缀,强制服务端硬编码扩展名。

怎么防止用户通过 filename 参数写入任意路径
直接用 $_POST['filename'] 拼接文件路径,等于把服务器磁盘根目录钥匙交给前端——../../etc/passwd 这种输入一提交,立刻触发目录穿越。必须做两层拦截:
- 用
realpath()解析用户传入路径,再与预设的「合法根目录」严格比对前缀,比如$allowed_root = '/var/www/html/uploads',若realpath($user_path)不以该字符串开头,直接拒绝 - 禁止路径中出现
..和绝对路径符号(/开头),但仅用strpos($filename, '..')不够——攻击者可用%2e%2e或....//绕过,必须先urldecode()再清理重复斜杠,最后再校验 - 写入前强制补全扩展名白名单,比如只允许
.txt、.json、.php(若真需编辑 PHP,必须额外校验内容是否含恶意函数调用)
为什么 file_put_contents() 不能直接存用户提交的内容
用户提交的编辑内容可能包含 XSS 载荷(如 <script>alert(1)</script>),如果这个文件后续被 Web 服务器以 text/html 类型返回(比如误配 MIME 类型或通过 include 加载),就会执行脚本。更危险的是,若文件保存在 Web 可访问目录且后缀为 .php,用户可直接上传一句话木马。
- 若保存为纯文本(如
.txt),应设置响应头Content-Type: text/plain; charset=utf-8,并禁用浏览器自动解析 HTML - 若必须支持
.php编辑,保存前需扫描内容:用token_get_all()解析 PHP 语法树,检查是否存在eval、system、shell_exec等危险函数调用,发现即拒写 - 绝不允许用户控制文件后缀,后缀必须由服务端硬编码决定,例如
$ext = pathinfo($safe_filename, PATHINFO_EXTENSION) ?: 'txt'
open_basedir 和 disable_functions 怎么配合才不形同虚设
单独配置 open_basedir 或只禁几个函数,都挡不住组合攻击。PHP 7.4 下常见绕过链是:上传一个临时文件 → 用未被禁用的 include 加载它 → 执行恶意代码。所以必须闭环控制:
-
open_basedir必须显式包含实际工作目录和临时目录,例如open_basedir = /var/www/html:/tmp,缺一不可;否则move_uploaded_file()会失败 -
disable_functions列表必须小写、无空格、覆盖全量高危函数,尤其要包含pcntl_exec、proc_open、putenv—— 这些常被用于绕过传统exec禁用 - 必须同步启用
opcache.restrict_api = "/dev/null",否则攻击者可通过opcache_get_status()泄露脚本路径,再构造包含链 - 验证是否生效:上传一个含
<?php system('id'); ?>的文件,再用include尝试加载,应报错而非执行
编辑前备份和并发冲突怎么处理才不丢数据
多人同时编辑同一文件时,后保存者直接覆盖前者的修改,这是典型的数据丢失场景。简单时间戳备份不够,得靠原子操作和版本锁:
- 每次保存前,先用
flock($fp, LOCK_EX)对目标文件加写锁,读取当前内容做校验(比如比对md5_file()),若发现已被修改,返回 HTTP 409 并提示“他人已更新,请刷新重试” - 备份文件名用
basename . '.' . date('Ymd_His') . '.bak'格式,避免纳秒级重名;备份后立即chmod 600限制权限,防止被意外读取 - 若需实时协同,不要自己实现 OT/CRDT,直接集成
Monaco Editor的 WebSocket 插件,它内置了基于操作变换的冲突解决逻辑,PHP 层只负责透传变更流
realpath() 之后做,而不是对原始 $_GET 字符串做简单 str_replace —— 因为 realpath() 会处理符号链接、大小写转换、多余斜杠等真实文件系统行为,而字符串替换永远跑在抽象层。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











