安全

Web应用程序的安全涉及到很多方面。针对常见的漏洞和攻击,比如跨站脚本攻击,Angular提供了一些内置的保护措施。本章将讨论这些内置保护措施,但不会涉及应用级安全,比如用户认证(这个用户是谁?)和授权(这个用户能做什么?)。

要了解更多攻防信息,参见开放式Web应用程序安全项目(OWASP)

目录:

Contents:

运行在线例子 / 可下载的例子来试用本页的代码。

举报漏洞

给我们(security@angular.io)发邮件,报告Angular本身的漏洞。

要了解关于“谷歌如何处理安全问题”的更多信息,参见谷歌的安全哲学

最佳实践

防范跨站脚本(XSS)攻击

跨站脚本(XSS)允许攻击者将恶意代码注入到页面中。这些代码可以偷取用户数据 (特别是它们的登录数据),还可以冒充用户执行操作。它是Web上最常见的攻击方式之一。

为了防范XSS攻击,我们必须阻止恶意代码进入DOM。比如,如果某个攻击者能骗我们把<script>标签插入到DOM,就可以在我们的网站上运行任何代码。 除了<script>,攻击者还可以使用很多DOM元素和属性来执行代码,比如<img onerror="..."><a href="javascript:...">。 如果攻击者所控制的数据混进了DOM,就会导致安全漏洞。

Angular的“跨站脚本安全模型”

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

Angular的模板同样是可执行的:模板中的HTML、Attribute和绑定表达式(还没有绑定到值的时候)会被当做可信任的。 这意味着应用必须防止把可能被攻击者控制的值直接编入模板的源码中。永远不要根据用户的输入和原始模板动态生成模板源码! 使用离线模板编译器是防范这类“模板注入”漏洞的有效途径。

无害化处理与安全环境

无害化处理会审查不可信的值,并将它们转换成可以安全插入到DOM的形式。多数情况下,这些值并不会在处理过程中发生任何变化。 无害化处理的方式取决于所在的环境:一个在CSS里面无害的值,可能在URL里很危险。

Angular定义了四个安全环境 - HTML,样式,URL,和资源URL:

Angular会对前三项中种不可信的值进行无害化处理。但Angular无法对第四种资源URL进行无害化,因为它们可能包含任何代码。在开发模式下, 如果Angular在进行无害化处理时需要被迫改变一个值,它就会在控制台上输出一个警告。

无害化示例

下面的例子绑定了htmlSnippet的值,一次把它放进插值表达式里,另一次把它绑定到元素的innerHTML属性上。

src/app/inner-html-binding.component.html

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

插值表达式的内容总会被编码 - 其中的HTML不会被解释,所以浏览器会在元素的文本内容中显示尖括号。

如果希望这段HTML被正常解释,就必须绑定到一个HTML属性上,比如innerHTML。但是如果把一个可能被攻击者控制的值绑定到innerHTML就会导致XSS漏洞。 比如,包含在<script>标签的代码就会被执行:

src/app/inner-html-binding.component.ts (class)

export class InnerHtmlBindingComponent {
  // For example, a user/attacker-controlled value from a URL.
  htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>';
}

Angular认为这些值是不安全的,并自动进行无害化处理。它会移除<script>标签,但保留安全的内容,比如该片段中的文本内容或<b>元素。

A screenshot showing interpolated and bound HTML values

避免直接使用DOM API

浏览器内置的DOM API不会自动针对安全漏洞进行防护。比如,document(它可以通过ElementRef访问)以及其它第三方API都可能包含不安全的方法。 要避免直接与DOM交互,只要可能,就尽量使用Angular模板。

内容安全策略

内容安全策略(CSP)是用来防范XSS的纵深防御技术。 要打开CSP,请配置你的Web服务器,让它返回合适的HTTP头Content_Security_Policy。 要了解关于内容安全策略的更多信息,请参阅HTML5Rocks上的内容安全策略简介

