可访问性和安全性


可访问性

Web 是发布信息的理想工具,而 JavaScript 程序可以增强对信息的访问,JavaScript 可访问性的一条重要原则是设计的代码即使在禁用 JavaScript 解释器的浏览器中也能正常访问。此外,还应该尽可能地支持独立于设备的事件,比如键盘或鼠标某一个设备不可用的情况下,JavaScript 事件及事件处理函数依然可用。

关于可访问性的完整讨论,可以查看这里的文档:https://www.w3.org/WAI/standards-guidelines/aria/

安全性

Web 浏览器中包含 JavaScript 解释器,也就是说,一旦载入 Web 页面,就可以让任意的 JavaScript 代码在计算机里执行。很明显,这里存在着安全隐患,浏览器厂商也在不断地权衡下面这两个方面的博弈:

  • 定义强大的客户端 API,启用强大的 Web 应用
  • 阻止恶意代码读取或修改数据、盗取隐私、诈骗或浪费时间

就像其他领域一样,JavaScript 也是在盘根错节的安全漏洞和补丁之间不断发展演化。下面就来介绍 JavaScript 的安全限制和安全问题,这些都是 Web 开发者需要意识到的。

JavaScript 不能做什么

浏览器针对恶意代码的第一条防线就是它们不支持某些功能,例如本地文件操作权限以及通用的网络能力等。

浏览器针对恶意代码的第二条防线是在自己支持的某些功能上施加限制:

  • JavaScript 程序可以打开一个新的窗口,但是为了防止广告商滥用弹出窗口,很多浏览器限制了这一功能,只有用户触发该事件的时候,才能使用;
  • JavaScript 程序可以关闭自己打开的浏览器窗口,但是不允许它不经过用户确认就关闭;
  • HTML file 元素的 value 属性是只读的
  • 脚本不能读取从不同服务器(严格来讲是不同的域名、接口或协议)载入的文档内容,除非这个就是包含该脚本的文档。这一限制叫做同源策略。

这里并没有给出所有的客户端 JavaScript 的限制项,不同浏览器有不同的安全策略,并可能实现不同的 API 限制。

同源策略

同源策略是对 JavaScript 代码能够操作哪些 Web 内容的一条完整的安全限制。当 Web 页面使用多个 <iframe> 元素或者打开其他浏览器窗口的时候,这一策略通常就会发挥作用。在这种情况下,同源策略负责管理窗口或窗体中的 JavaScript 代码以及和其他窗口或帧的交互。具体来说,脚本只能读取和所属文档来源相同的窗口和文档的属性。

文档的来源包含协议、域名、以及端口。从不同域名载入的文档具有不同的来源;通过同一主机的不同端口载入的文档具有不同的来源;使用 HTTP 协议载入的文档和使用 HTTPS 协议载入的文档也具有不同的来源,即使它们域名相同。

脚本本身的来源和同源策略并不相关,相关的是脚本所嵌入的文档的来源,理解这一点很重要。例如,假设一个来自域名 A 的脚本被包含到(使用<script>标记的src属性)宿主 B 的一个 Web 页面中,那么这个脚本的来源是域名 B,并且可以完整地访问包含它的文档的内容。如果脚本打开一个新窗口并载入来自域名 B 的另一个文档,脚本对这个文档的内容也具有完全的访问权限。但是,如果脚本打开第三个窗口并载入一个来自域名 C 的文档(或者是来自域名 A),同源策略就会发挥作用,阻止脚本访问这个文档。

实际上,同源策略并非应用于不同源的窗口中的所有对象的所有属性,不过它应用到了其中的大多数属性,尤其是对 Documen 对象的几乎所有属性而言。凡是包含另一个域名文档的窗口或窗体,都是同源策略适用的范围。如果脚本打开一个窗口,脚本也可以关闭它,但不能以任何方式査看窗口内部。同源策略还应用于使用 XMLHttpRequest 生成的 HTTP 请求。这个对象允许客户端 JavaScript 生成任意的 HTTP 请求到脚本所属文档的 Web 服务器,但是不允许脚本和其他 Web 服务器通信。

在某些情况下,同源策略有点太过严格,所以需要以下不严格的同源策略来满足需求:

  • 同源策略给那些使用多个子域名的大型站点带来一些问题,比如,来自 home.example.com 的文档里的脚本可能想要合法地读取 developer.example.com 载入文档的属性,为了支持这种多域名站点,可以使用 Document 对象的 domain 属性。默认情况下,该属性的值是载入文档的域名,我们可以通过设置这个属性值为根域名来支持多个子域名间的文档属性访问。因此,如果一个 domain 属性的初始值是 home.example.com,就可以把它设置为 example.com,如果两个窗口(或窗体)包含的脚本把 domain 设置成了相同的值,那么这两个窗口就不再受同源策略的约束,它们可以相互读取对方的属性。
  • 不严格的同源策略的第二项技术已经标准化了,那就是跨域资源共享(Cross-Origin Resource Sharing),简称 CORS。这个标准草案用新的 Origin: 请求头和新的 Access-Contro1-Allow-Origin 响应头来扩展 HTTP。它允许服务器用头信息显式地列出源,或使用通配符来匹配所有的源并允许从任何地址请求文件,这样 XMLHttpRequest 就不会被同源策略所限制了。
  • 还有一种新技术,叫做跨文档消息(Cross-Document Messaging),允许来自一个文档的脚本传递文本消息到另一个文档里的脚本,而不管脚本的来源是否不同。调用 Window 对象上的 postMessage() 方法,可以异步传递消息事件(可以用 onmessage 事件处理函数来处理它)到窗口的文档里。一个文档里的脚本还是不能调用在其他文档里的方法和读取属性,但它们可以用这种消息传递技术来实现安全的通信。

