HTML5安全

HTML 5是W3C制定的新一代HTML语言的标准。这个标准现在还在不断地修改,但是主流的浏览器厂商都已经开始逐渐支持这些新的功能。离HTML 5真正的普及还有很长一段路要走,但是由于浏览器已经开始支持部分功能,所以HTML 5的影响已经显现,可以预见到,在移动互联网领域,HTML 5会有着广阔的发展前景。HTML 5带来了新的功能,也带来了新的安全挑战。本章将介绍HTML 5的一些新功能及其可能带来的安全问题。有些功能非HTML 5标准,但也会在本章中一起进行介绍。

HTML 5新标签

新标签的XSS

HTML 5定义了很多新标签、新事件,这有可能带来新的XSS攻击。

一些XSS Filter如果建立了一个黑名单的话,则可能就不会覆盖到HTML 5新增的标签和功能,从而避免发生XSS。

笔者曾经在百度空间做过一次测试,使用的是HTML 5中新增的<video>标签,这个标签可以在网页中远程加载一段视频。与<video>标签类似的还有<audio>标签,用于远程加载一段音频。

测试如下:

 1  2  3  4  5  6  7  8  9 10 11
<video src="http://tinyvid.tv/ file/29d6g90a204i1.ogg" onloadedmetadata="alert(document.cookie);" ondurationchanged="alert(/XSS2/);" ontimeupdate="alert(/XSS1/);" tabindex="0"></ video>

成功地绕过了百度空间的XSS Filter:

百度空间的XSS

HTML 5中新增的一些标签和属性,使得XSS等Web攻击产生了新的变化,为了总结这些变化,有安全研究者建立了一个HTML5 Security Cheat-sheet项目,如下所示:

此项目对研究HTML 5安全有着重要作用。

iframe的sandbox

<iframe>标签一直以来都为人所诟病。挂马、XSS、ClickJacking等攻击中都能看到它不光彩的身影。浏览器厂商也一直在想办法限制iframe执行脚本的权限,比如跨窗口访问会有限制,以及IE中的<iframe>标签支持security属性限制脚本的执行,都在向着这一目标努力。

在HTML 5中,专门为iframe定义了一个新的属性,叫sandbox。使用sandbox这一个属性后,<iframe>标签加载的内容将被视为一个独立的“源”(源的概念请参考“同源策略”),其中的脚本将被禁止执行,表单被禁止提交,插件被禁止加载,指向其他浏览对象的链接也会被禁止。

sandbox属性可以通过参数来支持更精确的控制。有以下几个值可以选择:

1 2 3 4 5 6 7
allow-same-origin:允许同源访问; allow-top-navigation:允许访问顶层窗口; allow-forms:允许提交表单; allow-scripts:允许执行脚本。

可有的行为即便是设置了allow-scripts,也是不允许的,比如“弹出窗口”。

一个iframe的实例如下:

1 2 3 4 5 6 7
<iframe sandbox="allow-same-origin allow- forms allow-scripts" src="http://maps.example.com/ embedded.html"></iframe>

毫无疑问,iframe的sandbox属性将极大地增强应用使用iframe的安全性。

Link Types: noreferrer

在HTML 5中为<a>标签和<area>标签定义了一个新的Link Types:noreferrer。

顾名思义,标签指定了noreferrer后,浏览器在请求该标签指定的地址时将不再发送Referer。

<a href="xxx" rel="noreferrer" >test</a>

这种设计是出于保护敏感信息和隐私的考虑。因为通过Referer,可能会泄露一些敏感信息。

这个标签需要开发者手动添加到页面的标签中,对于有需求的标签可以选择使用noreferrer。

Canvas的妙用

Canvas可以说是HTML 5中最大的创新之一。不同于<img>标签只是远程加载一个图片,<canvas>标签让JavaScript可以在页面中直接操作图片对象,也可以直接操作像素,构造出图片区域。Canvas的出现极大地挑战了传统富客户端插件的地位,开发者甚至可以用Canvas在浏览器上写一个小游戏。

下面是一个简单的Canvas的用例。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
<!DOCTYPE HTML> <html> <body> <canvas id="myCanvas" width="200" height="100" style="border:1px solid #c3c3c3;"> Your browser does not support the canvas element. </canvas> <script type="text/javascript"> var c=document.getElementById("myCanvas"); var cxt=c.getContext("2d"); cxt.fillStyle="#FF0000"; cxt.fillRect(0,0,150,75); </script> </body> </html>

