返回介绍

10.2 Web 厂商的防御

发布于 2024-01-20 15:41:03 字数 12886 浏览 0 评论 0 收藏 0

在此推荐的防御方案在具体实施的过程中,Web厂商需要仔细评估自己的业务场景,然后给出合理的安全架构方案。请牢记:业务第一,安全围绕业务合理地展开,不可或缺。

除了合理实施上面说的那些浏览器策略外,Web厂商还需要考虑如下情况。

10.2.1 域分离

域分离做得好的可以参考Google,Google将一些业务关联性小的内容转移到了不相干的域中,比如:

http://www.google.com/enterprise/mini/554_google_mini.swf

和广告有关系的内容,在访问的时候会跳转到:

http://static.googleusercontent.com/exter-nal_content/untrusted_dlcp/www.google.com/zh-CN//enterprise/mini/554_google_mini.swf

这个跳转方案是后面才加上的,这样的后续解决方案既保证了安全性,又不影响之前链接的有效性。如果googleusercontent.com域下出现了XSS,也不会影响到Google的业务主域,所以这个域下的XSS拿不到Google奖励的美金。

Google不同业务的子域都进行了上面这样安全的划分,且子域很严格,不会出现很多网站为了方便用统一的js文件设置子域的document.do-main为顶级域,这样,不同业务的子域在Web前端可以互相影响,比如,人人网、QQ很多服务的子域设计。

还有一种糟糕的子域设计是新浪微博,主内容都在顶级域下(weibo.com),大量的子域提供不同的业务,任何一个子域有XSS,都可以轻易跨到顶级域下。

同样,糟糕的设计还有,在重要的业务子域下面有一个类似proxy.html的文件,这个文件会将document.domain设置为顶级域,那么就可以很容易地被其他子域的XSS利用,而间接地跨到这个重要业务的子域上。

这些糟糕的子域设计带来的安全风险在第7章已经有很详细的介绍,在此省略。

10.2.2 安全传输

Google很多重要的业务都完美地支持HTTPS安全传输(包括搜索)。安全传输可以有效地防止局域网内的明文抓包。就这点就让人安心,虽然防御不了XSS。国内很多重要的服务认为只要登录口与账号设置页面是HTTPS就足够,这个观点是错误的,被嗅探时,这个方案泄漏不了密码,但是在非HTTPS页面上照样可以泄漏身份认证Cookie以及页面的明文信息。

还有的服务HTTPS全站似乎都做得很好,但是在WAP页面上却还是HTTP,这又是一个安全脆弱口。

10.2.3 安全的Cookie

在第2章详细介绍了Cookie安全,这方面的典型同样可以学学Google,某些身份认证相关的Cookie肯定严格设置为HTTPS传输,肯定是HttpOnly标志,这样XSS即使盗取了Cookie,也无法正确使用。

有的服务乍一看也支持HttpOnly Cookie,但是这样的Cookie却和身份认证关系不大,只能说错误地应用了HttpOnly。

10.2.4 优秀的验证码

验证码的出现肯定降低了用户体验,但是这个降低阈值是可以控制好的,比如,Google的验证码,在登录进入页面前几次是不出现的,只有登录失败几次后才会出现,且Google不同业务的验证码体系的同一套,不会像有的网站在不同业务之间出现不同风格的验证码,这样安全性很难保证一致。

还有一些重要页面,比如注册页,其中的验证码必须填写,目的就是为了防止大量非人类的注册。

Google的验证码公认是比较安全的(字母连着、扭曲变形、线条平滑、无噪等),暴力破解很困难,这也带来了用户体验上的尴尬,经常会输错验证码,说明Google非常重视安全,宁可牺牲一点用户体验,这对我们来说是可以接受的。

这样的验证码如果出现在一些功能重要的地方,是可以有效防止XSS/CSRF非法提交的,比如,对一些资料的篡改,在HTML 5中,虽然JavaScript canvas可以进行图片识别(同域内),破解验证码,但这个代价太高,真实的攻击中还未见到。

10.2.5 慎防第三方内容

第三方内容的安全性是经常被大家提起的,常见的有以下几种形式:

· <script>引用第三方js文件,比如,各种流行的统计脚本、广告脚本等。

· <iframe>引用第三方HTML文件,比如,各种广告、第三方应用等。

