ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

XSS跨站脚本攻击原理与防御:从反射型到DOM型的完整指南

XSS跨站脚本攻击原理与防御:从反射型到DOM型的完整指南

搞安全这些年,XSS(跨站脚本攻击)是我见过最“熟悉又陌生”的漏洞。说熟悉,是因为它常年霸占各类漏洞榜单的前排,几乎所有做Web开发的朋友都能背出“反射型、存储型、DOM型”这几个字;说陌生,是因为真正能把攻击原理、利用链路和防御细节串成一条线的人,远比想象中少。很多时候,站点被XSS打穿,不是因为开发者不知道XSS,而是因为对“数据到底在哪个环节变成了代码”这件事缺乏体感。

这篇博文我准备用一篇完整的长文把事情讲透:从XSS的底层逻辑、三种类型的攻防差异,到危害链路的真实拆解,再到本地环境的动手复现和一套可以直接落地的防御方案。不管你是刚入门的Web开发者、前端新手,还是已经在做安全工作、想补强基础的安全爱好者,这篇内容都值得你花二十分钟认真读一遍。我会把踩过的坑和验证过的手段都写出来,尽量让你看完就能直接用上。

1. 先搞懂XSS的本质:浏览器被“借刀杀人”了

XSS全称是Cross-Site Scripting,跨站脚本攻击。很多人第一次听到这个名字会觉得奇怪,明明是脚本攻击,为什么要叫“跨站”?这里有一段历史渊源,早期的攻击方式喜欢把恶意脚本放在第三方站点上,诱导受害者去点击,所以叫“跨站”。发展到今天,XSS早就不是为了“跨站”才存在,但这个名字一直沿用了下来。理解XSS,关键是抓住一句话:攻击者想办法让受害者的浏览器执行了攻击者提供的、原本不该执行的脚本代码。

你可以把浏览器想象成一座安检严格的大楼。正常情况下,只有大楼自己的工作人员(站点自身的代码)能进控制室。但XSS做的事情,是让外来人员(攻击者的代码)伪装成工作人员,混进大楼后还能摸到控制面板。一旦脚本在受害者的浏览器里跑起来,它就拥有了这个页面赋予给正常脚本的所有权限,包括读取页面内容、操作DOM、发送网络请求、读取Cookie等等。也就是说,XSS攻击的受害者不是服务器,而是浏览器里那个正在使用网站的用户。

从工程角度说,XSS之所以普遍,是因为Web应用的本质就是“拼接”。页面上的每一个字符,几乎都经历了一遍“服务端数据 → 浏览器渲染”的过程。只要某个环节把用户可控的数据当成可执行代码输出了,漏洞就产生了。很多开发者习惯在前后端直接拼接HTML字符串,比如用innerHTML插一段包含用户昵称的片段,这就像在传纸条时顺手附上了一把钥匙,攻击者只需要在纸条上多加几个标签,钥匙就到手了。

搞清楚XSS的本质之后,你还需要建立一个基本认知:XSS虽然被归类为“前端漏洞”,但它往往能撬动后端的权限。攻击者利用XSS拿到用户的会话标识后,就能以受害者的身份向后端接口发起任意请求。这也是为什么越是大厂,越会把XSS列入高危漏洞,它表面上是浏览器层面的问题,实际撬动的是整个账号体系的安全。

2. 三种类型的XSS:数据走向决定攻击形态

XSS的分类标准其实很简单,就看数据最终是在哪里被当成了代码执行。不同执行位置决定了漏洞的发现难度、利用方式和修复成本。这一节我把三种类型放到一条完整的数据流里讲。

2.1 反射型XSS:攻击代码藏在URL里,服务器原样弹回来

反射型XSS的数据链路是:攻击者构造一个带恶意参数的特殊URL,把这个URL发给受害者;受害者点击后,浏览器带着恶意参数去请求服务器;服务器把参数内容拼进HTML响应的某个位置,原样返回给浏览器;浏览器解析HTML时,恶意脚本被当作页面脚本执行。

因为恶意代码“反射”了一下又回到受害者浏览器,所以叫反射型。它的最大特点是必须诱导受害者点击攻击者提供的URL,没法自己主动传播。常见的触发点包括搜索框、URL参数、报错提示等。举个例子,一个搜索页面如果直接把keyword参数值拼进页面:

<p>你搜索的关键词是:<?php echo $_GET['keyword']; ?></p>

如果我构造URL为/search.php?keyword=<script>alert(document.cookie)</script>,那么响应页面就会变成:

<p>你搜索的关键词是:<script>alert(document.cookie)</script></p>

浏览器解析时,<script>标签自然就被执行了。这种场景到处可见,尤其是一些老旧的内部系统,搜索框从不做转义,随手一测就是漏洞。

反射型XSS的危害受到URL长度、输入过滤、浏览器编码等因素的影响,但在实际攻防中,它的利用价值一点不低。攻击者可以把恶意URL缩短、伪装成二维码、做成邮件里的超链接文字,甚至藏在文档里,只要受害者点击,脚本照跑不误。修复的核心是:凡是把请求参数原样打印到页面上的地方,都必须做输出编码。

2.2 存储型XSS:代码进了数据库,什么时候触发由不得你

存储型XSS的数据链路更长:攻击者把恶意代码通过表单、评论、昵称、头像URL等入口提交到服务器;服务器没有过滤,直接把代码存进数据库;之后任意用户访问包含这条数据的页面时,服务端从数据库取出数据并拼进HTML,恶意脚本就在受害者的浏览器里稳定执行。

存储型XSS之所以比反射型更危险,是因为它的触发不依赖任何点击行为,也不需要针对特定受害者。攻击者只需要把“钉子”埋进去一次,之后所有访问该页面的用户都会中招。打个比方,反射型像是你递出一张写着咒语的门票,对方得愿意接;存储型则像是在大楼的水泥墙里埋了定时炸弹,只要大楼建成,谁来都得挨炸。

论坛签名档、评论区、商品评价、用户昵称、简历描述,这些都是存储型XSS的重灾区。历史上很多大规模XSS蠕虫事件都是存储型XSS做的,攻击者把脚本埋进页面,脚本又自动发起请求写入新的恶意数据,像滚雪球一样越滚越大。防御存储型XSS必须同时守住两端:输入端要做严格的校验和过滤,输出端要做语义化的编码。只靠任何一端的努力都不够稳。

2.3 DOM型XSS:不经过服务器,前端自己把自己卖了

DOM型XSS和上面两种有本质区别:它的数据完全不经过服务端。整个攻击过程发生在浏览器端:URL中的参数或localStorage中的数据被JavaScript获取,然后被写进DOM的某个位置,浏览器在渲染时直接把它当成代码执行。

典型场景是这样的,页面有一段JavaScript代码去获取URL参数,然后把它插入到页面元素里:

const name = new URLSearchParams(location.search).get('name'); document.getElementById('welcome').innerHTML = '欢迎你,' + name;

当我访问/welcome.html?name=<img src=1 onerror=alert(document.cookie)>时,JavaScript从URL里取出这个值,直接拼进innerHTML,一个onerror事件就把脚本给触发了。整个过程服务器只负责返回静态页面,服务端日志里甚至看不到任何异常,排查起来也最隐蔽。

DOM型XSS的检测难点在于,它不一定能在服务端响应里看到恶意代码,因为代码不是服务端生成的,而是前端脚本在本地动态插进去的。这就导致很多自动化扫描器对DOM型XSS无能为力。前端开发同学如果习惯操作innerHTML、document.write、outerHTML、eval这些接口,一定要格外小心。修复手段同样是“输出编码”,但在前端这边不是简单的服务端转义,而是尽量用textContent、innerText、setAttribute这类不会触发HTML解析的接口来写入动态内容。

2.4 三种类型的对比速查表

类型恶意代码存储位置触发条件排查难度危害范围
反射型XSS不在服务器存储,随URL走受害者必须点击构造的URL较易,服务端响应能直接看到单体受害者
存储型XSS存进服务器数据库任意用户访问受影响页面即触发中等,需排查数据输入点和输出点所有访问用户,可能产生蠕虫
DOM型XSS仅在浏览器内存/URL中浏览器端脚本取值并执行,服务器无感知较难,服务端看不到代码执行痕迹单体或局部受害者

这张表建议你存下来。平时做代码审计或漏洞评估时,先判断数据流属于哪种形态,再决定往哪个方向排查,效率会高很多。

3. 危害链路拆解:XSS能造成的破坏远不止弹个框

