90%高危漏洞藏于日志盲区:PHP服务器安全审计实战
|
去年十二月,凌晨三点十七分,我盯着屏幕右下角的时钟发呆——不是因为看时间,是那个数字卡在那儿,跟服务器负载曲线一样,一动不动。机房空调坏了,风扇声像老拖拉机在喘,嗡嗡嗡嗡嗡,第七次重听日志轮转脚本的报错提示音时,老张端着保温杯进来,把半块没吃完的葱油饼放我键盘旁边,饼屑掉进 USB 接口缝里了。我没碰。 我原以为日志审计就是 grep + awk + tail -f 撸起袖子干,结果上周三上午九点四十一,小王甩过来一个 cURL 请求体截图:POST /api/v2/user/upload?token=Zm9vYmFyMTIz——带 token 但没校验有效期,没绑定 IP,没做二次签名。他问我“这个要打补丁吗”,我还没开口,老张在隔壁工位接电话:“……对,SSL 证书续签?你找运维,不归 PHP。”电话那头声音太响,我听见他用左手食指关节敲了三下桌面,咚、咚、咚,像在给某段死循环计数。 90%高危漏洞藏于日志盲区,这事儿不是推论,是拿命换来的结论。你查 access.log?行。查 error.log?也行。但谁会盯着 php-fpm 的 slowlog 里那些被截断的 backtrace 看十八秒?就上个月,一个 vendor/composer/autoload_real.php 被注入 eval(‘base64_decode(“…”)’) 的 shell,它根本没进 access.log——请求走的是 POST /webhook,而 webhook handler 是个匿名函数加 array_map,连 route 都没注册,纯靠 $_POST['callback'] 动态调用。日志只记了一行:“PHP message: PHP Warning: Invalid argument supplied for foreach() in /var/www/html/core/hook.php on line 83”。就这行。没人理。我敢打包票,那天全组六个人扫过这行日志,五个人鼠标滚轮直接划过去了。 优胜劣汰?可不是说技术淘汰旧东西。是人自己筛自己:能忍住不删 debug 日志的活下来;能翻完三个月前慢查询日志里所有“Query_time: 3.2s”后还顺手 grep 一下 user_id 字段的,活下来;而看见 “file_put_contents(/tmp/log.txt, …, FILE_APPEND)” 就当它只是个 debug 输出的,自动离场。这事儿没培训、没文档、不写 KPI,但服务器每天夜里两点自动 flush 的时候,就有人突然想辞职。 昨天下午四点零七分,我蹲在测试环境 nginx 配置目录底下改 log_format,顺手删掉了那条 “log_subrequest off;”。不是为安全,是因为小王上周交的 MR 里,有个 location 块用了 internal,结果 upstream 日志全是 “-”,连 status 都是破折号。我看不下去。改完 reload,咖啡凉了,杯底一圈浅褐色印子,跟 nginx.pid 文件的权限位(644)颜色差不多。 说回盲区。你真以为盲区在日志格式设置?错。盲区在你的大脑缓存区——比如,你记得 open_basedir 开了,但忘了 php.ini 里 display_errors=On 会导致敏感路径裸奔到 HTML 注释里;你记得禁用了 proc_open,但没查 system() 调用前是不是刚 new 过一个 Phar 对象;你记得关了 expose_php,可 phpinfo() 的输出被埋在 vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php 第四百一十九行,作为调试 fallback 存活着。这个我说不准,得翻 commit 历史——但我知道,上个周五中午十二点十六,保洁阿姨推着吸尘器从走廊经过,停在我们工位门口两秒,吸尘器声音忽然变调,像硬盘寻道失败。
文章配图,仅供参考 再举个不相关的:前天晚上改完 SSO 登录回调,我试了三次才通,第二次失败时手机弹出钉钉消息,显示小李刚离职。系统发的,红底白字。我盯着那条通知看了七秒,然后把 curl -X POST 改成了 curl -k -X POST,因为证书链里少了个中间 CA。这不是重点。重点是,我改完那行,下意识点了保存,又反手按了 Ctrl+Z——其实没必要撤回,但我就是按了。人有时候就喜欢给自己留个退路,哪怕那条路压根不通。 有次在客户现场,对方安全团队拿 Burp 抓到一堆 500 响应,其中一条的响应体是:“Fatal error: Uncaught Error: Class ‘App\\Services\\JwtDecoder’ not found in /app/src/Auth/TokenService.php:72”。他们截图发群里,标题叫《PHP 类加载机制暴露绝对路径》。我截图发我们组,标题改成了《这个错误发生在 autoload 失败之后,说明前面至少有 3 次 class_exists() 返回 false 没被记录》。没人回。群里安静了三分钟,然后小王发了个表情包:一只猫在舔爪,配字“假装没看见”。 现在我 audit 新项目,第一件事不是看代码,是进机器 ls -la /var/log/php-fpm/;第二件事不是跑 scanner,是 grep -r ‘error_log(’ . --include=".php" | head -12;第三件事最绝:把当前 git 分支名复制下来,去 nginx 日志里搜它——很多团队上线不切日志,直接往 default.log 里写,而 branch 名就是临时 endpoint 的活体身份证。这事没文档,没人教,但老张说过一句,“log 不是你写的,是它自己长出来的”。当时窗外正下雨,雨点砸在机房铁皮顶上,噼啪,噼啪,噼啪,每三下之间停顿刚好半秒。 下一步?我准备把手头三个项目的 php-fpm.conf 里的 catch_workers_output 全打开,再挨个给每个 worker 加个 custom log_format,专门打 request_id 和 opcache hit 状态。至于能不能发现新盲区——我也不知道。我只知道,明天早上九点,我要去楼下便利店买热豆浆,顺便看看老板手机壳上贴的那张泛黄的“2020 年度安全演练优秀单位”奖状还在不在。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

