XSS、CSRF、SSRF 这三个漏洞之所以常年霸占 OWASP Top 10 榜单,不是因为它们原理多高深,而是因为它们的防护边界极其容易在复杂的业务逻辑中被模糊掉。很多开发人员不是不知道要防御,而是不知道防御的边界在哪里,哪里该做转义,哪里该做校验,哪里该阻断。一旦边界模糊,漏洞就产生了。

XSS 的防护边界:从输出编码到上下文隔离

XSS 的核心从来不是过滤输入,而是控制输出。很多人一上来就在输入端疯狂过滤关键字,结果要么被绕过,要么把正常用户输入搞得面目全非。真正的边界在输出端,在数据被拼接到 HTML 页面的那一刻。根据输出位置的不同,防护策略完全不同。如果数据被输出在 HTML 标签之间,比如 <div>这里</div>,那么你需要对 <、>、"、'、& 这几个字符做 HTML 实体编码。如果数据被输出在 HTML 属性里,比如 <input value="这里">,那属性值必须用引号包裹,并且对引号做编码。如果数据被输出在 JavaScript 代码块里,比如 <script>var x="这里";</script>,那 HTML 实体编码就完全无效了,你需要做的是 JavaScript 字符串编码,防止闭合引号注入新代码。更危险的情况是数据被输出在 URL 里,比如 <a href="这里">,那必须先校验协议头是否为 http 或 https,否则一个 javascript: 伪协议就能直接执行代码。每种上下文都有对应的编码函数,绝不能用错。现在前端框架如 React、Vue 默认对插值做了转义,这很好,但 dangerouslySetInnerHTML 和 v-html 这类 API 就是防护边界上的豁口,一旦使用就意味着你主动绕过了框架的防御,必须自行确保数据安全。另外,内容安全策略(CSP)是最后一道防线,它能限制浏览器只执行白名单内的脚本,即使攻击者注入了 script 标签也跑不起来。但 CSP 的边界在于配置粒度,如果配置了 unsafe-inline 或 unsafe-eval,那这道防线就形同虚设。

CSRF 的防护边界:从 Referer 校验到 Token 同步

CSRF 的本质是攻击者诱导用户在已登录的站点上执行非本意的操作,它利用的是浏览器自动携带 Cookie 的机制。所以防护边界就在如何区分一个请求是用户主动发起的,还是被第三方站点伪造的。最经典的方案是 Anti-CSRF Token,服务端生成一个随机 Token,放在表单隐藏域或请求头里,每次请求都校验这个 Token 是否与用户 Session 绑定且有效。这里的边界在于 Token 的生成必须足够随机,且不能泄露在 URL 里,否则 Referer 头一传出去就全暴露了。另一个边界是 Token 的存储位置,如果放在 Cookie 里,那攻击者通过 XSS 就能轻松读取,所以 Token 必须放在内存或不可被脚本读取的存储中。SameSite Cookie 属性是近年来最有效的 CSRF 防御手段,设置 SameSite=Strict 或 Lax 后,浏览器在跨站请求时根本不会发送 Cookie,从根源上切断了 CSRF 的利用路径。但它的边界在于对老版本浏览器的兼容性,以及某些业务场景确实需要跨站携带 Cookie 的情况,比如单点登录回调。Referer 或 Origin 头校验也是一种常用手段,检查请求来源是否在白名单域名内。但边界在于 Referer 头可能被用户浏览器策略或代理工具隐藏,所以只能作为辅助手段,不能作为唯一依赖。还有一个容易被忽略的边界是 GET 请求,很多人认为 GET 请求没有副作用就不做防护,但如果 GET 请求执行了敏感操作,比如 /delete?id=1,那一个 img 标签就能触发攻击。所以 RESTful 设计必须严格遵守 HTTP 语义,GET 只做查询,变更操作一律用 POST、PUT、DELETE,并且都加 CSRF 防护。

SSRF 的防护边界:从协议限制到内网隔离

SSRF 的可怕之处在于它让服务端成为攻击者的跳板,直接访问内网资源。防护 SSRF 的第一层边界是 URL 解析。攻击者经常利用 URL 解析器的差异绕过检查,比如 http://example.com@evil.com 这种格式,不同的解析器可能把 example.com 当主机,也可能把 evil.com 当主机。所以必须用统一、经过安全审计的 URL 解析库,并且解析后要检查主机名,而不是用正则去匹配原始字符串。第二层边界是协议限制,只允许 http 和 https 协议,禁止 file://、gopher://、dict:// 等危险协议。但即使只允许 http,攻击者还能通过 DNS Rebinding 技术,让域名第一次解析返回合法 IP,第二次解析返回内网 IP,从而绕过主机名检查。所以第三层边界是 DNS 解析后的 IP 校验,解析出 IP 后必须判断是否为内网地址,包括 127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 等私有地址段,以及 169.254.0.0/16 链路本地地址。但攻击者还能通过 302 跳转来绕过,第一次请求外网域名,服务端返回 302 跳转到内网地址,如果请求库默认跟随跳转,那 IP 校验就白做了。所以必须禁用自动跳转,或者对每次跳转的目标都重新做一遍完整的 URL 和 IP 校验。更深层的边界是网络层的隔离,即使代码层面没防住,如果服务本身运行在容器里,网络策略限制只能访问指定的外部服务,那 SSRF 的攻击面就被大幅缩小了。这就是纵深防御的思路,不要把宝全押在代码校验上。

边界模糊导致的绕过案例

在实际攻防中,很多绕过都是因为边界条件没考虑全。比如 XSS 防护中,开发人员只对双引号做了编码,但攻击者用单引号闭合属性值;或者开发人员对 script 标签做了过滤,但攻击者用 img 标签的 onerror 事件执行脚本。比如 CSRF 防护中,Token 校验逻辑在 GET 请求上忘了加,或者 Token 生成算法使用了可预测的时间戳。比如 SSRF 防护中,只检查了 IP 是否为内网,但没检查 0.0.0.0 这种特殊地址,或者用短网址服务做跳板绕过域名白名单。这些绕过都指向同一个问题:防护措施只覆盖了主路径,没覆盖边界情况。安全防护不是做完了 checklist 就万事大吉,而是要理解攻击者的思维,不断追问“如果我是攻击者,我会怎么绕过”。

纵深防御与架构层面的思考

单点防护永远不可靠,真正的安全来自于多层防御的叠加。对于 XSS,前端框架的自动转义是第一层,CSP 是第二层,输入端的业务校验是第三层,HttpOnly Cookie 防止会话劫持是第四层。对于 CSRF,SameSite Cookie 是第一层,Anti-CSRF Token 是第二层,Referer 校验是第三层,敏感操作二次验证是第四层。对于 SSRF,URL 白名单是第一层,协议限制是第二层,IP 校验是第三层,禁用跳转是第四层,网络隔离是第五层。每一层都可能被突破,但多层叠加后,攻击成本会指数级上升。另外,架构设计时就应该考虑最小权限原则,一个服务只访问它需要的资源,即使被 SSRF 利用,能造成的危害也有限。日志监控也是防护边界的一部分,当检测到异常的内网访问请求或大量编码后的 payload 时,及时告警能让你在攻击成功前止损。

这三个漏洞的防护边界,本质上是对数据流动路径的精确控制。XSS 控制的是数据从服务端流向浏览器时的形态转换,CSRF 控制的是请求从浏览器流向服务端时的身份校验,SSRF 控制的是请求从服务端流向外部或内部时的目标校验。把每一条数据流动路径上的边界点都卡死,漏洞就无机可乘。安全不是功能,而是一种贯穿始终的意识。