很多没做过安全的人对XSS的印象停留在“页面弹窗”这种恶作剧层面。但实际上,XSS是Web安全里效果最直接、变化最多的漏洞之一,它能被利用的方向极其丰富。我拆几个最常见的利用场景,帮你建立完整的危害认知。

3.1 会话劫持:拿到Cookie就能冒充你登录

攻击者通过XSS执行document.cookie,把当前用户的会话标识发送到自己的服务器。之后攻击者把这个Cookie塞进自己的浏览器,就完成了会话劫持。服务器不关心请求到底来自哪台电脑,它只认Cookie。这个过程的本质是偷取“身份凭证”。需要注意,只要是Cookie没有被标记HttpOnly,JavaScript就能读取,这个属性是防御会话劫持的一道重要闸门。

3.2 钓鱼与诱导:你信任的是域名,不是页面

浏览器地址栏显示的域名是合法的,用户在页面上看到的也是正常的业务内容,但页面内部却被嵌入了攻击者的表单或提示。“您的会话已过期,请重新登录”,这个弹窗如果出现在正常域名下面,用户的防备心会大幅降低。XSS能做到在合法页面里伪造一切内容,这是钓鱼攻击最凶险的形态之一——所有信任都被合法域名承担了。

3.3 键盘记录与敏感信息窃取

恶意脚本可以在页面里注册keydown事件,实时记录用户在这个页面输入的所有按键,包括账号、密码、验证码、搜索关键字。用户以为自己在向正常网站输入信息,但这些信息正被脚本悄悄转发到第三方。这类利用方式不需要任何系统漏洞,只要XSS能执行,键盘记录就挡不住。

3.4 蠕虫式传播

存储型XSS结合Ajax请求,可以在受害者浏览器里自动执行写操作。比如脚本读取当前用户的身份认证信息,自动向接口提交新的恶意内容,把自己复制到更多页面。一个用户中招,脚本就替他发布恶意内容,下一个用户访问又中招,形成自动化传播。历史上著名的XSS蠕虫事件,就是这样在极短时间内感染了大量用户。

强调一句,理解这些危害链条不是为了让谁去搞破坏,而是因为防御方必须比攻击者更懂攻击路径。不知道攻击能到哪里,就不知道防御要做到哪里。安全领域的对抗,本质是认知的对抗。

4. 动手实践:在本地环境完整复现一次XSS

纸上谈兵没有用。我建议你跟着这一节,在自己的电脑上搭建一个最小实验环境,把三种XSS都触发一遍,亲眼看看代码是怎么一步步变成执行的。务必记住:实验环境一定要隔离,绝不要拿公网站点、真实业务系统或者未经授权的主体做测试。安全测试的底线就是授权。

4.1 环境准备

最小实验环境只需要两个文件,不需要装数据库,不需要装复杂框架。我习惯用Python起一个极简HTTP服务,然后准备三个HTML/PHP文件来模拟不同漏洞场景。

如果你有Python环境,直接建一个目录,里面放页面文件,然后在目录下执行:

python3 -m http.server 8080

这样http://localhost:8080目录下的静态文件就能被浏览器访问了。对反射型和DOM型XSS,静态文件足够了;存储型XSS的完整数据链路会需要后端存储,但为了看懂原理,可以用浏览器自带的localStorage模拟“存储”的动作。

4.2 反射型XSS复现过程

在实验目录建一个简单的服务端页面,因为这里用的是静态服务器,我直接把PHP语义换成静态HTML模拟。实际反射型XSS需要服务端脚本拼接参数,你可以把文件改成.php并用php -S localhost:8080跑起来,或者用任何支持服务端模板的语言。

以PHP为例,代码长这样:

<?php $keyword = $_GET['keyword'] ?? ''; ?> <html> <body> <h2>搜索演示</h2> <p>你搜索的关键词是:<?php echo $keyword; ?></p> </body> </html>

访问:

http://localhost:8080/search.php?keyword=<script>alert(document.domain)</script>

浏览器弹窗,说明脚本执行了。这时候你打开开发者工具的“网络”面板,能看到这次请求的路径里带着完整的恶意参数。再把<?php echo $keyword; ?>改成<?php echo htmlspecialchars($keyword, ENT_QUOTES, 'UTF-8'); ?>,刷新页面,恶意代码就变成了纯文本显示,不再触发执行。这就是输出编码的直观效果。