· <objet>等引用第三方Flash等资源,比如,各种广告、游戏等。

针对第1点,经典的案例是2009年初ring04h针对phpwind和Discuz!的劫持事件,通过对ph-pwind和Discuz!管理员后台<script>嵌入的统计脚本域名进行劫持,篡改了统计脚本的内容,导致登录管理员后台的管理员执行了恶意JavaScript代码,从而使论坛首页被篡改。

这个经典的案例对我们是一个很好的提醒,那些使用第三方js文件的网站需要好好评估一下,它们的安全性高吗?

针对第2点,曾经出现最多的案例就是挂马事件,包括新浪宠物频道,曾经就因为<iframe>嵌入的页面被挂马,直接导致访问新浪宠物频道的人中招。

对于第3点,有以下两个经典的案例。

一个是人人网于2009年曾经出现的Flash蠕虫,攻击者制作了一个Flash假视频植入人人网的个人分享中,这个Flash假视频可以执行恶意JavaScript代码,单击视频的人都会中招传播该恶意的Flash。

另一个是2008年发生在国外知名网站(MSNBC.COM)上的剪贴板劫持事件,大概过程是MSNBC.COM上有一个Flash广告,这个广告可能被攻击者控制,并植入了恶意的ActionScript代码,如下:

if (clipboard.length) 
System.setClipboard(clipboard);

当用户访问MSNBC.COM时,用户的剪贴板会自动植入下面的网址:

http://xp-vista-update.net/?id=xx

其中,id会根据值的不同,重定向到不同的网页,几乎都是做杀毒软件虚假推广的。后来Flash因为好几起这类事件也进行了更新,不允许自动设置剪贴板的内容,而需要用户交互(比如用户单击)才行。

由此可见,第三方内容带来的危害可以很大,Web厂商们需谨慎。

10.2.6 XSS防御方案

我们认为熟悉了“攻”,“防”才会更加顺手,否则对于只关注防的人来说,是很希望有一种手册般完美的防御策略的,这太理想化了。下面的防御方案中,我们会主要引入OWASP的XSS防御方案,原因如下:

· OWASP出品的质量是业界公认的,XSS防御方案虽然不够完美,但已经不错了。

· OWASP给出的解决方案不仅仅是文字,还有ESAPI(企业级安全API),几乎流行的语言都可以离线调用对应的接口进行相应的防御,这省去了我们自己写相关的过滤函数与防御逻辑。

下面介绍一些防御策略。首先假定一切输入都是有害的,那么在服务端的对策一般如下。