在支持Canvas的浏览器上,将描绘出一个图片。

在支持Canvas的浏览器上描绘的图片

在以下浏览器中,开始支持<canvas>标签。

 1  2  3  4  5  6  7  8  9 10 11 12 13
IE 7.0+ Firefox 3.0+ Safari 3.0+ Chrome 3.0+ Opera 10.0+ iPhone 1.0+ Android 1.0+

Dive Into HTML5很好地介绍了Canvas及其他HTML 5的特性。

Canvas提供的强大功能,甚至可以用来破解验证码。Shaun Friedle写了一个GreaseMonkey的脚本,通过JavaScript操作Canvas中的每个像素点,成功地自动化识别了Megaupload提供的验证码。

Megaupload验证码

其大致过程如下。

首先,将图片导入Canvas,并进行转换。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
function convert_grey(image_data){ for (var x = 0; x < image_data.width; x++){ for (var y = 0; y < image_data.height; y+ +){ var i = x*4+y*4*image_data.width; var luma = Math.floor(image_data.data[i] * 299/1000 + image_data.data[i+1] * 587/1000 + image_data.data[i+2] * 114/1000); image_data.data[i] = luma; image_data.data[i+1] = luma; image_data.data[i+2] = luma; image_data.data[i+3] = 255; } } }

