一篇让你从入门到精通的 XSS 实战笔记
一、Pikachu —— 入门首选的 XSS 练兵场
Pikachu 这个靶场真的是入门首选,关卡设计很有层次感,从最基础的无过滤注入到各种绕过技巧,打完一整套下来对 XSS 的理解能上一个大台阶。不多说,直接开攻!
1. 反射型 XSS(GET)
这是 Pikachu 的第一关,纯纯的“让你体验一下什么叫 XSS”。输入框有长度限制,但别慌,审查元素改一下 maxlength 的值就行,然后直接注入就完事。没有任何过滤,纯纯的“欢迎来到前端世界”。
Payload 如下:
|
<script>alert(/xss/)</script> |
2. 反射型 XSS(POST)
和上一关原理一模一样,只不过这次变成了 POST 请求。需要先登录后台(账号 admin/123456),然后在输入框里直接丢 Payload,一样无过滤。说实话,POST 和 GET 的区别在于攻击面不一样:GET 可以通过 URL 诱导点击触发,POST 则需要借助自定义表单或者某些“巧妙”的方式来触发。
Payload 如下:
|
<script>alert(/xss/)</script> |
3. 存储型 XSS
这个就有意思了!Payload 会被写入数据库,之后每次访问这个页面的人都会触发弹窗。存储型 XSS 的危害性在于它是“持久化”的,不像反射型那样一次性触发,而是谁来谁中招,是三种类型里危害最大的。同样没有任何过滤,直接丢就完事。
Payload 如下:
|
<script>alert(/xss/)</script> |
4. DOM 型 XSS

