Web安全是网站开发中我们经常容易忽视的一个问题,但一旦出现问题将会带来很大的影响。这几天读了白帽子讲Web安全这本书的前半部分内容,目前还没有怎么接触过后端的东西,今天主要简单总结一下客户端脚本安全中的头号大敌——跨站脚本攻击。先说说浏览器的安全相关知识。
浏览器安全
浏览器安全被越来越多的人所重视。首先,浏览器是一个客户端,如果具备了安全功能,就可以像安全软件一样对用户上网起到很好的保护作用。另外,浏览器安全也是各大浏览器厂商之间竞争的一张底牌。下面介绍一些浏览器具有的的安全功能。
同源策略
同源策略是浏览器最核心最基本的安全功能。该策略限制了来自不同源的“document”或脚本,对当前“document”读取或设置某些属性。影响源的因素有:host(域名或IP地址,如果是IP地址则看做一个根域名)、子域名、端口、协议。
注意:对于当前页面来说,页面内存放JavaScript文件的域并不重要,重要的是加载JavaScript页面所在的域。
浏览器沙箱
在网页中插入一段恶意代码,利用浏览器漏洞执行任意代码的攻击方式,在黑客圈被称为“挂马”。浏览器厂商根据挂马的特点研究出了一些对抗挂马的技术。与此同时,浏览器还发展出了多进程架构,将浏览器的各个功能模块分开,各个浏览器实例分开,当一个进程崩溃时,也不会影响到其他的进程。
Sandbox,即沙箱,已经成为泛指“资源隔离类模块”的代名词。其设计目的一般是为了让不可信任的代码运行在一定的环境中,限制不可信任代码访问隔离区之外的资源。如果一定要跨越Sandbox边界产生数据交换,则只能通过指定的数据通道。
恶意网址拦截
恶意网址拦截的工作原理很简单,一般都是浏览器周期性地从服务器端获取一份最新的恶意网址拦黑名单,如果用户上网时访问的网址存在于此黑名单中,浏览器就会弹出一个警告页面。
常见的恶意网址分为两类:一类是挂马网站,这类网站通常有恶意的脚本,如JavaScript或Flash,通过利用浏览器的漏洞执行shellcode,在用户电脑中植入木马;另一类是钓鱼网站,通过模仿知名网站的相似页面来欺骗用户。
下面进入本文的正题,跨站脚本攻击(XSS).
跨站脚本攻击
跨站脚本攻击(XSS)是客户端脚本安全中的头号大敌。通常指黑客通过“HTML注入”篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。XSS主要有三种不同的类型,下面一一介绍。
反射型XSS
第一种是反射型XSS,首先来举个例子,假设一个页面把用户输入的参数直接输出到页面上:
|
|
在正常的情况下,用户向param提交的数据会展示到页面中,比如提交:
|
|
会得到如下结果:
但是如果提交一段HTML代码:
|
|
会发现alert在当前页面执行了:
再查看源代码会发现:
以上测试在IE中进行,有些浏览器对这种简单的XSS攻击做了防御
用户写入的script脚本已经被写入页面中。这就是XSS的第一种类型:反射型XSS。它只是简单地把用户输入的数据反射给浏览器。黑客往往需要诱使用户“点击”一个恶意链接,才能攻击成功。反射型XSS也叫做“非持久性XSS”。
存储型XSS
第二种XSS类型是存储型XSS,它会把用户输入的数据“存储”在服务器端。这种XSS具有很强的稳定性。
比较常见的场景是黑客写下一篇包含有恶意JavaScript代码的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的JavaScript代码。黑客把恶意的脚本保存到服务器端,所以这种XSS攻击叫做“存储型XSS”。通常也叫做“持久型XSS”。
DOM Based XSS
第三种类型是DOM Based XSS,从效果上来说也是反射型XSS。它是通过修改页面的DOM节点形成的XSS,称之为DOM Based XSS。还是举个例子:
|
|
点击“write”按钮后,会在当前页面插入一个超链接,其地址为文本框的内容:
“write”按钮的onclick事件调用了test()函数,在test()函数中修改了页面的DOM节点,通过innerHTML把一段用户数据当作HTML写入到页面中,就造成了DOM Based XSS。
然后,我们构造如下数据:
|
|
输入后,页面代码变成了:
|
|
首先第一个单引号闭合掉href的第一个单引号,然后插入一个onclick事件,最后再用注释符“//”注释掉第二个单引号。点击这个新生成的连接,脚本将被执行。
此外,还可以通过闭合a标签,并插入一个新的HTML标签,来利用XSS攻击。例如,进行如下输入:
|
|
会有什么效果可以自己下去实践一下!
XSS防御
XSS防御是很复杂的,前文也只是浅显地介绍了XSS,这里也只简单介绍几种易懂的XSS防御方式。
HttpOnly
HttpOnly最早是由微软提出,并在IE6中实现的,现在已经逐渐成为一个标准。浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie。
输入检查
常见的Web漏洞如XSS,SQL注入等,都要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。
输入检查的逻辑必须放在服务端脚本代码中实现。如果只是在客户端使用JavaScript进行输入检查,是很容易被攻击者绕过的。目前Web开发的普遍做法,是同时在客户端JavaScript中和服务器端代码中实现相同的输入检查。客户端的JavaScript的输入检查可以阻挡大部分误操作的正常用户,从而节约服务器资源。
在XSS防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、’、”等。如果发现这些字符就将这些字符过滤或者编码。比较智能的“输入检查”,可能还会匹配XSS的特征。比如查找用户数据中是否包含了“script”、“javascript”等敏感字符。这种输入检查的方式可以称为“XSS Filter”。互联网上有许多开源的“XSS Filter”实现。
输出检查
在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。
编码分为很多种,针对HTML代码的编码方式是HtmlEncode。它并非专用名词,只是一种函数实现,作用是将字符转换成HTMLEntities。在PHP中,有 htmlentities() 和 htmlspecialchars()两个函数可以满足安全要求。
防御DOM Based XSS
DOM Based XSS是一种比较特别的XSS漏洞,前文提到的几种防御方法都不太适用,需要特别对待。
先回头看看前面的例子:
|
|
在button的onclick事件中,执行了test()函数,而该函数中最关键的一句是:
|
|
它将HTML代码写入了DOM节点,最后导致了XSS的发生。DOM Based XSS是从JavaScript中输出数据到HTML页面中。而前面提到的方法都是针对“从服务器应用直接输出到HTML页面”的XSS漏洞,因此不适用于DOM Based XSS。先举个例子看看正确的解决方法是什么。
|
|
变量“$var”输出在script标签内,可是最后又被 document.write输出到HTML页面中。假设为了保护“$var”直接在script标签内产生XSS,服务器端对其进行了javascriptEscape。可是,$var在document.write时,仍然能够产生XSS。原因在于,第一次执行javascriptEscape后,只保护了:
|
|
但是当 document.write输出数据到HTML页面时,浏览器重新渲染了页面。在script标签执行时,已经对变量x进行了解码,其后 document.write再运行时,其参数就变成了:
|
|
因此产生了XSS。
正确的防御方法应该是首先在“$var”输出到script时,应该执行一次javascriptEncode;其次,在 document.write输出到HTML页面时,要分具体情况:如果是输出到事件或者脚本,则要再做一次javascriptEncode;如果是输出到HTML内容或者属性,则要做一次HtmlEncode。也就是说从JavaScript输出到HTML页面,也相当于一次XSS输出的过程,需要分语境使用不同的编码函数。
关于XSS的分享就这么多了。