脚本化插件和 ActiveX 控件

尽管核心 JavaScript 语言和基本的客户端对象模型缺乏大多数恶意代码所需要的文件系统功能和网络功能,但是在很多 Web 浏览器中,JavaScript 也被用作很多组件的「脚本引擎」,这样的组件有 IE 中 ActiveX 控件和其他浏览器的插件,Fash 和 Java 是最常见的例子,它们为客户端提供了非常重要且强大的特性。

不过,脚本化 ActiveX 控件和插件也存在安全隐患,比如 Java Applet 有访问底层网络的能力,ActiveX 控件作为 Windows 操作系统的一部分,也有着很多历史遗留问题。

跨站脚本

跨站脚本(Cross-Site Scripting),也叫做 XSS,用来表示一类安全问题,也就是攻击者向目标 Web 站点注入 HTML 标签或者脚本。防止 XSS 攻击是服务器端 Web 开发者的一项基本工作,不过,客户端 JavaScript 程序员也必须意识到或者能够预防跨站脚本。

如果 Web 页面动态地产生文档内容,并且这些文档内容是基于用户提交的数据,却并没有通过从中移除任何嵌入的 HTML 标签或脚本来「消毒」,那么这个 Web 页面就很容易遭到跨站脚本攻击。来看一个小例子,使用 JavaScript 通过用户的名字来向用户问好:

<script>
  var name decodeURIComponent(window.location.search.substring(1)) || "";
document.write("Hello " + name);
</script>

这两行脚本使用 window.location.search 来获得它们自己的 URL 中以 ? 开始的部分,并使用 document.write() 向文档添加动态生成的内容。如果 URL 是 http://www.example.com/greet.html?david 的时候,它会显示文本「Hello david」。但考虑一下,当用下面的 URL 来调用它,会发生什么情况:

http://www.example.com/greet.html?%3cscript%3Cscript%3Ealert("David")%3C/script%3E

使用这个 URL,脚本会动态地生成另一个脚本。在这个例子中,注入的脚本只显示一个对话框,这还是相对较好的情况。

之所以叫做跨站脚本攻击,就是因为它涉及多个站点。站点 B 包含一个专门指向站点 A 的链接,它会注入一个来自站点 B 的脚本 evil.js

http://siteA/greet.html?name=%3Cscript src=siteB/evil.js%3E%3C/script%3E

这个脚本会驻留在恶意站点 B 中,但现在,它嵌入到站点 A 中,并且可以对站点 A 的内容进行任何想要的操作。它可能损坏这个页面或者使其不能正常工作,比如启动拒绝服务攻击。这可能会对站点 A 的用户带来不少坏处。更危险的是,恶意脚本可以读取站点 A 所存储的 Cookie,然后把数据发送回站点 B。注入的脚本甚至可以诱骗用户进行其他操作并将数据发送回站点 B。

通常,防止 XSS 攻击的方式是在使用任何不可信的数据创建文档内容之前,从中移除所有 HTML 标签。可以通过添加如下一行代码来移除<script>标签两边的尖括号,从而修复前面给出的 greet.html 文件:

name = name.replace(/</g,"&lt;" ).replace(/>/g,"&gt;");

上面的简单代码替换把字符串中所有的尖括号替换成它们对应的 HTML 实体,也就是说将字符串中任意 HTML 标签进行转义和过滤删除处理。IE 8定义了一个更加微妙的 tostaticHTML() 方法,可以移除 <script> 标签而不修改不可执行的 HTML。tostatichtMl() 是不标准的,但在 JavaScript 核心代码中自己实现一个 HTML 安全函数也非常简单。

HTML5 的内容安全策略则更进一步,它为 <iframe> 元素定义了一个 sandbox 属性。在实现之后,它允许显示不可信的内容,并自动禁用脚本。

拒绝服务攻击

这里的拒绝服务攻击和服务器端的拒绝服务攻击有点不同,它是面向客户端的,比如使用 alert() 对话框无限循环占用浏览器,或者用一个无限循环或没有意义的计算来占用客户端 CPU。

某些浏览器可以检测运行时间很长的脚本并让用户终止它们,但是恶意脚本可以使用 setInterval() 函数来占用 CPU,并通过分配很多内存来攻击系统,而且浏览器没有通用的办法来防止这种攻击手法。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 兼容性和互用性

>> 下一篇: 客户端框架