4.3 DOM型XSS复现过程

DOM型XSS不需要服务端脚本,静态文件就能演示。建一个dom.html:

<html> <body> <div id="welcome"></div> <script> const name = new URLSearchParams(location.search).get('name'); document.getElementById('welcome').innerHTML = '欢迎你,' + name; </script> </body> </html>

访问:

http://localhost:8080/dom.html?name=<img src=1 onerror=alert(document.cookie)>

你会在“网络”面板里发现,服务器返回的HTML只有静态内容,恶意代码完全是在浏览器端脚本执行时才被插入的。这一点非常关键:排查DOM型XSS不能只看服务端响应,还要跟踪前端的DOM操作链。

改成安全的写法:

document.getElementById('welcome').textContent = '欢迎你,' + name;

恶意标签就变成一段普通文本了。textContent和innerHTML的差别,就是“数据”和“代码”的差别,这正是XSS防御的核心哲学。

4.4 存储型XSS的模拟演示

存储型XSS的完整链路需要一个能写数据的后端,但理解原理可以先在前端模拟:用一个表单把内容保存到localStorage,另一个页面再从localStorage取出来渲染。这个流程完美还原了“提交入库、取出展示”的动作。

模拟存储页面:

<input id="content" placeholder="输入内容"> <button onclick="save()">提交</button> <script> function save() { localStorage.setItem('msg', document.getElementById('content').value); } </script>

模拟展示页面:

<div id="display"></div> <script> document.getElementById('display').innerHTML = localStorage.getItem('msg'); </script>

在存储页输入<script>alert('stored xss')</script>并提交,再打开展示页,你会看到脚本执行了。虽然浏览器localStorage里的数据只在本地,但逻辑和真实存储型XSS完全一致。把这个思路迁移到真正的数据库场景,你就能理解为什么入库前过滤和出库前编码必须双管齐下。

4.5 实验过程中值得关注的细节

  • 打开开发者工具的“元素”面板,对比恶意场景下innerHTML插入后的DOM结构和textContent插入后的差异。
  • 在“网络”面板里观察请求和响应,反射型XSS能在响应体里直接搜到恶意代码,DOM型XSS则搜不到。
  • 尝试不同标签和事件属性,比如<img src=x onerror=alert(1)>、<svg/onload=alert(1)>,理解为什么有些位置能触发、有些位置不能。

5. 防御必修课:从源头堵住XSS

修复XSS不是靠一两句歪招,而是要靠一套纵深防御体系。我从四个层面展开讲:输入校验、输出编码、安全头配置、框架规范。每一层都有不可替代的作用。

5.1 输入校验:白名单永远优于黑名单

输入校验的目的是在数据进入系统之前,把明显危险的内容挡住。但必须明确一点,输入校验只能作为第一道闸门,不能作为唯一防线。实践中删除<script>、过滤onerror这类黑名单思路是最不靠谱的,因为绕过姿势太多,大小写变化、编码嵌套、换行拆分都能让黑名单失效。白名单思路则可靠得多:明确这个字段到底应该是什么格式,比如手机号只允许数字和短横线、用户名只允许字母数字和下划线、年龄只允许正整数,不符合格式就拒绝。通过白名单校验,攻击者在大多数业务字段里根本没有插入标签的机会。

5.2 输出编码:数据与代码彻底分离

输出编码是防XSS的灵魂。它的核心思路是:**数据在进入HTML上下文之前,必须被转换成纯粹的文本表示,让浏览器不再把它解析为标签或脚本。**不同的上下文需要不同的编码方式:

  • HTML实体上下文:在HTML标签内部显示文本时,把<转成&lt;、>转成&gt;、&转成&amp;、双引号转成&quot;、单引号转成&#39;。
  • HTML属性上下文:如果数据要拼进<a href="...">或<input value="...">这样的属性里,除了HTML实体编码,还要确保属性值使用引号包裹,防止数据中的引号提前“闭合”属性导致新属性注入。
  • JavaScript上下文:数据如果出现在<script>里的字符串中,需要做JavaScript字符串转义,并且尽量避免用字符串拼接方式构造动态代码。
  • URL上下文:数据如果用于href或src,要校验协议只允许http、https、mailto等白名单。

