前端框架中XSS的代码表现形式

随着前端技术的发展,越来越多的框架默认对XSS做了有效的防范,遵循secure by default原则。

如果不了解前端框架本身的安全特性,就会导致代码审计过程中对XSS的审计不严密。

本文是从代码审计的视角去看不同前端框架中可能导致XSS的代码写法。

参考:https://sqreen.github.io/VueXSSDemo/#/

Vue

Vue使用双大括号将参数显示在浏览器中,双大括号默认将其中的数据解析为普通文本,而非 HTML 代码,这样避免了XSS的产生。

其他输出指令也同样会将数据解析为普通文本,比如v-model

当然实际业务中也存在输出HTML代码的需求,若想输出真正的 HTML,就需要使用 v-html 指令

1
2
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

若此时rawHtml为<span style="color: red">This should be malicious.</span>,实际效果如下图:

vue-xss

v-html的内容会被浏览器当作HTML字符解析,会导致XSS漏洞产生。

代码审计关键:注意v-htmlv-html输出的值若用户可控,就可能存在XSS。

来源:https://cn.vuejs.org/v2/guide/syntax.html#原始-HTML

Angular

Angular同样默认把所有值都当做不可信任的。 当值从模板中以属性(Property)、DOM 元素属性(Attribte)、CSS 类绑定或插值等途径插入到 DOM 中的时候, Angular 将对这些值进行无害化处理(Sanitize),对不可信的值进行编码。

在低版本(<1.6)的Angular中,存在模版注入的漏洞,用户直接传入49会将7*7插入到Angular的解析之中导致代码执行。

但前端框架更新速度是非常快的,截止到写文章,目前最新的Angular版本为v9.1.9,及时更新可以一定程度避免框架本身导致的漏洞。

回到前端开发利用Angular框架回显参数时触发XSS的问题上来。

和Vue一样,Angular也使用双大括号将数据显示在浏览器中,双大括号默认将其中的数据解析为普通文本

不同的是Angular使用[innerHTML]来表示其中的内容会被浏览器正常解析,不过Angular会识别其中的值,并将其进行无害化处理。

比如存在以下场景,一处使用显示htmlSnippet,另一处使用[innerHTML]="htmlSnippet"显示htmlSnippet

1
2
3
4
5
<h3>Binding innerHTML</h3>
<p>Bound value:</p>
<p class="e2e-inner-html-interpolated">{{htmlSnippet}}</p>
<p>Result of binding to innerHTML:</p>
<p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>

当htmlSnippet的内容如下时

1
htmlSnippet = 'Template <script>alert("seikei")</script> <b>Syntax</b>';

得到的结果如下,最后一行<script>标签不见了,不过它不是被浏览器解析了,而是被Angular自动进行了无害化处理,移除了<script>标签,保留安全的内容,比如片段中的<b>标签。

angular-xss-sanitize

那万一Angular无害化处理器把业务所需的内容过滤了怎么办呢?

为了防止这种情况,Angular可以使用以下方法把值标记为可信任的,这样无害化处理器就不会再对该值进行过滤。

  • bypassSecurityTrustHtml
  • bypassSecurityTrustScript
  • bypassSecurityTrustStyle
  • bypassSecurityTrustUrl
  • bypassSecurityTrustResourceUrl

如下场景

1
2
3
4
<h4>An untrusted URL:</h4>
<p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me</a></p>
<h4>A trusted URL:</h4>
<p><a class="e2e-trusted-url" [href]="trustedUrl">Click me</a></p>

dangerousUrl为恶意代码,trustedUrl是经过信任的代码,可以不经过无害化处理器直接解析。

1
2
dangerousUrl = 'javascript:alert("Hi seikei")';
trustedUrl = sanitizer.bypassSecurityTrustUrl(dangerousUrl);

结果是trusted URL会导致XSS产生,而untrusted不会。

angular-xss

代码审计关键:注意[innerHTML][href][src]等,再关注其传入的值是否经过bypassSecurityTrust标记为信任,若值是信任的且用户可控,就可能存在XSS漏洞。

来源:https://angular.cn/guide/security#xss

React

React使用dangerouslySetInnerHTML作为HTML输入的方法,并且需要向其传递包含 key 为 __html 的对象。只有使用dangerouslySetInnerHTML会存在XSS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';

class SetHtml extends React.Component {
constructor(){
super();
this.state = {
content:'React XSS Demo <script>alert("seikei")</script>'
}
}
render() {
return (
<div>
<div>
{this.state.content}
</div>

<div dangerouslySetInnerHTML={{__html: this.state.content}} />

</div>
)
ß}
}

export default SetHtml;

代码审计关键:关注dangerouslySetInnerHTML,若用户输入在dangerouslySetInnerHTML中,可能导致XSS。

来源:https://zh-hans.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml

Thymeleaf

其实thymeleaf算不上前端框架,但是也能控制输入到前端页面的内容。在这里简单记录一下。

thymeleaf作为模版引擎,可以处理HTML,XML,JavaScript,CSS。thymeleaf有自己的XSS转义方法,thymeleaf模版在对th:text标签进行渲染的时候,默认对特殊字符进行了转义,所以我们输入的XSS payload是在输出时被转义的。

th:utext不会将字符转义,这就会导致XSS产生。

代码审计关键:关注th:utextth:utext中的内容用户可控则存在XSS。