修跨站点脚本漏洞最容易修反的一步,说白了就一个动作——把转义做到了入库那一头。评论框里刚敲进去的东西,先在服务端把尖括号变成实体,再存进数据库,看着挺安心对吧。可这层安心是假的。真正的输出点有几十个:列表页、详情页、后台审核页、搜索回显、给运营导出的邮件模板、手机端接口。入库只有一个口子,输出有一堆口子。你只在一处加了防护,别的地方仍是裸的。

你试试在昵称里写 Tom & Jerry。入库前转一次,字段里存的是 Tom & Jerry;页面渲染时模板的自动转义再转一次,用户看到的是 Tom &amp; Jerry,多出来的符号让人抓瞎。富文本更惨,编辑器保存时已经被转义成实体,第二次进编辑器再解码、再转义,来回几轮之后,后台一点“编辑”整个正文全是 <div>。这时候人最容易干的事是什么?把输出端的转义“临时”关掉,好让页面恢复正常。那一关,才是真正出问题的时候。
所以你看代码得看对地方。DOM 型的站,你在后端把 $_GET 全部过滤一遍,页面照样中招,因为参数根本没经过后端。反过来,一个纯服务端渲染的老站,你只盯着前端 JS 改,同样白搭。分清是哪一类,再动手,否则改三天也说不清改了什么。
正确的方向反过来:不猜哪些是坏的,只认哪些是好的。该是数字的字段就只放数字,该是纯文本的昵称就只留字母数字和少量符号,富文本用白名单——允许 p、br、strong、a 这几个,其余标签全部剥掉,属性只留 href 且必须校验协议是 http 和 https。白名单麻烦,但它不用天天追着新姿势跑。
清理的时候分两拨:确实是攻击 payload 的直接删或者替换成占位文字;属于正常用户内容被二次转义、显示成乱码的,做一次小心解码再按正确的编码流程重新处理。做完这批,把修改记录和涉及的文章 ID 列个表存下来,以后前台哪个页面显示怪了可以对着找。顺手把后台审核列表多刷几页,看看有没有漏网的。
为什么不用 strip_tags?因为它只剥标签不剥属性,onclick、style 全留着;而且会把内容里正常的数学小于号也当成标签开头吞掉。用白名单之后,再回过头测一遍老文章:加粗还在不在、链接能不能点、换行有没有丢、图片会不会被删光。删过头和删不掉一样是事故,前者用户会投诉,后者等于没修。
导出 CSV 是另一个大坑。内容以等号、加号、减号、艾特符号开头,用表格软件打开会被当成公式解析,这一条属于跨站脚本漏洞家族里的表亲。处理方式是在导出时给这类开头的值前面加一个单引号或者制表符,让它老老实实当文本。导出接口本身也别省事用 GET 拼参数,改 POST 加权限校验。
具体怎么找:后端搜 echo、print、write,模板里搜不转义的写法(Twig 的 raw、Blade 的双叹号、Django 的 safe 过滤器),前端搜 innerHTML、document.write、insertAdjacentHTML。搜出来别一把改,先判断数据来源:来自数据库但入库时没转义的算存储型;直接读 location 拼进去的算 DOM 型。分完类一次改一类,改完测一类,比混着改好收尾得多。
顺序别搞反:先把输出点的编码修对,再补输入校验,最后上 CSP 和 WAF 当兜底。把兜底当主力,出事只是时间问题。
代码推上去页面还是老样子,先别怀疑自己。整页缓存的站,CDN 边缘节点上还挂着旧 HTML;PHP 开了 opcache 没 reload 服务,跑的也是旧代码;框架自己的模板编译缓存还得单独清。顺序是清 CDN、reload 服务、清框架缓存、浏览器强刷,测试时最好直接在 URL 后面加个随机参数,不然你测的可能是几小时前那份页面。
修跨站点脚本漏洞这件事,记住一句就够:入口只做校验,出口才做编码,上下文决定编码方式。顺序对了,剩下的都是体力活;顺序反了,补丁会越打越多,而且每打一次都在给下一次挖坑。改完记得把改动记下来,谁在哪个文件哪个函数改的,下次出问题好回查。
【版权声明】:本站内容若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!