· 输入校验:长度限制、值类型是否正确、是否包含特殊字符(如<>'"等)。其实校验是对数据无害的,满足就放行,不满足就阻止。这个过程一般不建议进行任何过滤操作,这样能保证数据的原生态

· 输出编码:根据输出的位置进行相应的编码,如HTML编码、JavaScript编码、URL编码。原则就是:该数据不要超出自己所在的区域,也不要被当做指令执行。

下面列举OWASP XSS防御的一些规则,其中融入了我们的观点。

规则一:进入HTML标签之间时

比如,在<div></div>之间,HTML编码转换规则如下:

& -->&
< --><
> -->>
" -->"
' -->'   '不被建议了
/ -->/   包含斜杆是因为它是HTML结束标签里的符号

使用ESAPI接口就非常简单了,一个函数调用(以下都以Java为例,其他语言类似)如下:

String safe 
  = ESAPI.encoder().encodeForHTML( request.getParameter( "input" ) );

规则二:进入HTML普通属性值时

普通属性如value、width、height等,而href、src、action、style、on*事件等除外(参考规则三)。普通属性的样例如下:

<div attr=...编码不可信的数据...>content</div>无引号包围
<div attr='...编码不可信的数据...'>content</div>单引号包围着
<div attr="...编码不可信的数据...">content</div>双引号包围着

如果是单引号或双引号引起来的,那就简单了,只要编码单引号或双引号为&#xHH;的形式即可,否则要编码的就多了:空格(包含ASCII十进制数的9、10、11、12、13、32)<>等。所以强烈建议用引号引起来。

使用ESAPI接口如下:

String safe 
  = ESAPI.encoder().encodeForHTMLAttribute( request.getParameter( "input" ) );

规则三:进入JavaScript中时

其实这个有些复杂,修补的本质参考6.2节的内容,在此不再赘述,不过有一点要注意,如果是进入<script></script>里,小心输入的</script>闭合之前的<script>,无论这个</script>在JavaScript的什么位置。

使用ESAPI接口如下(不完备,大家多测试):

String safe 
  = ESAPI.encoder().encodeForJavaScript( request.getParameter( "input" ) );

规则四:进入CSS中时

CSS非常松散,如果是定义具体某CSS属性的值,比如width的值,避免出现'"、;、}、{、(、)等特殊字符。

如果允许用户完整自定义CSS,则需要过滤掉javascript伪协议、expression及各种变形、@im-port及各种变形等,这些变形在<style>里或单独的CSS文件里往往是嵌入了\字符以及字母替换为\HH(十六进制),如果是在HTML标签style属性里,往往是字母替换为&#DD(十进制)或&#xHH;(十六进制)形式。expression在IE 6下的任意字符替换为对应的全角也有效。

使用ESAPI接口如下:

String safe 
  = ESAPI.encoder().encodeForCSS( request.getParameter( "input" ) );

规则五:进入URL时

一般就是对特殊字符采用%HH(十六进制)形式的编码,使用ESAPI接口如下:

String safe 
  = ESAPI.encoder().encodeForURL( request.getParameter( "input" ) );

需要特别注意的是,如果输出的值是href/src等属性的完整值或前部分值,需要注意javascript:/data:等这类伪协议,一般采用白名单前缀协议进行限制会比较好,比如,只允许http://或https://打头的URL。

最后,这些防御方案的实施需要依据自己的场景进行,有的场景是不允许出现任何富文本的,此时直接在输出时进行HTML编码即可,而有的场景是允许部分富文本的(比如邮件系统、Blog系统),此时就不能简单地进行HTML编码了事,而是过滤掉那些危险的标签、事件等,但是我们通过前面的内容知道,这样过滤不会那么简单,HTML很松散,字符集编码有的时候是一个大问题,浏览器可能还有解析BUG,总是会出现相关的绕过。还会有更复杂的场景,我们只要清楚地知道用户输入到最终的输出整个过程的每个环节,保证在最终输出是安全的,那么就可防御了。

10.2.7 CSRF防御方案

CSRF的防御其实非常简单,为什么现在还有很多网站存在CSRF漏洞呢?因为这种攻击相对来说很少被重视。另外,代码参差不齐,总存在防御缺口的地方。针对CSRF攻击的防御,目前常用的有以下几种策略。

1. 检查HTTP Referer字段是否同域

一般情况下,用户提交站内请求,Referer中的来源应该是站内地址。如果发现Referer中的地址异常,就可以怀疑遭到了CSRF的攻击。这样,检查Referer字段是一个不错的方法,在浏览器客户端层面上,使用JavaScript和ActionScript已经无法修改HTTP Referer(除非0day)了。

这一招既简洁,效果又非常好,不过对来自站内的CSRF攻击就无能为力了。

2. 限制Session Cookie的生命周期

比如,用户登录网上银行,如果闲置10分钟,则自动销毁Cookie。这样的设置也可以在一定程度上减少被CSRF攻击的概率。

3. 使用验证码

虽然验证码的方式在一般情况下会降低用户体验的感受。但特定情况下,使用验证码方式是阻断CSRF攻击的有效手段。比如,网银中的付款交易需要用到验证码才能通过。

4. 使用一次性token

这是当前CSRF攻击中最流行的解决方案,to-ken是一段字母数字随机值,长度为10~40个字符,每次生成的值都是唯一的,所以往往会加上这几个因子:时间搓、用户ID、随机串等。

token经常出现在表单项中,比如,一个name是csrftoken的隐藏表单项,会随着表单提交而提交,然后服务端会校验该token值的合法性。校验的原理就是:在服务端生成这个唯一的token时,会保留下来,然后判断用户提交的表单是否有这个唯一的token。

这样,攻击者要得到这个token就比较难,CSRF攻击也就无法成功。这样的解决方案比验证码的用户体验好很多,但是仍需要如下几种方式才能得到这样的token:

· 这个token唯一性不够好,能够被猜测到,如仅根据时间搓+用户ID md5后的结果,这样的token就可能被预测到。

· 通过Flash crossdomain.xml跨域读取方式导致token泄漏,这个方式在前面已介绍过。

· 如果这个token还出现在GET请求中,那么很可能会因为Referer而泄漏。

除了上面这几种常规的方式,xeye的monyer还提了一种防御思路,在此整理出来供大家参考。

我们知道,一般防御CSRF有三种方法:判断Referer、验证码、token。

对于判断Referer来说,虽然客户端带用户状态的跨域提交,JavaScript和ActionScript已经无法伪造Referer了,但对于客户端软件和Flash的提交,一般是不带Referer的,那么就需要为此开绿灯,但这样使得外站的Flash请求伪造无法被防御。

而验证码的弊端很明显:会对用户造成影响。

token存在的问题是:时效性无法保证;大型服务时,需要一台token生成及校验服务器;需要更改所有表单添加的字段。

最近我们在做此类防御时,想出了另外一种方法,与xeye、woyigui等人在群里讨论后认为是可行的。其实原理非常简单,与token也差不多:当表单提交时,用JavaScript在本域添加一个临时的Cookie字段,并将过期时间设为1秒之后再提交,服务端校验有这个字段即放行,没有则认为是CSRF攻击。

token防CSRF的原理是:无法通过AJAX等方式获得外域页面中的token值,XMLHttpRe-quest需要遵守浏览器同源策略;而临时Cookie的原理是:Cookie只能在父域和子域之间设置,也遵守同源策略。

下面看一个简单的demo:

http://127.0.0.1/test.html:

<script>
function doit(){
  var expires = new Date((new ate()).getTime()+1000);
  document.cookie = "xeye=xeye; expires=" + expires.toGMTString();
}
</script>
<form action="http://127.0.0.1/test.php" name="f" id="f" onsubmit="doit();" target="if1">
<input type="button" value="normal submit" onclick="f.submit();">
<input type="button" value="with token" onclick="doit();f.submit();">
<input type="submit" value="hook submit">
</form>
<iframe src="about:blank" name="if1" id="if1"></iframe>

http://127.0.0.1/test.php

<?php
  echo "<div>Cookies</div>";
  var_dump($_COOKIE);
?>

test.html为浏览器端的表单,其中有三个按钮:第一个是正常的表单提交;第二个是添加临时Cookie后提交表单;第三个是以hook submit事件来添加临时Cookie并提交。

正常的表单提交不会出现临时Cookie字段,第二个和第三个按钮提交则会出现。大家可以反复单击按钮来查看结果,但需要注意时间间隔需超过1 秒。(当然可以将test.html拿到外域看看,不过要注意form的target不能指向iframe了,可以以新窗口打开。由于同源策略,Cookie肯定是带不过去的。)

不过这种方式只适用于单域名站点,或者安全需求不需要“当子域发生XSS隔离父域”。因为子域是可以操作父域的Cookie的,所以它的缺陷也比较明显:这种方法无法防御由于其他子域产生的XSS所进行的表单伪造提交。而一个区分子域的自校验token是可以防止从其他子域到本域的提交的。但如果对于单域而言,这种方法应该是足够的,并且安全性可能会略大于token。

这种方式没有什么大问题,不过确实有一些小的疑问,比如:

· 网络不流畅,有延迟会不会导致Cookie失效。这个显然是不会的,因为服务端Cookie是在提交请求的header中获得的。延时在服务端,不在客户端,而1秒钟足可以完成整个POST表单的过程。

· Cookie的生成依赖于JavaScript,相当于这个token是明文的?这是肯定的,不管采取多少种加密,只要在客户端,就会被破解,不过不管怎样,CSRF无法在有用户状态的情况下添加这个临时Cookie字段。虽然服务端curl等可以,但是无法将当前用户的状态也带过去。

· 外站是否可以伪造这个临时Cookie呢?目前看来至少通过ActionScript和JavaScript无法向其他域添加和更改Cookie,通过服务端虽然可以伪造Cookie,但不能获取到目标域的用户状态。

· 如果由于某种网络问题无法获取Cookie呢?那么用户状态也不能获取,用户只能再提交一次才可以。

说实话,我们无法确定这种新方法究竟是否真正有效,也许存在被绕过的可能性。如果真有效,那么这大概是一种最简单的、对代码改动最小,且对服务器压力也最小的防御CSRF的方法。

10.2.8 界面操作劫持防御

在第5章介绍过界面操作劫持,它的防御方法更简单。基于界面操作劫持的攻击模式是用巧妙的视觉欺骗的方式,对Web会话进行劫持。防御这种攻击的技术思路是:使有重要会话的交互页面不允许被iframe嵌入,或者只允许被同域iframe嵌入。

目前针对界面操作劫持的防御有以下几种。

1. X-Frame-Options防御

X-Frame-Options是由微软提出来的防御界面操作劫持的一种方法,Web开发人员可以在HTTP响应头中加入一个X-Frame-Options头,浏览器会根据X-Frame-Options字段中的参数来判断页面是否可以被iframe嵌入,如图10-1所示。

图10-1 X-Frame-Options头

X-Frame-Options有两个参数:DENY和SAMEORIGIN。如果是参数DENY,下面这个Frame_deny.php页面就不能被嵌入到<iframe>中。

<?php
  header ("X-Frame-Options: DENY");
?>
<b>Deny_test</b><br>

如果是参数SAMEORIGIN,那么下面这个Frame_same.php页面只能被嵌入在和其同源的页面中。

<?php
  header ("X-Frame-Options: SAMEORIGIN");
?>
<b>Same_test</b><br>

目前支持X-Frame-Options的浏览器如表10-2所示。

表10-2 支持X-Frame-Options的浏览器

浏览器

支持版本

IE

8.0以上

Opera

10.0以上

Safari

4.0以上

Chrome

5.0以上

Firefox

3.6.9以上

以上面的Deny程序为例,设置Frame_test.php 内容如下:

<b>X-Frame-Options_test</b><br>
<iframe src=frame_deny.php width=500height=300></iframe>

比如,IE 9下的防御提示如图10-2所示。

图10-2 IE 9浏览器防御界面操作劫持

2. Frame Busting脚本防御

防御操作劫持的另一种方法是使用JavaScript脚本来对页面进行控制,达到页面无法被iframe嵌入的目的,这样的防护脚本被称为Frame Busting脚本。下面给出一段Frame Busting脚本示例。

<script>
if (top.location != self.location)
{
  top.location=self.location;
}
</script>

代码中的top指代主体窗口对象,self指代当前窗口对象。如果判断出页面的主体窗口地址与当前窗口地址不同,就将主体窗口地址设置为当前窗口地址。这样就避免了用户的操作实际发生窗口与所见窗口不一致的情况,从而防护了利用透明层进行操作劫持的攻击方法。

但是,以上这段Frame Busting代码也存在一些问题:在IE浏览器下,如果我们在主体窗口中加入<script> var location="" </script>这段代码,那么这段Frame Busting代码就会被突破。

X-Frame-Options和Frame Busting方法都可以做到对界面操作劫持的防御。相对而言,X-Frame-Options的方式还是比Frame Busting更安全。X-Frame-Options是在浏览器中嵌入的,而Frame Busting是脚本控制。这就意味着JavaScript代码始终有被突破的可能性。

这种突破Frame Busting的方法称为BustingFrame Busting。Collin Jackson在2010年的Bust-ing Frame Busting:a Study of Clickjacking Vulner-abilities on Popular Sites报告中,对Frame Bust-ing脆弱性做了深入研究,讨论了10类FrameBusting被突破的情况,也列举了包括Yahoo、Facebook等知名网站Frame Busting被突破的真实案例。

所以在X-Frame-Options机制没有普及的情况下(例如,使用IE 6版本的浏览器的用户还有一些),我们大部分时间还是要依赖于使用JavaScript脚本来抵御界面操作劫持的,所以严格编写合格的Frame Busting代码,才能使防护更加安全。下面给出一段目前防御性最高的Frame Busting代码,这段代码由Collin Jackson的报告中给出:

<style>
html {display:none;}
</style>
<script>
if(self==top){
  document.documentElement.style.display='block';
}
else{
  top.location=self.location;
}
</script>

3. 使用token进行防御

在业界主流的防御界面操作劫持攻击的方法中,似乎并没有提及防御CSRF中的token也可以对其进行防御。

比如,这样一个登录页面:http://url/login?idtoken=90dfOlk,如果攻击者无法猜测到idtoken后面的数值,那么这个登录页面就不能被iframe嵌入,进而也无法对这个页面实施界面操作劫持攻击。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文