大多数 JSP 项目的 XSS 漏洞,不是因为没做防护,而是因为防护做在了错误的位置、用了错误的编码方式。页面能正常显示,功能验收通过,问题就一直躺在那里,直到某天被人提交到漏洞平台。
这篇文章不重复"要过滤特殊字符"这类结论,只讲六个具体到代码行的编码细节。
JSP 最方便的地方,也是它最危险的地方:Java 代码和 HTML 处在同一个文件里。
<%= request.getParameter("q") %> 这样一行,就把 URL 里的内容原封不动写进了页面。访问 ?q=
更能说明问题的是存储型。工单标题、商品评价、留言、后台备注,这些内容会被保存、被列表页输出、被管理员打开。一条带脚本的工单,等于给所有看到它的人埋了一次执行机会,包括权限更高的运营和客服。企业常觉得"系统在内网、用户都是员工",但真正被打穿的往往就是这类后台。
很多团队有自研的网站设计 escapeHtml,把尖括号换成实体就认为安全了。问题在于,同一个值出现在页面的不同位置,需要的编码方式并不相同。
属性值是第一个破口。写成 <input value=<%= name %>> 而不加引号时,攻击者只要输入一个空格加 onmouseover=,就能在标签里凭空加一个属性;加了引号,编码则必须覆盖单双引号,只换尖括号没有用。所以属性的正确姿势是:始终加引号,并且用属性专用的编码方法。
第二个破口是链接。href 里塞进 javascript:alert(1),整串没有一个字符需要转义,任何基于特殊字符的过滤都会放行。这个位置真正该做的是协议白名单,只允许 http 和 https,而不是再加一层转义。
第三个破口是脚本块。
src=x onerror=alert(1)>,浏览器会老实执行它——因为浏览器分不清哪一段是开发者写的代码、哪一段是用户提供的数据。这就是反射型 XSS 的最小复现。
02 转义函数再完善,也管不到"上下文"