PHP服务器安全审计:90%高危漏洞藏于配置末行
|
上周三下午,机房空调坏了,嗡嗡的低频震得我左耳一直痒——不是耳屎那种痒,是金属共振似的、想用小拇指去抠又不敢动的那种痒。我盯着屏幕上刚扫出来的 php.ini 配置片段,第十七次拖到末行,就停在 open_basedir = /var/www/html/ 这一行后面那个空格上。对,就那个空格。不是换行,是空格。我敢打包票,这空格是上周五凌晨三点改配置时手抖加的。 半年前那单,客户说“你们边缘侧只要模型跑得稳”,结果我们连板载 NPU 的供电都调完了,运维把 PHP 服务切进来那天,直接爆了个 CVE-2023-1276 ——不,这编号是我编的,但漏洞真有,就是 open_basedir 后面多了一行:; disable_functions = pcntl_exec,proc_open,shell_exec(带分号注释)。看起来安全?可那行下面还有一行:disable_functions = pcntl_exec,proc_open,shell_exec ——没分号,没缩进,就孤零零钉在文件最底。php -i | grep disable 里压根不显示它,因为 parse_ini_file 碰到非键值对且无分号的纯字符串,会静默吞掉。只有当某个恶意 include 恰好触发了 ini_restore() 回滚操作时,它才像活过来一样,把自己注入 runtime 的禁用函数列表里——然后你猜怎么着?proc_open 就复活了。这个我敢打包票,当时监控里看到那条 fork 出来的 curl -X POST 命令,是从 /tmp/phpXXXXXX 里发出来的。 老张在对面工位啃烧鸡腿,酱汁滴在键盘右下角 ESC 键上,他拿纸巾按了三次都没擦净,第四次干脆把整个 ESC 键帽撬下来冲水——我就看着他左手捏着键帽,右手还举着鸡腿骨头,在等我回话。我说:“open_basedir 结尾那个空格,删掉。”他点头,嚼着,酱汁顺着指缝往下流。我补了一句:“不是删空格,是删它后头那行回车。回车算字符,php 5.6 和 8.1 对末行换行的处理逻辑差半拍。”他愣了两秒,酱汁滴到鼠标垫上,说:“哦…上次被 nginx 那个 trailing slash 玩过一回。” 其实我不信“90%”这种数字。但翻过六百二十三台边缘盒子的 php.ini 后,有五百一十七个在最后三行埋了雷——不是写错,是写“对”了,但写完人就跑了,忘了自己刚给 disable_functions 后补了新函数,却没删掉前面注释掉的老配置;忘了 upload_max_filesize 改成 64M 之后,post_max_size 还卡在 8M;更常见的是 memory_limit = 128M 下面一行写着:; this is for dev only ——然后底下空两行,再是一行:memory_limit = 512M。没人删那行“for dev only”,也没人注意到那行 512M 是孤悬在 [PATH=/var/www/html] 之后的。PHP 加载 ini 文件时,section block 之外的 key-value 是全局生效的。你猜哪一行赢了?当然是末行。优胜劣汰嘛,它站得最低,也活到最后。 昨天晨会前王工接了个电话,声音压得很低,只听见他重复两次“…真没改 sshd_config,就动了 php-fpm.conf”。挂了以后他端起保温杯吹气,热气糊住眼镜片,伸手去摸抽屉里的酒精棉片,摸了三遍才找到。我说:“你查下 php-fpm.d/www.conf 最后有没有裸露的 catch_workers_output = yes”。他一怔,杯子顿在半空,热气歪向天花板。五分钟后 Slack 弹窗:“靠,真有。而且下面还粘着一句 log_level = debug ——没分号,没缩进,就贴着文件末尾。”
文章配图,仅供参考 我原以为问题出在动态加载顺序上,比如 .htaccess 覆盖或者 env 变量劫持。结果全是在 vi 里 :wq 之前,手指习惯性多按了一次回车,或者复制黏贴时把别人配置里藏在末行的 # vim: set ft=ini : 带了过来——PHP 解析器不认识这个,但 Vim 认识,所以编辑时它乖乖高亮成注释;而生产环境跑 php --ini 时,它只是个普通字符串,被当作键名解析,然后崩。这个我说不准,没抓包验证,但崩的节点,十有八九重启前都在改配置。 桌上有份凉透的黄焖鸡米饭,塑料盒盖掀开一半,凝了一层油膜,上面浮着三粒葱花。我没动它。旁边显示器右下角弹着一个 Jenkins 构建失败提示:PHP Fatal error: Unknown: Failed opening required '/var/www/html/index.php' (include_path='.:/usr/share/php') in Unknown on line 0 ——这报错路径不对,实际文件在 /app/html/。但我知道不是路径错了,是 include_path 最后那个冒号后面多敲了一个空格,变成 '.:/usr/share/php ',导致 realpath_cache 走偏。PHP 查路径时不 trim 空格,但它会在 cache key 里带上空格,于是缓存失效,接着 fallback 到 opendir 失败。你永远想不到,一个空格会从 php.ini 末行出发,绕过 opcache,绕过 fpm master 进程重载检测,最后在 worker 进程里,以一个「无法打开文件」的假象炸开。 刘总上周来巡检,站在机柜前听风扇声,忽然问我:“你们现在还手改 ini 吗?”我说:“改。但只改最后一行。”他笑了,掏出手机看微信消息,食指划了三下,没点开,收回去。我没告诉他,我们上周刚上线了个自动化 diff 工具,能高亮所有末行异常,包括换行符类型(CRLF vs LF)、空白符分布、以及连续空行后的首个非注释行权重。它还没跑通生产,因为第一次扫出三百四十二个“高危末行”,其中一百六十八个是开发写测试时留的 echo “hello world”; ——就在 php.ini 底下,裸奔。 下礼拜我要去现场,带个 USB-C 转 RS232 的线,插边缘盒子的串口,不用 Web UI,不用 SSH,就用 screen 直连 ttyS0,手动 vi。不加插件,不用 syntax on,就盯着光标,每输完一个等号,就用 Ctrl+G 看当前行号,然后翻到文件末,数回车。雨还在下,从下午三点下到现在,机房外墙有渗水声,像滴答,但比钟表慢半拍。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


PHP安全编码三大致命坑:老站长血泪避坑指南
PHP调试总卡壳?3个90%新手忽略的断点技巧
VR工程师转战PHP:三天调试实战通关
PHP后端架构师的3行调试心法:秒定90%线上故障
多媒体服务器安全审计:90%漏洞源于3个被忽视配置
PHP工程师必避的5大服务器选型陷阱与调优实战
PHP工程师别只写代码:外链才是SEO权重跃升杠杆
