存在跨站漏洞

去年秋天,存跨一个做电商的站漏朋友半夜给我打电话,声音都在抖。存跨他的站漏网站被挂了一条脚本,用户点进商品详情页,存跨就会在不知情的站漏情况下给某个陌生账号自动下单。三天,存跨损失了十几万。站漏他问我:“我明明用了参数化查询,存跨怎么还能被搞?站漏”我让他把页面代码发过来,看了一眼就明白了——搜索框里的存跨关键词,直接拼进了HTML里。站漏

这就是存跨跨站脚本,简称XSS。站漏名字听着像黑客电影里的存跨东西,其实原理特别朴素:网站太信任用户输入的内容了。用户在搜索框里打什么字,网站就原样吐回页面上。如果那个“字”不是字,而是一段<script>标签呢?浏览器可不管这是不是用户写的,它只认标签,照单全收,执行。

我见过最离谱的一个案例,某政府网站的意见反馈栏,有人提交了一条“建议”,内容是一段JavaScript。后台管理员打开审核页面,脚本直接在他的浏览器里跑起来,把管理员的cookie发到了攻击者邮箱。整个过程不需要任何高深技术,一个文本输入框就够了。

XSS分三种。反射型最像钓鱼——你点了一个带参数的链接,服务器把参数里的脚本“反射”回页面,执行一次就没了。存储型最阴险,脚本被存进数据库,每个访问该页面的用户都会中招。我朋友那个电商站就是存储型,攻击者把脚本写进了商品评论,所有看评论的人都被自动下单。还有DOM型,问题出在前端JavaScript自己身上,服务器完全不知情,数据在浏览器里就被拼接执行了。

很多人以为过滤<script>就万事大吉。太天真了。攻击者可以用<img src=x onerror=alert(1)>,图片加载失败触发事件,照样执行。或者用<svg onload=alert(1)>,甚至<body onpageshow=alert(1)>。黑名单永远列不完,因为浏览器支持的事件和标签太多了。我见过一个漏洞,过滤了script字符串,但没过滤大小写混写,<ScRiPt>直接绕过。

防御的核心思路就一条:把用户输入当数据,永远别当代码。输出到HTML里,用HTML实体编码;输出到属性里,用引号包起来并编码;输出到JavaScript里,用JSON编码。别自己写过滤函数,用成熟库,比如DOMPurify。还有个容易忽略的点:设置HttpOnly cookie,就算脚本跑起来了,也偷不走会话凭证。

我后来帮朋友修那个站,发现他用的框架其实自带转义,但他为了“灵活”,手动用innerHTML拼了一段推荐商品列表。就这一处,全盘皆输。框架再好,架不住人非要绕过去。

跨站漏洞像门没锁。你装了防盗门,但窗户开着。攻击者不需要破解密码,只需要在输入框里打几个字符。每次看到有人把用户输入直接塞进页面,我都觉得像在看人闭着眼睛过马路——车没撞到是运气,撞到是必然。