分割不同字符,此处很简单,因为三个字符都使用了不同颜色。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
filter(image_data[0], 105); filter(image_data[1], 120); filter(image_data[2], 135); function filter(image_data, colour){ for (var x = 0; x < image_data.width; x++){ for (var y = 0; y < image_data.height; y+ +){ var i = x*4+y*4*image_data.width; // Turn all the pixels of the certain colour to white if (image_data.data[i] == colour) { image_data.data[i] = 255; image_data.data[i+1] = 255; image_data.data[i+2] = 255; // Everything else to black } else { image_data.data[i] = 0; image_data.data[i+1] = 0; image_data.data[i+2] = 0; } } } }

将字符从背景中分离出来,判断背景颜色即可。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19
var i = x*4+y*4*image_data.width; var above = x*4+(y-1)*4*image_data.width; var below = x*4+(y+1)*4*image_data.width; if (image_data.data[i] == 255 && image_data.data[above] == 0 && image_data.data[below] == 0) { image_data.data[i] = 0; image_data.data[i+1] = 0; image_data.data[i+2] = 0; }

再将结果重新绘制。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
cropped_canvas.getContext("2d").fillRect(0, 0, 20, 25); var edges = find_edges(image_data[i]); cropped_canvas.getContext("2d").drawImage(can vas, edges[0], edges[1], edges[2]-edges[0], edges[3]-edges[1], 0, 0, edges[2]-edges[0], edges[3]-edges[1]); image_data[i] = cropped_canvas.getContext("2d").getImageData( 0, 0, cropped_canvas.width, cropped_canvas.height);

完整的实现可以参考前文注释中提到的User-Scripts代码。

在此基础上,作者甚至能够破解一些更为复杂的验证码,比如:

破解验证码

通过Canvas自动破解验证码,最大的好处是可以在浏览器环境中实现在线破解,大大降低了攻击的门槛。HTML 5使得过去难以做到的事情,变为可能。

其他安全问题

Cross-Origin Resource Sharing

浏览器实现的同源策略(Same Origin Policy)限制了脚本的跨域请求。但互联网的发展趋势是越来越开放的,因此跨域访问的需求也变得越来越迫切。同源策略给Web开发者带来了很多困扰,他们不得不想方设法地实现一些“合法”的跨域技术,由此诞生了jsonp、iframe跨域等技巧。

W3C委员会决定制定一个新的标准来解决日益迫切的跨域访问问题。这个新的标准叙述如下。

假设从http://www.a.com/test.html发起一个跨域的XMLHttpRequest请求,请求的地址为:http://www.b.com/test.php。

 1  2  3  4  5  6  7  8  9 10 11 12 13
<script> var client = new XMLHttpRequest(); client.open("GET", "http://www.b.com/ test.php"); client.onreadystatechange = function() { } client.send(null); </script>

如果是在IE 8中,则需要使用XDomainRe-quest来实现跨域请求。

1 2 3 4 5
var request = new XDomainRequest(); request.open("GET", xdomainurl); request.send();

如果服务器www.b.com返回一个HTTPHeader: www.b.com

1
Access-Control-Allow-Origin: http://www.a.com

代码如下:

1 2 3 4 5 6 7
<?php header("Access-Control-Allow-Origin: *"); ?> Cross Domain Request Test!

那么这个来自http://www.a.com/test.html的跨域请求就会被通过。

在这个过程中,http://www.a.com/test.html发起的请求还必须带上一个Origin Header:

1
Origin: http://www.a.com

跨域请求的访问过程

在Firefox上,可以抓包分析这个过程。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
GET http://www.b.com/test.php HTTP/1.1 Host: www.b.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1b2) Gecko/20081201 Firefox/3.1b2 Paros/3.2.13 Accept: text/html,application/xhtml +xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.5 Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Proxy-Connection: keep-alive Referer: http://www.a.com/test.html Origin: http://www.a.com Cache-Control: max-age=0 HTTP/1.1 200 OK Date: Thu, 15 Jan 2009 06:28:54 GMT Server: Apache/2.0.63 (Win32) PHP/5.2.6 X-Powered-By: PHP/5.2.6 Access-Control-Allow-Origin: * Content-Length: 28 Content-Type: text/html Cross Domain Request Test!

Origin Header用于标记HTTP发起的“源”,服务器端通过识别浏览器自动带上的这个OriginHeader,来判断浏览器的请求是否来自一个合法的“源”。Origin Header可以用于防范CSRF,它不像Referer那么容易被伪造或清空。

在上面的例子中,服务器端返回:

1
Access-Control-Allow-Origin: *

从而允许客户端的跨域请求通过。在这里使用了通配符“”,这是极其危险的,它将允许来自任意域的跨域请求访问成功。这就好像Flash策略中的allow-access-from: 一样,等于没有做任何安全限制。

对于这个跨域访问的标准,还有许多HTTPHeader可以用于进行更精确的控制:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
4 Syntax 4.1 Access-Control-Allow-Origin HTTP Response Header 4.2 Access-Control-Max-Age HTTP Response Header 4.3 Access-Control-Allow-Credentials HTTP Response Header 4.4 Access-Control-Allow-Methods HTTP Response Header 4.5 Access-Control-Allow-Headers HTTP Response Header 4.6 Origin HTTP Request Header 4.7 Access-Control-Request-Method HTTP Request Header 4.8 Access-Control-Request-Headers HTTP Request Header

有兴趣的读者可以自行参阅W3C的标准。

postMessage——跨窗口传递

消息

在“跨站脚本攻击”一章中,曾经提到利用window.name来跨窗口、跨域传递信息。实际上,window这个对象几乎是不受同源策略限制的,很多脚本攻击都巧妙地利用了window对象的这一特点。

在HTML 5中,为了丰富Web开发者的能力,制定了一个新的API:postMessage。在Firefox 3、IE 8、Opera 9等浏览器中,都已经开始支持这个API。

postMessage允许每一个window(包括当前窗口、弹出窗口、iframes等)对象往其他的窗口发送文本消息,从而实现跨窗口的消息传递。这个功能是不受同源策略限制的。

John Resig在Firefox 3下写了一个示例以演示postMessage的用法。

发送窗口:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
<iframe src="http://dev.jquery.com/~john/ message/" id="iframe"></iframe> <form id="form"> <input type="text" id="msg" value="Message to send"/> <input type="submit"/> </form> <script> window.onload = function(){ var win = document.getElementById("iframe").contentWind ow; document.getElementById("form").onsubmit = function(e){ win.postMessage( document.getElementById("msg ").value ); e.preventDefault(); }; }; </script>

接收窗口:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
<b>This iframe is located on dev.jquery.com</ b> <div id="test">Send me a message!</div> <script> document.addEventListener("message", function(e){ document.getElementById("test").textContent = e.domain + " said: " + e.data; }, false); </script>

在这个例子中,发送窗口负责发送消息;而在接收窗口中,需要绑定一个message事件,监听其他窗口发来的消息。这是两个窗口之间的一个“约定”,如果没有监听这个事件,则无法接收到消息。

在使用postMessage()时,有两个安全问题需要注意。

(1)在必要时,可以在接收窗口验证 Domain,甚至验证URL,以防止来自非法页面的消息。这实际上是在代码中实现一次同源策略的验证过程。

(2)在本例中,接收的消息写入textContent,但在实际应用中,如果将消息写入innerHTML,甚至直接写入script中,则可能会导致DOMbased XSS的产生。根据“Secure By Default”原则,在接收窗口不应该信任接收到的消息,而需要对消息进行安全检查。

使用postMessage,也会使XSS Payload变得更加的灵活。Gareth Heyes曾经实现过一个JavaScript运行环境的sandbox,其原理是创建一个iframe,将JavaScript限制于其中执行。但笔者经过研究发现,利用postMessage() 给父窗口发送消息,可以突破此sandbox。类似的问题可能还会存在于其他应用中。

Web Storage

在Web Storage出现之前,Gmail的离线浏览功能是通过Google Gears实现的。但随着GoogleGears的夭折,Gmail转投Web Storage的怀抱。目前Google众多的产品线比如Gmail、GoogleDocs等所使用的离线浏览功能,都使用了WebStorage。

为什么要有Web Storage呢?过去在浏览器里能够存储信息的方法有以下几种:

1 2 3 4 5
Cookie Flash Shared Object IE UserData

其中,Cookie主要用于保存登录凭证和少量信息,其最大长度的限制决定了不可能在Cookie中存储太多信息。而Flash Shared Object和IE User-Data则是Adobe与微软自己的功能,并未成为一个通用化的标准。因此W3C委员会希望能在客户端有一个较为强大和方便的本地存储功能,这就是Web Storage。

Web Storage分为Session Storage 和 LocalStorage。Session Storage关闭浏览器就会失效,而Local Storage则会一直存在。Web Storage就像一个非关系型数据库,由Key-Value对组成,可以通过JavaScript对其进行操作。目前Firefox 3和IE 8都实现了Web Storage。使用方法如下:

1 2 3
设置一个值:window.sessionStor-age.setItem(key, value); 读取一个值:window.sessionStor-age.getItem(key);

此外,Firefox还单独实现了一个globalStor-age,它是基于SQLite实现的。

1
window.globalStorage.namedItem(domain).setItem(key, value);

下面这个例子展示了Web Storage的使用。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
<div id="sessionStorage_show"> sessionStorage Value: </div> <br> <div id="localStorage_show"> localStorage Value: </div> <input id="set" type="button" value="check" onclick="set();"> <script> function set(){ window.sessionStorage.setItem("test", "this is sessionStorage"); if (window.globalStorage){ window.globalStorage.namedItem("a.com").setIt em("test", "this is LocalStorage"); }else{ window.localStorage.setItem("test", "this is LocalStorage"); } document.getElementById("sessionStorage_show" ).innerHTML += window.sessionStorage.getItem("test"); if (window.globalStorage){ document.getElementById("localStorage_show"). innerHTML += window.globalStorage.namedItem("a.com").getIt em("test"); }else{ document.getElementById("localStorage_show").innerHTML += window.localStorage.getItem("test"); } } set(); </script>

运行结果如下:

测试页面

Web Storage也受到同源策略的约束,每个域所拥有的信息只会保存在自己的域下,如下例:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
<body> <script> if (document.domain == "www.a.com"){ window.localStorage.setItem("test",123); } alert(window.localStorage.getItem("test")); </script> </body>

运行结果如下

读取localStorage

当域变化时,结果如下:

跨域时无法读取localStorage

Web Storage让Web开发更加的灵活多变,它的强大功能也为XSS Payload大开方便之门。攻击者有可能将恶意代码保存在Web Storage中,从而实现跨页面攻击。

当Web Storage中保存有敏感信息时,也可能会成为攻击的目标,而XSS攻击可以完成这一过程。

可以预见,Web Storage会被越来越多的开发者所接受,与此同时,也将带来越来越多的安全挑战。

小结

HTML 5是互联网未来的大势所趋。虽然目前距离全面普及还有很长的路要走,但随着浏览器开始支持越来越多的HTML 5功能,攻击面也随之产生了新的变化。攻击者有可能利用HTML 5中的一些特性,来绕过一些未及时更新的防御方案。要对抗这些“新型”的攻击,就必须了解HTML 5的方方面面。

对于HTML 5来说,在移动互联网上的普及进程也许会更快,因此未来HTML 5攻防的主战场,很可能会发生在移动互联网上。

浙ICP备11005866号-12