xss-labs Level9 代码审计报告
目标文件:
level9.php| 漏洞类型: 反射型 XSS ——href伪协议注入(绕过http://字符串校验) | CWE: CWE-79 / CWE-80 | 危害等级: 中危
风险路径:GET /level9.php?keyword=<payload>→<a href="...">友情链接</a>
一、漏洞代码定位
level9.php 第 17-39 行:
$str = strtolower($_GET["keyword"]);
$str2 = str_replace("script", "scr_ipt", $str); // L18 ... 同 Level8 的过滤链
$str3 = str_replace("on", "o_n", $str2);
$str4 = str_replace("src", "sr_c", $str3);
$str5 = str_replace("data", "da_ta", $str4);
$str6 = str_replace("href", "hr_ef", $str5);
$str7 = str_replace('"', '"', $str6); // L23
...
if (false === strpos($str7, 'http://')) { // L32 校验:必须包含 http://
echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
} else {
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>'; // L38 危险:未校验 scheme
}
漏洞点: L38。校验逻辑仅判断字符串是否包含 http:// 子串,而不校验 URL 的实际 scheme 是否为 http,也不拦截伪协议。
二、漏洞原理
- Level9 在 Level8 基础上加了一道"必须含
http://"的校验,但用的是strpos($str7, 'http://')—— 只查"包含",不解析 URL scheme。 - 攻击者把
http://作为无害占位混进 payload 即可通过校验,真正的执行载体仍是javascript::javascript:alert(1)//http://- 过滤链对字面量
script的替换被s实体打断(同 Level8);http://出现在注释里,满足strpos校验;浏览器解码 href 后,javascript:alert(1)//http://中//http://是 JS 行注释,alert(1)照常执行。
- 已实测: 该 payload 经 Level9 过滤链后原样通过且含
http://,可进入 L38 输出。
三、利用方式与 Yakit 可重放数据包
Payload(URL 解码后):
?keyword=javascript:alert(1)//http://
Yakit 可重放原始请求:
GET /level9.php?keyword=java%26%23x73%3Bcript:alert(1)//http:// HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0
Accept: text/html
Connection: close
触发方式: 发送后页面出现"友情链接"(校验通过),点击 → 执行 alert(1) → 命中通关逻辑跳转 level10。
四、修复建议
$str = $_GET["keyword"] ?? "";
// 正确姿势:解析并校验真实 scheme,而不是判断“是否包含 http:// 字符串”
$scheme = strtolower((string)parse_url($str, PHP_URL_SCHEME));
if ($scheme !== "http" && $scheme !== "https") {
$link = "#"; // 拒绝 javascript:/data:/ftp: 等
} else {
$link = $str;
}
echo '<center><BR><a href="'.htmlspecialchars($link, ENT_QUOTES, 'UTF-8').'">友情链接</a></center>';
要点:
- 用
parse_url()解析出真实 scheme 做白名单,绝不能strpos查子串 —— 子串可以被注释、实体、占位内容混入。 - 输出编码
htmlspecialchars ENT_QUOTES。 - 纵深: CSP 收紧
script-src/navigate-to。
五、备注
- 已实测:
javascript:alert(1)//http://与javascript:alert(1)/*http://*/均通过过滤且含http://;两者均可,主推注释形式//。 - 审计启示: 用"包含关键字"做安全校验是一类高频误用(与之类似:用
strpos判断"存在"而非解析结构)。URL 校验必须解析结构(scheme/host)后再决策。
正文完