不同后端框架都有现成的编码函数,比如Java里可以用StringEscapeUtils.escapeHtml4,Python里可以用html.escape,前端则优先使用textContent而不是innerHTML。重点不是你手动去转,而是制定一个项目级编码规范,规定不同类型数据在不同位置的输出必须调用对应编码函数。

5.3 前端写DOM的黄金三原则

前端是XSS执行端的最后一米,有些习惯必须刻在脑子里:

  • textContent能解决的,绝不碰innerHTML。
  • setAttribute能设置属性值的,绝不拼HTML字符串。
  • 确实需要渲染富文本的场景,不要自己写过滤器,直接引入经过安全验证的富文本白名单库,把标签和属性严格圈定在安全范围内。
  • 对eval、setTimeout字符串形式、new Function、document.write这些危险接口,全局禁用或做强制代码审计。

5.4 安全头配置:给浏览器下达防御指令

服务端通过响应头告诉浏览器如何约束自己的行为,这一层几乎零成本,收益却非常直接。

HttpOnly是Cookie的一个属性,设置后JavaScript读取不到document.cookie,XSS窃取会话的能力会被直接废掉一块。注意,HttpOnly只能防“读取Cookie”,不能防“以用户身份发请求”,所以它只是一道辅助闸门。

Content-Security-Policy(CSP)是另一张王牌。CSP可以限制页面允许加载和执行脚本的来源。一个常见的严格配置是这样:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'

这个配置的意思是:所有资源默认只能从本站加载,脚本只能从本站加载,禁止任何外部域名的JS。一旦这条生效,即使攻击者找到了注入点,他内联的<script>或者指向外部域名的恶意脚本也会被浏览器直接拦截。部署CSP初期可能会因为页面加载第三方统计脚本、CDN资源而出现报错,你需要逐步放行并观察上报日志,但这套机制值得认真投入。

5.5 框架的默认防护与二次编码问题

现代主流前端框架(记住一点:用框架不等于没有XSS)默认对绑定的文本内容做转义,这是极大的安全红利。真正容易出问题的,是框架提供的“插HTML”接口,比如v-html、dangerouslySetInnerHTML。看到这些接口就要条件反射般提高警惕,确认插入的内容是可信的、经过白名单过滤的。

还有一类非常隐蔽的坑叫“二次编码”或“双重解码”。有些系统做了输出编码,但在某个环节又做了一次unescape、decodeURIComponent、或数据库读取后的解码操作,导致之前的编码被还原,数据重新变成可执行代码。排查这类问题时,需要沿着数据流一层层检查,不能只盯着一处。

6. 安全测试与自查:如何把站里的XSS找出来

给站点做XSS排查,不能只靠扫描器,也不能只靠人工翻代码。一个成熟的做法是两者结合,先自动化扫出可疑点,再人工验证和利用。

6.1 自动化扫描的定位与局限

常见的自动化工具分两类:一种是爬虫加漏洞验证的一体化扫描器,能爬取页面、填表、注入测试向量并判断响应变化;另一种是代理型测试工具,需要人工操作浏览器,工具在后台分析流量并辅助测试。两类工具各有各的价值。

用自动化工具跑完一轮,得到的报告需要你人工二次确认,因为扫描器很容易误报。比如某些站点会在输入框中直接回显用户输入,扫描器看到参数值出现在响应里就报一个反射型XSS,但实际上输出位置经过转义、或者环境不允许脚本执行,这就属于误报。反过来,扫描器抓不到DOM型XSS的情况也常见,因为DOM型XSS不经过服务端拼接,工具很难从响应体里判断漏洞特征。

6.2 手动测试的思路和姿势

手动测试XSS,最关键的技巧是“判断输出位置”。拿到一个输入点,先随便输入一串特殊字符,比如:

"\'<>&<script>alert(1)</script>

然后去查看响应源码,看这些字符被原样输出在什么上下文里,有没有被转义。根据输出位置分四种情况:

  • 原样输出在HTML标签之间,尝试<script>或<img onerror>。
  • 输出在属性值里,尝试用"闭合属性后插入新的事件属性。
  • 输出在<script>里的字符串中,尝试闭合字符串后执行代码。
  • 输出在注释里或CSS里,尝试注释闭合或expression之类(旧IE场景较少,但仍值得了解)。

