XSS上下文注入¶
常见上下文场景¶
1. 标签间注入 (HTML Body Context)¶
数据直接输出在 HTML 标签闭合处(如
<div>数据</div>)。
直接注入 <script> 标签:
借助带有事件回调的标签(常用 <img> 或 <svg>):
<!-- 触发图片加载失败事件 -->
<img src="x" onerror="alert(1)">
<!-- SVG 标签在解析时即触发 onload -->
<svg onload="alert(1)">
2. HTML 属性内注入 (Attribute Context)¶
数据输出在标签属性值中(如
<input value="数据">)。
模板 A:闭合属性与标签(跳出属性)
模板 B:利用现有标签事件(无需闭合标签)
模板 C:伪协议(适用于 href、src、action 等属性)
3. JavaScript 脚本块内注入 (Script Context)¶
数据输出在
<script>内部(如<script> var name = "数据"; </script>)。
模板 A:闭合当前的 JavaScript 语句或闭合整段脚本
模板 B:利用 ES6 模板字符串(若输出在反引号 数据 内)
速查表:你的输入最终显示在哪里?¶
├── 在 <script> 标签内部 ──────→ 用 Quotes 闭合变量 (例如 "; alert(1); //)
├── 在 <a> 的 href 属性里 ─────→ 用 伪协议 (例如 javascript:alert(1))
├── 在 普通属性 里 (如 value) ──→ 先用 "> 闭合属性,再接 <img> 或 <svg>
└── 在 <div> 等标签文本间 ────→ 首选 <script>;若被拦截,换成 <img onerror> 或 <svg onload>
简洁版 Payload 收藏¶
<script>alert(1)</script>
<img src=1 onerror=alert(1)>
<svg onload=alert(1)/>
<a href=javascript:alert(1)>xss</a>
为什么需要闭合¶
为什么要闭合?难道直接在后面添加 script 脚本不行吗?
这是因为 HTML 解析器在处理代码时,有着非常严格的语法规则。如果不闭合,你输入的脚本只会被浏览器当作**普通文本数据**处理,而根本不会被当作**可执行的代码**。
通过下面这个对比,就能一眼看懂原因:
假设后端的渲染逻辑是把用户的输入放到 value 属性中:
情况一:不闭合¶
如果你在搜索框直接输入:
最终生成的 HTML 页面代码是:
浏览器是如何解读的?
浏览器从左往右解析 HTML。当它看到 <input type="text" value=" 时,它知道:接下来引号里的所有内容,全是这个输入框的文本值。
即使引号里面出现了 <script>,浏览器也会认为这**只是用户想在文本框里显示的一串普通文字**(就像有人在搜索框搜"如何写 script 标签"一样)。
结果**:页面上成功展示了一个输入框,里面填着
<script>alert(1)</script>这一串字,没有任何弹窗触发。
情况二:进行了"闭合"操作¶
如果你输入的字符串是:
最终生成的 HTML 页面代码是:
浏览器是如何解读的?
- 浏览器看到
<input type="text" value=",开始读取属性值。 - 读到了你输入的第一个
",浏览器认为:value属性到此结束(闭合)! - 紧接着读到了
>,浏览器认为:<input>这个 HTML 标签到此完全结束(闭合)! - 接下来读到了
<script>alert(1)</script>:此时浏览器已经位于标签外部,它会判定**这是一个独立的 JavaScript 代码块**,于是立刻开始运行它!
结果:代码被成功执行(触发弹窗)。
分析DOM上下文¶
在浏览器 F12(开发者工具)中定位输入的 DOM 上下文,本质上就是寻找**"你的输入被放置在 HTML 源码的什么结构里"**。
下面以 Chrome / Edge 为例,用一套标准的操作流程和实际场景,带你快速完成分析与 Payload 匹配。
一、实操分析 4 步走¶
第一步:发送唯一探针字符串¶
在目标输入框或 URL 参数中,提交一个容易被全局搜出的**高辨识度字符串**(探针),最好包含常见的特殊字符(用于测试是否被转义)。
推荐探针:
第二步:利用 F12 检索定位¶
- 按 F12(或右键 → 检查)打开开发者工具。
- 快捷键 Ctrl + F(Mac 上为 Cmd + F),输入你的探针字符串
test1234。 - 检查匹配到的所有位置(注意区分是服务器渲染的 HTML 还是 JS 变量,或是后续动态生成的 DOM)。
第三步:分析探针的 DOM 结构(上下文)¶
定位到输入点后,查看探针被哪个标签、属性或脚本块包裹:
| 检查项 | 分析要点 |
|---|---|
| 看转义情况 | 探针里的 ' " < > 是否被变成了 ' " < >?如果全部被转义为实体字符,且不在 javascript: 属性或未经过 DOM 渲染,通常说明过滤有效。 |
| 看闭合需求 | 探针的前后紧挨着什么符号? |
闭合需求速查:
| 探针所在位置 | 闭合方法 |
|---|---|
在 "..." 内 |
需要用 " 闭合 |
在 <div>...</div> 内 |
不需要闭合属性,但可能需要闭合其他标签 |
在 <script>var a='...';</script> 内 |
需要用 ' 闭合变量,或用 </script> 闭合整个脚本块 |
第四步:构造 Payload 并验证¶
根据闭合逻辑拼接代码,提交后再回 F12 观察:你的 Payload 是否成功破坏了原本的 HTML 结构,并独立成为了一个新的标签或执行语句?
二、常见 DOM 上下文实操图解¶
场景 1:标签文本间上下文¶
F12 看到的渲染效果:
- 上下文分析:处于
<p>标签之间,且<被转义,但如果直接输入新的标签无阻碍。 - 测试 Payload:
<script>alert(1)</script>或<svg onload=alert(1)>
成功突破后的 DOM 结构:
场景 2:HTML 属性上下文¶
F12 看到的渲染效果:
- 上下文分析:探针落在了
value="..."的双引号内部。 - 构造逻辑:需要先用
"闭合前面的value属性,再用>闭合整个<input>标签,最后接新标签。 - 测试 Payload:
"><img src=x onerror=alert(1)>
成功突破后的 DOM 结构:
场景 3:JavaScript 脚本块上下文¶
F12 看到的渲染效果:
- 上下文分析:处于双引号包裹的 JS 字符串变量中。
构造逻辑:
| 方案 | 方法 | 原理 |
|---|---|---|
| 方案 A | 闭合 JS 语句 | 用 " 闭合变量,用 ; 隔开,写 JS 代码,再用 // 注释掉后面的双引号。 |
| 方案 B | 直接标签截断 | HTML 解析器的优先级高于 JS 解析器。直接用 </script> 可以强制终止当前脚本块! |
测试 Payload:
- 方案 A:
"; alert(1); // - 方案 B:
</script><script>alert(1)</script>
成功突破后的 DOM 结构(以方案 A 为例):
场景 4:URL / 属性伪协议上下文¶
F12 看到的渲染效果:
- 上下文分析:探针落在了
<a>标签的href属性中。 - 构造逻辑:当点击该链接时,浏览器会解析
href里的协议头。直接使用javascript:伪协议即可,不需要任何闭合符号(除非有引号包裹干扰)。 - 测试 Payload:
javascript:alert(1)
成功突破后的 DOM 结构:
三、F12 调试小技巧¶
利用 Elements 面板 vs Sources 面板¶
| 面板 | 显示内容 |
|---|---|
| Elements(元素) | 显示的是浏览器**解析并渲染后**的最终 DOM 树。 |
| Network / Sources(源码) | 显示的是服务器返回的**原始** HTML 响应。 |
💡 技巧:如果 Sources 里有你的 Payload 但 Elements 里没有触发,说明可能是前端 JS(如 React/Vue)对数据做了二次安全处理。
使用 Console(控制台)验证¶
如果你不确定某个 DOM 节点是通过什么 JS 逻辑渲染的,可以在 Elements 面板选中该节点,然后在 Console 里输入 $0($0 代表当前选中的 DOM 元素),直接调出进行调试。