使用离线模板编译器

离线模板编译器阻止了一整套被称为“模板注入”的漏洞,并能显著增强应用程序的性能。尽量在产品发布时使用离线模板编译器, 而不要动态生成模板(比如在代码中拼接字符串生成模板)。由于Angular会信任模板本身的代码,所以,动态生成的模板 —— 特别是包含用户数据的模板 —— 会绕过Angular自带的保护机制。 要了解如何用安全的方式动态创建表单,请参见动态表单烹饪宝典一章。

服务器端XSS保护

服务器端构造的HTML很容易受到注入攻击。当需要在服务器端生成HTML时(比如Angular应用的初始页面), 务必使用一个能够自动进行无害化处理以防范XSS漏洞的后端模板语言。不要在服务器端使用模板语言生成Angular模板, 这样会带来很高的“模板注入”风险。

信任安全值

有时候,应用程序确实需要包含可执行的代码,比如使用URL显示<iframe>,或者构造出有潜在危险的URL。 为了防止在这种情况下被自动无害化,你可以告诉Angular:我已经审查了这个值,检查了它是怎么生成的,并确信它总是安全的。 但是千万要小心!如果你信任了一个可能是恶意的值,就会在应用中引入一个安全漏洞。如果你有疑问,请找一个安全专家复查下。

注入DomSanitizer服务,然后调用下面的方法之一,你就可以把一个值标记为可信任的。

记住,一个值是否安全取决于它所在的环境,所以你要为这个值按预定的用法选择正确的环境。假设下面的模板需要把javascript.alert(...)方法绑定到URL。

src/app/bypass-security.component.html (URL)

<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>

通常,Angular会自动无害化这个URL并禁止危险的代码。为了防止这种行为,我们可以调用bypassSecurityTrustUrl把这个URL值标记为一个可信任的URL:

src/app/bypass-security.component.ts (trust-url)