手动验证比扫描器更能理解业务逻辑。比如搜索框、评论框、昵称这些每个字段都值得测一遍,尤其要关注“编辑后展示”的二次渲染路径,很多漏洞藏在保存和展示之间。

6.3 容易被忽略的高危盲区

根据我的经验,有四个地方是XSS排查中最常被忽略的:

第一,JSONP接口。很多老系统用JSONP跨域取数据,callback参数直接拼进返回的JavaScript代码里,这里几乎逢测必有。

第二,自定义错误页面。应用框架的错误页如果回显了原始URL或请求参数,且没有转义,就是一个隐藏的反射型XSS。

第三,文件上传后的文件名展示。图片名、附件名如果原样显示在页面上,而文件名里带了"><script>,照样能打。

第四,富文本编辑器的粘贴内容。用户从Word、网页里复制粘贴内容时,前端如果不做清洗,<img onerror>、<a href="javascript:...">都可能混进来。

这四处地方,建议你在自查清单里单独列一行,重点检查。

7. 踩坑实录:这些年遇到过的XSS防御翻车现场

最后分享几个我在实际项目中遇到过的真实问题,为了让描述不涉及具体单位,我把系统代称为“模拟项目X”,但问题和解决思路都是原汁原味的。

7.1 “做好了输出编码,还是被打了”的二次解码事故

模拟项目X有一个数据同步模块,从上游系统拉取内容后,在前端展示。开发同学在展示位置做了HTML编码,测试阶段一切正常。上线一个月后,安全巡检发现该模块可以被XSS打到。排查后发现,数据链路里有一个历史遗留的公共组件会对内容统一做一次HTML解码,开发同学的编码在这层解码里被“拆掉”了。这就是非常典型的二次编码问题。修复方式是把编码动作下沉到最靠近输出的位置,同时移除中间层的自动解码逻辑,并在代码评审规范里明确“输出编码只做一次、只做在渲染前”。

7.2 只对参数做了过滤,却忘了把数据存进数据库之前的入库逻辑

模拟项目X的用户评论功能,前端做了onclick、onerror等关键词的黑名单过滤,测试用例也过了。后来工程师在一次攻防演练中发现,攻击者通过直接调用后端接口,绕过了前端过滤,把恶意数据传了进去。因为后端入库前没有校验,出库展示时也没有编码,导致存储型XSS。这个案例的教训非常深刻:前端的任何过滤都是为了用户体验,不能当作安全边界。真正的安全检查必须落在服务端,而且要同时覆盖入库前和输出前。

7.3 误以为CSP是万能药,结果内联脚本业务被一刀砍断

模拟项目X在引入CSP时,直接设置了script-src 'self',结果线上大量使用内联事件处理器和内联<script>的旧页面全部失效,报错日志堆积如山,团队被迫紧急回滚。CSP是好东西,但也要讲究落地节奏。稳妥的做法是先通过Content-Security-Policy-Report-Only模式观察线上情况,收集所有违规报告,逐一修整页面后,再切到强制模式。旧系统改造CSP不是一蹴而就的,需要耐心。

7.4 自动化扫描报告一片绿,手动一测却立刻出洞

这个项目我印象最深。当时自动化扫描器跑完内部系统,报告显示全部通过,但我用浏览器手动访问一个带参数的导出页面,在参数里插入"><svg/onload=alert(1)>,结果系统返回错误页时把完整参数原样打印了出来,成功弹窗。原因是扫描器只检查了业务页面,没有检查自定义错误页面。这次之后我养成一个习惯:凡是接收参数的应用,除了主流程页面,错误页、跳转页、空结果页也要纳入输入输出检查范围。

写在最后的一点真实体会

XSS这个东西,真正理解它的关键不在于背多少payload,而在于建立“数据与代码边界”的思维习惯。开发时多问一句:这段数据最终会出现在什么位置?它有没有可能被浏览器当成代码解释?如果有,就立刻做编码和验证。防御XSS也没有一劳永逸的方案,它需要的是一层层叠加的纵深防御,从输入校验、输出编码、安全头配置到代码审计。

我个人的建议是,无论你做前端还是后端,都要试着亲手在本地环境触发一次XSS,再看一次转义后代码的变化,然后把这些经验固化成团队的文化。安全意识和工程习惯,才是防住XSS最可靠的东西。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进