|
2014年,跨站一名安全研究员在uber的脚本网站后台发现了一个存储型XSS漏洞。他只需在乘客投诉表单里填入一段恶意代码,攻击就能让uber员工打开后台时被劫持会话。漏洞这并非黑客的跨站高深技巧,只是脚本往输入框里贴了段脚本。这故事说明,攻击跨站脚本攻击(XSS)至今仍是漏洞web世界里最普遍的漏洞之一,它不挑平台,跨站不挑语言,脚本专挑开发者对输入输出的攻击疏忽。 XSS的漏洞本质是浏览器把用户提交的数据当成了代码来执行。你设计一个搜索框,跨站用户输入"",脚本如果页面原样返回这句话,攻击浏览器就会乖乖弹出警告框。这看似无害,但换成窃取cookie的payload,攻击者就能伪装成你登录账号。更阴险的是存储型XSS,攻击者把恶意脚本存在服务器上,任何访问该页面的用户都会中招——就像论坛签名档里藏毒,每个路过的人都被咬一口。 我见过太多开发者的迷思:以为过滤了"script"标签就万事大吉。可XSS的变体多如牛毛。你用正则过滤了尖括号,攻击者改用svg标签的onload事件;你转义了单引号,他利用javascript伪协议构造链接。有个真实案例:某电商平台用白名单过滤标签,允许用户使用" ",结果攻击者利用img的src属性发起请求,把用户浏览记录偷偷传走。这就像你堵住了大门,却发现小偷从窗户翻了进来。 防御的关键在于理解浏览器的解析机制。HTML解析器、CSS解析器、JS解析器各自独立运作,你输出的内容在不同上下文里有不同语义。在div里输出文本,和输出到script标签内部,处理方式天差地别。最稳妥的办法是采用上下文感知编码:当数据要进入HTML标签时,转义<、>、&;当数据要进入属性值时,还得处理引号;当数据要进入JS字符串时,得用unicode转义。很多现代框架如React默认转义输出,但你要是用dangerouslySetInnerHTML,等于亲手拆掉安全护栏。 还有个常被忽视的角落——DOM-based XSS。这类漏洞纯粹发生在浏览器端,服务器完全无辜。比如页面用location.hash里的值动态更新DOM,攻击者构造一个以"#"开头的恶意URL,诱导用户点击。服务器返回的是正常页面,但前端代码在解析hash时把攻击者数据写进了innerHTML。这种攻击更隐蔽,因为web日志里连恶意请求都看不到。我测试过不少网站,发现前端框架的v-html、innerHTML插值是最容易踩雷的地方。 修补XSS没有银弹。安全编码规范要落实到每个开发环节,从输入验证到输出编码,再到CSP(内容安全策略)的部署。CSP能限制脚本来源,就算XSS被注入,浏览器也会拒绝执行非白名单的脚本。但CSP配置不当也会误伤正常功能,比如内联脚本被拦截导致页面崩溃。这需要开发者和安全团队反复调试。 说到底,XSS攻击考验的是开发者对浏览器规则的敬畏。你写的每行代码,都可能被攻击者以意想不到的方式利用。别总想着用几个过滤函数就高枕无忧,得把安全当成编码习惯。下次准备往HTML里拼接用户输入时,先问自己一句:这段数据会被浏览器当成什么解析?想明白这个,漏洞就少了一半。 |