前面几关都是服务端渲染的漏洞,这关开始接触纯前端的 DOM 型 XSS 。核心原理是你的输入被拼接到了一个 a 标签的 href 属性里,所以我们只需要闭合掉这个 a 标签,然后插入自己的代码就行。这种漏洞有个特点:Burp 抓不到服务端的交互,因为一切都发生在浏览器端,纯纯的前端事儿。
两种 Payload 都能打:
|
'><img src="#" onmouseover="alert('xss')"> |
|
' onclick="alert('xss')"> |
5. DOM 型 XSS-X
原理和上一关一样,但触发链更长,中间多了几层 DOM 操作。其实就是让你理解“触发链越长,漏洞越隐蔽”这个道理,但本质还是那个本质——输入没被安全处理就被拼接进 DOM 了。Payload 和上一关完全一样,这里就不重复写了(效率起见!
Payload 同第 4 关,此处略~
6. XSS 盲打
这个有意思啦!前端提交后你会发现——哎?怎么没弹窗?别急,这就是“盲打”的精髓所在:Payload 其实已经打到后台了,只不过不在你的前端触发而已。你需要访问 /xssblind/admin_login.php,登录后台才能看到效果。现实中的盲打往往是把 Payload 发到管理员会看到的地方,等管理员一点就中招。
Payload 如下:
|
<script>alert(/xss/)</script> |
7. XSS 之过滤
终于有过滤了!但这个过滤写得太随意了——只是简单地用黑名单过滤了 script 关键字,而且还没有统一大小写。所以我们只需要把大小写混着写,就能轻松绕过。这告诉我们一个重要的道理:写过滤规则的时候,一定要注意大小写统一处理,否则就是给攻击者留了后门。
Payload 如下:
|
<SCRIPT>alert(/xss/)</sCRIpt> |
8. XSS 之 htmlspecialchars
这关开始有点技术含量了。开发者用了 PHP 的 htmlspecialchars() 函数来转义特殊字符,但是!这个函数默认情况下不会转义单引号。而我们的输入点恰好是在一个用单引号包裹的属性里,所以可以利用单引号闭合属性值,构造事件注入。这个例子很经典,很多开发者都会在这里踩坑。
Payload 如下:
|
#' onclick='alert(/xss/) |
9. XSS 之 href 输出
输入被拼接到 a 标签的 href 属性中,双引号被转义了,但我们还有 javascript 伪协议这个神器。在 href、src、action 等属性中,浏览器会把 javascript: 开头的内容当作 JS 代码执行。所以只需要输入 javascript: 伪协议,就能绕过标签过滤,直接执行代码。这是 XSS 绕过里非常实用的一招。
Payload 如下:
|
javascript:alert(/xss/) |
10. XSS 之 js 输出
最后一关,输入被拼接到 JS 变量赋值语句中,我们可以通过闭合 script 标签或者闭合字符串来注入。两种思路都行:一是直接先闭合当前的 </script>,再开一个新的 script 标签;二是闭合字符串后注入自己的代码,再注释掉后面的内容。

Payload 1 —— 闭合标签法:
|
</script><script>alert(/xss/)</script> |
Payload 2 —— 闭合字符串法:
|
';alert(1);// |
写 XSS 就是有这种一关接一关往下写的爽感,像上瘾一样停不下来(qwq。不过 Pikachu 的十关打完,基本就能对 XSS 的各种形态有个直观的认识了。接下来换另一个经典靶场继续磨练!
二、DVWA —— 经典之作,四个难度逐层拆解
哎,经典之 DVWA,安全学习的“入门圣经”。它的好处在于同一个漏洞分了四个难度级别(Low、Medium、High、Impossible),让你能很直观地看到防御是如何逐步升级的。这次就开启梭哈模式,每个级别简单打个 Payload 就完事(喔~
1. Low 级别 —— 光影窗台,无防御
没有任何防护,纯纯的“我就站在这里等你来打”。三种类型的 XSS 都可以用最基础的 Payload 直接打穿:

DOM 型:
|
<script>alert(1)</script> |

反射型:
|
<script>alert(/xss/)</script> |


存储型:
|
<script>alert(1)</script> |
小提示:存储型需要 F12 修改 maxlength 长度限制的值哦~
2. Medium 级别 —— 防御小作,绕过即可
DOM 型:输入被拼接在 select/option 标签中,我们需要先闭合这些标签,再插入恶意代码。这里用的是 img 标签的 onerror 事件来触发:
|
</option></select><img src=1 onerror=alert(/xss/)> |

反射型:这里用的是经典的双写绕过。DVWA 使用 str_replace() 做了一次替换,但这个函数不会递归执行。所以只要在关键字中间再嵌套一层,被删掉之后刚好还原成完整的标签:
|
<sc<script>ript>alert(/xss/)</script> |


存储型:这里用 HTML 实体编码来绕过。将每个字符转义为 &#xx; 的形式,浏览器解析时会自动还原成原始字符并执行:
|
<script>alert(1)</script> |
同样需要 F12 修改 maxlength 长度的值~
3. High 级别 —— 正则过滤,换个姿势
DOM 型:script 标签被过滤了,但我们可以用 # 号构造锚点。这样 default 参数就不会参与 URL 拼接,而 # 后面的内容依然会被浏览器解析执行。这个思路很巧妙,利用了浏览器的锚点机制:
|
#<script>alert(/xss/)</script> |

反射型:script 被正则过滤了,但谁说只能用 script 标签?换成 img 标签的 onerror 事件,同样能执行 JS:
|
<img src="" onerror=alert(1)> |

存储型:和反射型同理,同样用 img 标签绕过:
|
<img src=# onerror=alert(1)> |
4. Impossible 级别 —— 白名单 + HTTPOnly + CSP
这个级别就是“正确的防御示范”。采用了白名单过滤、HTTPOnly 保护 Cookie、以及 CSP 内容安全策略,基本上不存在 XSS 漏洞。学习安全不仅要知道怎么攻击,更要知道怎么防御,这个级别就是教你怎么写安全代码的。
三、知识点梳理 —— 万变不离其宗
哎,最后懒得分散写,全部整到这里吧(。打完两个靶场,是时候整理一下知识体系了,不然打完就忘,那可就白打了。
XSS 三种类型的本质区别
反射型:恶意脚本存在于 URL 参数中,服务端原样返回并执行,需要诱导用户点击链接触发,一次性触发,危害相对较小。
存储型:恶意脚本被持久化写入数据库,任何用户访问该页面均会触发,是三种类型中危害最大的。经典场景就是评论区、留言板注入。
DOM 型:纯前端漏洞,Payload 不经过服务端处理,由 JS 动态解析 DOM 时触发。Burp 抓不到服务端交互,完全是前端的事儿。
核心绕过思路总结
黑名单绕过:大小写混合(<ScRiPt>)、双写(<sc<script>ript>)、使用其他标签(<img onerror>、<svg onload>)。就是想尽办法绕过“禁止列表”,禁止了 A 就用 B,总有一款适合你。
编码绕过:HTML 实体编码(<)、Unicode 编码、Base64(配合 data: 协议)。编码的本质就是让服务端看不懂你写的是什么,但浏览器能解析。
上下文闭合:明确 Payload 注入点的上下文环境。在 HTML 标签内就闭合标签,在属性内就闭合属性值,在 JS 中就闭合字符串或 </script> 标签。找到你的位置,才能精准出拳。
伪协议:在 href、src、action 等属性中,使用 javascript: 伪协议绕过常规标签过滤。浏览器会把 javascript: 开头的内容当作代码执行,这是很多人容易忽略的一个绕过点。
还是那句话:XSS 的万变不离其宗——
寻找输入点,闭合上下文,把数据变成代码。
四、关键函数源码审计 —— 知其然更要知其所以然
PHP str_replace() 的致命缺陷
在 DVWA 的 Medium 级别中,服务端使用 str_replace('<script>', '', $input) 来过滤恶意输入。看起来没问题对吧?但这个函数有一个致命缺陷:它仅执行单次替换,不会递归。也就是说,当你输入 <sc<script>ript> 时,它只会把中间的 <script> 删掉,然后前后两段拼接起来刚好还原成完整的 <script> 标签。正确的做法应该是使用 str_ireplace() 配合循环替换,或者直接用正则表达式。
htmlspecialchars() 的单引号陷阱
在 Pikachu 中,开发者使用了 htmlspecialchars() 来转义特殊字符,但没有设置 ENT_QUOTES 标志。PHP 默认的 ENT_COMPAT 模式仅转义双引号,不转义单引号。这就意味着,如果输入点是在单引号包裹的属性值中,攻击者就可以通过单引号闭合来注入恶意代码。修复方式很简单:
|
htmlspecialchars($input, ENT_QUOTES, 'UTF-8'); |
加上 ENT_QUOTES 标志后,单引号也会被转义,这个陷阱就不存在了。写安全代码的时候,建议永远默认加上这个标志,当成一种肌肉记忆。





















467

被折叠的 条评论
为什么被折叠?



