90%高危漏洞藏于日志盲区:PHP服务器安全审计实战
|
去年八月份,我蹲在IDC机房那台老戴尔R720后面改php.ini,空调滴水正往下砸——咚、咚、咚——砸在服务器散热口护盖上,跟打节拍似的。陈工端着黄焖鸡米饭进来,油星子溅到我刚写的logrotate配置单上,他摆摆手说“李哥你先吃,这台web103的access_log昨天凌晨三点十七分开始丢条目,不是磁盘满,是日志轮转脚本被谁chmod 000了”。 我原以为是运维误操作,结果翻/var/log/php-fpm/www-error.log发现前二十行全是空白,后五十行突然插进一条:PHP Warning: Use of undefined constant xxxxx – assumed 'xxxxx' in /var/www/html/api/v2/user.php on line 89。那个xxxxx是用户传进来的base64_decode($_GET['s']),而line 89那行正好是file_get_contents($s)。我敢打包票,这个漏洞存在至少四个月——因为error_log里根本没报错,它压根儿没进error_log。 日志盲区?不就是把错误日志关成哑巴,再把access_log当流水账记,最后把audit.log塞进/var/log/old/底下落灰?我们组小张上周交的整改报告写着“已启用error_log+syslog双写”,我打开他的rsyslog.conf看了三秒就笑出声——他只配了.info /var/log/syslog,PHP的warning级别是notice,notice不会进info。我说小张你这叫双写吗?这叫单写加个空盒。他挠头:“可文档里写的是‘默认包含’啊……” 文档是谁写的?王主编上个月让平台规则部出的《PHP安全自查十项清单》,第七条写“检查日志级别是否覆盖所有PHP错误等级”,没写怎么查、查哪、查完改哪儿。我问主编,主编微信回了个“收到”,然后转发了条公众号推文《日更破万的爆款心法》——那标题党味儿,比我当年在tp5里硬塞eval($_POST['a'])还冲。
文章配图,仅供参考 打印机卡纸了。卡在第二页,“原创度检测通过率提升27%”那行字印得歪歪扭扭,像被谁用圆珠笔狠狠划过。王主编下午三点零七分推门进来,敲了三下桌面,说“审稿流程必须嵌入KPI挂钩”,我就指着桌上那张卡纸:“那您把‘日志盲区’加进编辑KPI吧——每季度抽检五个PHP项目,抽到一个error_reporting=0但开了display_errors的,扣绩效两百。”他愣了三秒,转身喊助理:“把专题排期表发群里,明天上午十点过会,重点讲转载授权合规和伪原创清洗标准。” 我说王主编,咱是不是漏了件事儿?比如为什么user.php第89行能被绕过open_basedir还读到/etc/passwd?他掏手机看消息,回我:“这个我说不准,你问问甲方那边要不要加个‘安全审计专项采购’?” 我翻出八月六号那天的线上会议录音。小赵在钉钉语音里连麦说:“log_path配置成./logs/,结果被前端传参?log_path=/etc/passwd直接打穿”,我说那得看error_log路径有没有写死——他说写了,写成‘/var/log/php/error_‘.date(‘Ymd’).’.log’。我一听就知道完了,date函数拼进去,PHP里没加引号过滤,实际执行的是error_20230806.log,但攻击者传?log_path=|id>/tmp/pwn;于是PHP进程一写日志,先执行id命令,再往文件里追加。这种事,log里不记shell_exec,不记system,不记passthru,只记一行“Failed to open stream: No such file or directory”,就像骂人只敢在背后咳一声。 那天我改了十九遍标题。第一次交“PHP日志审计避坑指南”,驳回。第二次“九成高危漏洞藏于日志盲区:一次真实渗透复盘”,驳回。第三次加了emoji:“⚠️九成高危漏洞藏于日志盲区????(附checklist)”,还是驳回。编辑说:“用户心智疲劳,要强钩子”,让我改成“别再被黑客当盲区养蛊了!PHP日志三不管地带正在喂大0day”。我抄起马克笔在打印稿上圈出“养蛊”俩字,划掉,底下补:“这个我敢打包票,咱们服务器跑着的thinkphp3.2.3,debug=true+display_errors=on+log_errors=off——三年了,没人碰过error_log目录权限,也没人试过curl -v 'http://api.xxx.com/index.php?s=/Index/\\\\think\\\\App/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=123'。” 小赵说“卧槽真能跑”,我回他:“能跑,但access_log里只记了一条200,error_log里连WARNING都没吐,因为log_errors=off。” 微信弹了个群消息:【运维中心】提醒:php-fpm进程重启失败,主因:/etc/php/7.4/fpm/pool.d/www.conf 中 slowlog = /var/log/php/slow.log 路径不存在且父目录无写权限。我把手机倒扣在桌上,听见王主编在隔壁会议室讲:“转热点不是投机,是主动出击。” 我掏出烟,没点,就叼着。想起去年这时候,我在某电商后台看到一个日志埋点:每次调用payment_callback接口,自动写一条JSON到/tmp/pay_debug.log,里面明文记着银行卡号后四位+完整token。那条日志,从来不在logrotate列表里,也没设chmod 600,更没人告诉开发——/tmp目录在某些发行版里默认777。 优胜劣汰?不是代码淘汰人,是日志淘汰人。谁还在盯着/var/log/messages查su记录,谁就还没看见nginx error log里藏着的fpm.sock权限溢出警告;谁还信“上线前扫过一次Wappalyzer”,谁就忘了logrotate脚本里那行rm -rf /var/log/php/ 是不是加了find -mtime +30条件;谁还在等甲方提安全需求,谁就错过了今天早上七点二十三分,jenkins流水线自动部署后,新生成的error_log权限从644变成了666。 这样,我今晚回去,先把dev环境所有php-fpm pool的catch_workers_output = yes打开,再挨个确认error_log路径有没有被$_SERVER覆盖。不为别的——就为下次空调再滴水的时候,我能听见错误真正落地的声音。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


