加载中…
个人资料
HorseLuke
HorseLuke
  • 博客等级:
  • 博客积分:0
  • 博客访问:112,815
  • 关注人气:33
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

小议OAuth 2.0的state参数——从开发角度也说《互联网最大规模帐号劫持漏洞即将引爆》

(2012-11-10 01:43:45)
标签:

it

oauth

安全

oauth2

分类: 电脑病毒和其它等IT文章
有一个外国研究员在最近研究OAuth 2.0的登录过程中发现了许多程序员常犯的错误(见翻译文:http://www.freebuf.com/articles/web/5997.html ;网友找出的该翻译原文出处:http://homakov.blogspot.jp/2012/07/saferweb-most-common-oauth2.html ),知道创宇测试国内各网站后发现确为如此,并在今天发表了《互联网最大规模帐号劫持漏洞即将引爆》(http://tech.ccidnet.com/art/32963/20121109/4448657_1.html )。但这篇文章本身的标题实在过于劲爆,同时微博出现了各种误传,结果成了一场个人看来并不必要的恐慌,以及口水战。这篇文章,主要从开发者的角度,来解释这个漏洞的来龙去脉,并以问答的形式给出一些个人建议和其他看法。

通俗的说一下这个漏洞的表现(虽然不全准确):你有一间房子,证明方法是你有一张房契,上面说“你和房子存在有效关系”。某一天,有个中介叫你签续租合同(带复写纸一式三份那种);你签了,结果被告知房子成了别人了。后经调查,续租合同的第二联,其实是一份转让合同。就这样,房契的关系就变了。

你也许懊恼,为什么没有好好检查这一式三份的合同是否一致。同样,在这个漏洞中,开发者就会懊恼,为什么没有好好使用并检查state参数?

但是在说明这个state参数前,有必要了解大部分程序员所写的绑定OAuth账号流程,由于绑定流程很多,这里挑最常见的“用户在第三方网站A上登录后,通过Authorization code方式绑定微博”流程(也是这个漏洞常见的场景流程):
(1)用户甲到第三方网站A登录后,到了绑定页面。此时还没绑定微博。
(2)绑定页面提供一个按钮:“绑定微博”(地址a:http://aaa.com/index.php?m=user_3rd_bind_sina)
(3)用户甲点击地址a,程序生成如下地址b(为方便大家查看,参数部分均未urlencode以【】包含显示):
https://api.weibo.com/oauth2/authorize?client_id=【9999999】&redirect_uri=【http://aaa.comindex.php?m=user_3rd_bind_sina_callback】&response_type=【code】
(4)用户甲浏览器定向到地址b,授权该应用。
(5)授权服务器根据传递的redirect_uri参数,组合认证参数code生成地址c:
http://aaa.comindex.php?m=user_3rd_bind_sina_callback&code=【809ui0asduve】
(6)用户甲浏览器返回到地址c,完成绑定。

咋看起来,好像没啥问题,毕竟code也是不可预测嘛。但是各开发者有没有想过,地址c实质上是和当前登录用户一点关系都没有的,因为地址c只能证明微博用户信息,但无法证明网站A的用户信息。所以漏洞就此产生了——假设有用户乙和丙,同时发起绑定请求,登录到不同的微博账号,然后在第5步后打住,互相交换地址c,会是什么结果?答案就是用户乙绑定了用户丙的微博,用户丙绑定了用户乙的微博了…...攻击者的目标,其实就是要获取地址c,然后诱骗已登录网站A的受害者点击,从而改变了绑定关系。

为应对这种情况,Oauth 2.0引入了state参数。state参数是什么?看看没多长时间前最终定稿的rfc6749(The OAuth 2.0 Authorization Framework, http://tools.ietf.org/html/rfc6749 ):

state: RECOMMENDED. An opaque value used by the client to maintain state between the request and callback.  The authorization server includes this value when redirecting the user-agent back to the client.  The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.
(推荐。一个由client使用的不透明参数,用于请求阶段和回调阶段之间的状态保持。此参数将在授权服务器重定向用户代理(如浏览器,译者注)回client时包含。该参数理应使用,以防止章节10.12中描述的CSRF。)(PS:不知咋翻译opaque value,只好翻译为“不透明参数”)

这个参数在许多开放平台上也会有提及,比如新浪微博的Oauth2/authorize(http://open.weibo.com/wiki/Oauth2/authorize ):
用于保持请求和回调的状态,在回调时,会在Query Parameter中回传该参数。开发者可以用这个参数验证请求有效性,也可以记录用户请求授权页前的位置。这个参数可用于防止跨站请求伪造(CSRF)攻击。

然而大多数开发者(包括许多官方SDK),会忽略使用这个state参数。所以,大面积的网站(不乏大站)确实存在这种漏洞,这也是知道创宇认为“互联网最大规模帐号劫持即将引爆”的根源。但是在我看来,有点危言耸听,因为利用条件实属有点苛刻
(1)攻击者必须了解第三方网站可能的绑定特性,否则攻击极可能失败。绝大多数网站都是一个网站账号只能绑定一个OAuth提供方账号(比如微博帐号)。这种特性,导致这个漏洞在绝大多数网站根本无法快速撒网,只能定向劫持未绑定的用户到攻击者OAuth账号上,而攻击一次后,这个账号必须解绑才能用于别的攻击,导致利用难度增大不少。
(2)攻击者还必须了解被定向劫持用户的上网习惯。在这个漏洞中,绝大部分都需要受害者在第三方网站上处于登录状态,否则攻击基本失效。
(3)攻击者还必须了解第三方网站、以及用户在该第三方网站上存在的利益。现在的攻击有许多都是带有利益的,如果是电商类的话,网站本身的金钱利益驱动可能存在,但又需要判断该用户在该网站是否存在高价值,这就增加了额外的工作量;如果只是娱乐类网站,除了言论相关和用户所拥有的网站管理权,我还想不出有什么可以吸引攻击者去定向攻击。

以上各种条件造就了在攻击实施环节更像是那种一对一的淘宝或者QQ钓鱼手段、或者放入针对高价值目标的社工(或APT)一环中。就前者而言,淘宝、QQ甚至各类热门游戏的钓鱼量之大,安全研究者们应该更清楚;就后者而言,实质还有其它更有效地手段。那么,这个漏洞,还能冠以“最大规模帐号劫持”吗?我相信,地下产业者看到后只会轻蔑的笑一下,然后继续埋头干活……


对于开发者而言,要修复这个漏洞,就是必须加入state参数,这个参数既不可预测,又必须可以充分证明client和当前第三方网站的登录认证状态存在关联(如果存在过期时间更好)。见rfc6749 章节10.12:

The binding value used for CSRF protection MUST contain a non-guessable value (as described in Section 10.10), and the user-agent's authenticated state (e.g., session cookie, HTML5 local storage) MUST be kept in a location accessible only to the client and the user-agent (i.e., protected by same-origin policy).
(这个用于防御CSRF的绑定参数(即state参数,译者注)必须包含一个不可预测的数值(如同章节10.10的描述),还有用户代理(如浏览器,译者注)的认证状态必须保存在只允许client和用户代理两者访问的地方(也就是受到同源策略保护))

这听起来好复杂,其实,随机算一个字符串,然后保存在session,回调时检查state参数和session里面的值,就满足要求了。php示例如下:

构造Oauth2/authorize阶段:
$_SESSION['REQ_STATE'] = md5(uniqid(mt_rand(1, 100000), true));  //生成state参数,并存放于session
//$_SESSION['REQ_STATE'] = $_USER['uid']. '_'. $_USER['reg_ip']. '_'. $state;   //(如果还是怕随机不够,算法多考虑一些独特且不可预知的用户信息吧,reg_ip是个不错的选择)

callback回调检查:
$_state_check = !empty($_SESSION['REQ_STATE']) && ($_SESSION['REQ_STATE'] == $_GET['state']) ? true : false;   //callback回调检查



以下是问答环节:

一、普通用户问答环节

问:有微博提出,针对这个漏洞,解决方法是“微博用户立刻检查“我的应用”,暂取消所有应用授权,再根据需要重新授权”,是否正确?
答:就这个漏洞而言,完全错误!该漏洞的结果是第三方网站帐号被关联到攻击者的微博账号上,而不是自己的微博帐号被关联到攻击者的第三方网站账号。在“我的应用”中解除所有应用并不会对攻击者产生任何影响。还记得房子的例子吧?可以类比为:即使自己自杀,也不能导致房子和攻击者解除房契关系。
正确方法请看知道创宇的方案:“定期查看重要网站(比如你经常看优酷的话就检查优酷)的第三方帐号绑定页面,检查是否有陌生的其他帐号绑定到自身账户,如果发现应立即取消绑定或授权。”


问:如果有一个网站的帐号可绑定多个OAuth帐号(或反过来),我是否很容易中招?(即攻击难度是否降低成可以广撒网?)
答:明显是的。然而,从实际接触的业务来看,这种奇葩应用有理由相信存在量不会多,而且做不好绝对不只有这个漏洞,各平台也基本不允许这类应用存在。
真遇到了这类应用怎么办?如果你不是搞营销的,还是别碰这类应用稳妥,赶紧双向取消绑定吧(奇葩应用取消所有绑定,微博“我的应用”也取消)。


问:如何判断自己是否为高价值用户,容易遭受攻击?
答:简单判断方法就是,你在微博上经常晒自己购物的网站来源,还说自己中了优惠券,就有可能了;又或者你成大嘴了,就容易被盯上社工。但是实际上,偷账号的手段更多,这个漏洞对普通用户意义实在不大,该干啥就干啥吧!


二、开发者问答环节

问:这个漏洞究竟严不严重?
答:“安全无小事”。作为合格的开发者,如果你是对用户负责,又是高价值网站,那是确实挺高的。而综合目前的情况,我个人综合评价是低。原因已经在上面讲了。
而且个人觉得开发者过于关注这个漏洞,其实并不见得是好事,因为这个漏洞实质上暴露的是开发者们对OAuth协议(或者说各种第三方接入协议)的各种流程和参数了解得不足——当然这也有平台甚至协议本身的责任在,看看各wiki和sdk就知道了。历史上相关案例也不少,甚至更严重,比如《淘网址sina oauth认证登录漏洞》:http://www.wooyun.org/bugs/wooyun-2012-011104 
基于以上原因,个人非常建议开发者对整个OAuth流程部分(包括登录、绑定、解绑等)都通盘检查一次;阅读各种wiki(如果可以读RFC,那就更好了),对OAuth的相关参数要有相当清晰的了解并合理地运用——但这个过程确实很长,我自己也没完全仔细看各种wiki和rfc,囧。平台的话,加强引导(尤其是SDK)还是很重要的,这点绝对要赞淘宝的@放翁_文初 (http://weibo.com/fangweng )。


问:如果设计为“在绑定状态下不允许更换,需要先解绑再绑定”,那是否意味着没有问题?
答:难说,假设攻击者可以CSRF解绑;或者你的绑定回调callback代码实际上没有检查绑定状态,一样可以遭受这个攻击。如果可以确定不存在上述问题,那还好。


问:为什么OAuth 1.0没有这个问题?
答:问这个问题的都是第三方应用的开发老手啊!想必也是和我一样被一路折磨而来,还不知道哪天到头啊!表示泪流满面!(群众:喂喂喂你干嘛这么激动,扔鸡蛋 -_-#)
其实不是没这个问题,而是OAuth 1.0时代,大多数开发者都会将REQUEST TOKEN写到session(或者和当前用户有关的地方中),攻击者获取到的REQUEST TOKEN根本无法跑到受害者的session中,并且ACCESS TOKEN的时候又必须要用REQUEST TOKEN来换,这样一来,无意间完成了client的用户登录状态维持和校验,这就救了大家一命啊!OAuth 2.0时代,没了三次握手,大多数开发者也没有留意到用户状态校验这点,所以就出事了。还是强调那句,state参数除了随机,还必须可以充分证明和当前网站的登录认证状态存在关联。


问:还是不明白state参数所说的“必须可以充分证明client和当前网站的登录认证状态存在关联”,不是随机就成了么?
答:只要state参数不能校验第三方网站的登陆认证状态,这个漏洞就生效。比如:

构造Oauth2/authorize阶段:
$state = md5(uniqid(mt_rand(1, 100000), true));  //生成state参数
set_cache($state, 1, 1800);  //$state作为cachekey存入缓存。

callback回调检查:
If(!empty($_GET['state'])){
    $_state_check = get_cache($_GET['state']) == 1 ? true : false;   //这块缓存并不能证明是当前第三方网站的用户的,所以state并没有防御这个漏洞的效果
}else{
    $_state_check = false;    
}

解决方法还是要想方设法加入用户登录认证信息的校验,不愿想的话,session足矣。


问:如果想在参数state内传递多个参数,怎么办?

答:强烈建议在传递前进行一次base64编码!在这方面我吃过很大的亏,只是使用http_build_query而没有在最后再加一次base64_encode,导致在最后回调阶段的时候,因为php解析器自动帮我urldecode了参数state,导致数据不完整,哭死了!

 


三、安全研究员问答环节

问:《百度开放平台oauth授权接口可以劫持access_token》(WooYun-2012-12683)是否就是这个漏洞?
答:乌云这次确实搞错了。百度所反映的问题是OAuth 2.0所普遍存在的client-side Implicit Grant问题;但这个漏洞攻击的前提是使用了Authorization code方式(所以知道创宇所说的“推荐使用Authorization Code方式进行授权,而不要使用Implicit Flow方式。这样access token会从授权服务器以响应体的形式返回而不会暴露在客户端”在这个漏洞上并不成立的原因)。两者的流程并不一致。
关于client-side Implicit Grant问题,请参考本人之前写的另一篇文章:http://zone.wooyun.org/content/1088


问:知道创宇提供的解决方案,在业务上有没有可能存在冲突?
答:有一个不完全符合业务需求:“在绑定过程中,应强制用户重新输入用户名密码确认绑定,不要直接读取当前用户session进行绑定”。这样实质上增加了用户的负担;同时,现在新兴网站均在使用OAuth登录授权后自动创建账号,此时账号密码是随机的,用户并不知道,导致这种方案的流程实质走不下去。
对于大部分网站而言,加强state的校验、登录和绑定流程分开、不允许绑时换账号、只允许一对一绑定、绑时检查绑定状态等措施已经足够。


问:纵观全文,你既不能否定这个漏洞未来可以爆发到“最大规模”的可能性,又无充分案例证明不可能爆发。是否你在误导人更多一些啊?
答:是的,我无法证明,因为对于安全问题,绝大部分开发者永远不可能达到深入的境界,但是他们拥有对业务方面更专深的了解。安全研究者当然可以认为任何漏洞都可以爆发到不可收拾的后果,但摊分到业务,是否真的有充分的条件可以爆发?摊分到公众,是否真的需要冠上“最大”“史上”“灾难性”等词汇来刺激公众、造成恐慌?这正是我从业务层面写这篇文章的原因,至于是否误导(即云舒所说的第二和第三种程序员),相信日后会有证明。
特别是在这次漏洞传播中,有安全公司竟然传播错误的解决方案,就像一个本来教地震安全知识的课堂,却传授大家地震时往防空洞跑(即将防空知识错误地传授到地震领域中),用户觉得学到安全知识了,结果呢?太多空喊“狼来了”的后果大家都懂,那么错误的安全知识会否造成比这更严重的后果?安全公司在传授安全意识时,是否应当有更多务实的意识呢?IT界是一团浆糊,安全圈是娱乐圈,那么是否就要将公众当无知的公仔乱扯?…...

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有