constructor(private sanitizer: DomSanitizer) {
  // javascript: URLs are dangerous if attacker controlled.
  // Angular sanitizes them in data binding, but you can
  // explicitly tell Angular to trust this value:
  this.dangerousUrl = 'javascript:alert("Hi there")';
  this.trustedUrl = sanitizer.bypassSecurityTrustUrl(this.dangerousUrl);
A screenshot showing an alert box created from a trusted URL

如果需要把用户输入转换为一个可信任的值,我们可以很方便的在控制器方法中处理。下面的模板允许用户输入一个YouTube视频的ID, 然后把相应的视频加载到<iframe>中。<iframe src>是一个“资源URL”的安全环境,因为不可信的源码可能作为文件下载到本地,被毫无防备的用户执行。 所以我们要调用一个控制器方法来构造一个新的、可信任的视频URL,然后把它绑定到<iframe src>

src/app/bypass-security.component.html (iframe)

<h4>Resource URL:</h4>
<p>Showing: {{dangerousVideoUrl}}</p>
<p>Trusted:</p>
<iframe class="e2e-iframe-trusted-src" width="640" height="390" [src]="videoUrl"></iframe>
<p>Untrusted:</p>
<iframe class="e2e-iframe-untrusted-src" width="640" height="390" [src]="dangerousVideoUrl"></iframe>

src/app/bypass-security.component.ts (trust-video-url)

updateVideoUrl(id: string) {
  // Appending an ID to a YouTube URL is safe.
  // Always make sure to construct SafeValue objects as
  // close as possible to the input data so
  // that it's easier to check if the value is safe.
  this.dangerousVideoUrl = 'https://www.youtube.com/embed/' + id;
  this.videoUrl =
      this.sanitizer.bypassSecurityTrustResourceUrl(this.dangerousVideoUrl);
}

HTTP级别的漏洞

Angular内置了一些支持来防范两个常见的HTTP漏洞:跨站请求伪造(XSRF)和跨站脚本包含(XSSI)。 这两个漏洞主要在服务器端防范,但是Angular也自带了一些辅助特性,可以让客户端的集成变得更容易。

跨站请求伪造(XSRF)

在跨站请求伪造(XSRF或CSFR)中,攻击者欺骗用户,让他们访问一个假冒页面(例如evil.com), 该页面带有恶意代码,秘密的向你的应用程序服务器发送恶意请求(例如example-bank.com)。

假设用户已经在example-bank.com登录。用户打开一个邮件,点击里面的链接,在新页面中打开evil.com

evil.com页面立刻发送恶意请求到example-bank.com。这个请求可能是从用户账户转账到攻击者的账户。 与该请求一起,浏览器自动发出example-bank.com的cookie。

如果example-bank.com服务器缺乏XSRF保护,就无法辨识请求是从应用程序发来的合法请求还是从evil.com来的假请求。

为了防止这种情况,你必须确保每个用户的请求都是从你自己的应用中发出的,而不是从另一个网站发出的。 客户端和服务器必须合作来抵挡这种攻击。

常见的反XSRF技术是服务器随机生成一个用户认证令牌到cookie中。 客户端代码获取这个cookie,并用它为接下来所有的请求添加自定义请求页头。 服务器比较收到的cookie值与请求页头的值,如果它们不匹配,便拒绝请求。

这个技术之所以有效,是因为所有浏览器都实现了同源策略。只有设置cookie的网站的代码可以访问该站的cookie,并为该站的请求设置自定义页头。 这就是说,只有你的应用程序可以获取这个cookie令牌和设置自定义页头。evil.com的恶意代码不能。

Angular的http客户端在其XSRFStrategy中具有对这项技术的内置支持。 默认的CookieXSRFStrategy会被自动开启 在发送每个请求之前,CookieXSRFStrategy查询名为XSRF-TOKEN的cookie,并设置一个名为X-XSRF-TOKEN的HTTP请求头,并把该cookie的值赋给它。

服务器必须要完成自己的任务,设置初始XSRF-TOKENcookie,并确认接下来的每个请求包含了配对的XSRF-TOKENcookie和X-XSRF-TOKEN页头。

CSRF令牌对每个用户和session应该是唯一的,它包含一大串由安全的随机数字生成器生成的随机值,并且在一两天之内过期。

你的服务器可能使用不同的cookie或者页头名字。Angular应用可以通过自己的CookieXSRFStrategy值来自定义cookie和页头名字。

{ provide: XSRFStrategy, useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name') }

或者你可以实现和提供完整的自定义XSRFStrategy

{ provide: XSRFStrategy, useClass: MyXSRFStrategy }

到开放式Web应用程序安全项目(OWASP)的这里这里学习更多关于跨站请求伪造(XSRF)的知识。 这个斯坦福大学论文有详尽的细节。

参见Dave Smith在AngularConnect 2016关于XSRF的演讲

跨站脚本包含(XSSI)

跨站脚本包含,也被称为Json漏洞,它可以允许一个攻击者的网站从JSON API读取数据。这种攻击发生在老的浏览器上, 它重写原生JavaScript对象的构造函数,然后使用<script>标签包含一个API的URL。

只有在返回的JSON能像JavaScript一样可以被执行时,这种攻击才会生效。所以服务端会约定给所有JSON响应体加上前缀")]}',\n",来把它们标记为不可执行的, 以防范这种攻击。

Angular的Http库会识别这种约定,并在进一步解析之前,自动把字符串")]}',\n"从所有响应中去掉。

要学习更多这方面的知识,请参见谷歌Web安全博客文章的XSSI小节。

审计Angular应用程序

Angular应用应该遵循和常规Web应用一样的安全原则并按照这些原则进行审计。Angular中某些应该在安全评审中被审计的API( 比如bypassSecurityTrust API)都在文档中被明确标记为安全性敏感的。