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

有一个外国研究员在最近研究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界是一团浆糊,安全圈是娱乐圈,那么是否就要将公众当无知的公仔乱扯?…...

 

摘自:http://zone.wooyun.org/content/1562


白帽子精彩评论:

shine (shield) | 2012-11-10 07:52

这里的问答模式很好,日后可能催生乌云一个新的功能或平台发展,介于安全与用户的第三方安全问答平台,也就是安全翻译了,楼主就是首席安全问答官,哈哈!

至于主不主观,个人想,如果问题都是不确定性地去描述,那还有什么观点意义了!

 

gainover (">_< ' / & \ 看啥,没见过跨站字符么) | 2012-11-10 10:34

@shine 嗯,从主观的角度讲,我觉得这篇文章挺客观的,既肯定了可能性,又否定了必然性。

 

horseluke (微碌) | 2012-11-10 10:38

@B1n4ry @伟大娃娃 @only_guest @gainover 我擦.......你们果然是夜猫。

昨晚想了一下,如果想广撒网,也不是不可以,攻击者需要许多个OAuth账号,然后不停地授权获取地址然后群发。但这个缺陷有:开放平台的异常登录封杀;撒网后还需要按受害者list进行检查......成本还是高了

 

gainover (">_< ' / & \ 看啥,没见过跨站字符么) | 2012-11-10 10:54

@horseluke 这样是不是黑客的oauth账号数目决定了攻击规模了?

 

horseluke (微碌) | 2012-11-10 11:09

@gainover 是。从我知道的情况来看,一个营销团队拥有上w账号是基本业务量要求。

 

p.z (让我们一起,把前列腺溶解在风里。) | 2012-11-10 12:04

@horseluke 获取授权地址之类的事情,完全可以在服务器端来完成,对于攻击者来说,只要大量的spam账号即可。攻击代码可简化到<img src='http://evil.com/hijack.php'/>,只要收益足够高,不排除有人会这么搞。

 

horseluke (微碌) | 2012-11-10 12:15

@p.z 用服务端频繁模拟授权,会导致平台对其进行ip限制问题;如果弄肉鸡,各平台也应该有对应的策略(因为肉鸡的对抗已经不是一天两天的事情了)。

收益问题,需要要看目标网站的属性和相关代码实现是否存在纰漏容易被利用。目前来讲,电商类确实会危险点。

 

Pnig0s (Know me then ignore me.) | 2012-11-10 12:29

其一,译文只提到了OAuth CSRF的部分问题。我们在其基础上进行了研究,扩大了攻击面,降低了利用成本。

其二,如你的文章题目-从开发者角度,而我们那篇文章更多不是给开发人员去看的。

其三,RFC怎么写是一回事,开发人员怎么用是另一回事。OAuth授权一环扣一环,用户其中一个帐号被劫持,他使用OAuth授权的所有帐号都会存在被劫持风险。比如:我劫持了目标的360帐号,而360又向其他厂商提供用户资源,那么就可以继续劫持用户在其他网站使用360平台登录的帐号。

其四,我们的报道点在于,广泛存在于整个互联网各行业且易于实施攻击,一个GET即可,不用搞接收平台,且为持久性劫持不必考虑时效性,鉴于目前研究OAuth安全问题的人并不多,我们的报道要起到预警作用。

其五,以讹传讹不是我们能控制的,但报道的文章中我们已字字斟酌。危言耸听是你狭隘了,仅以技术角度去分析那篇文章。你所说的质疑,也都集中在圈内,还要算上各路水军吧:)

其六,技术人员看不惯这种公司层面借用技术大面积报道可以理解。

综上,没有任何争论的必要了:)

P.S:至于你说的封杀,我基本把国内提供或使用第三方平台的知名站点都测试过,目前看我的号一个都没有被干掉:)当然还是辛苦你熬夜写出一篇文章,抛开事件不谈,还是值得开发人员学习和了解的,只是难道好文都要这么被逼出来么:)

 

horseluke (微碌) | 2012-11-10 12:39

@Pnig0s 对于开发人员来讲,他看到一篇不透明的文章,不知道如何修复,那么预警就完全失去了意义;对于公众,无处不在的漏洞和信息泄露下,漏洞再多再大都只会把公众继续推向不愿抵抗的羊群的边缘,这也是营销们可以肆无忌惮搞小动作搞口水的理由。

我反对的不是这个漏洞本身,我也承认这个漏洞可能的最大严重后果;我反对的,是报道这个漏洞的时候,没有结合业务来搞清楚,并且使用了疑似夸大与不透明的词汇,导致不必要的公众恐慌和口水。

 

xsser (十根阳具有长短!!) | 2012-11-10 12:45

@horseluke 譬如某些微博说取消所有应用授权...... 另外,不透明真没好处,总有一天有人会说,XXX存在严重安全漏洞,请大家卸载或者不要访问,而根本原因可能是个xss,如果这事真发生了,我们都有责任

 

horseluke (微碌) | 2012-11-10 12:59

摘一个转发:

===================================

http://weibo.com/1921985135/z4wUOvAon

@渥村万涛: 其实, 这个漏洞的根本原因, 是违反了RFC6749, 没有任何CSRF保护, 而不是没有使用state参数 (state只是实现CSRF保护的一种). 根据RFC6749 http://t.cn/zlTZyo6 state是推荐(SHOULD), 而不是必须(MUST). 但CSRF保护是MUST. 没有用state, 不违反标准, 但没有CSRF保护, 则违反

===================================

这个转发更能说明这个漏洞的本质。顿感自己写这文太啰嗦了,哈。

 

xsser (十根阳具有长短!!) | 2012-11-10 13:02

@horseluke 本质是状态的身份验证吧,还是state的事儿,不认为是csrf

 

c4bbage | 2012-11-10 13:08

rfc   RFC

 

horseluke (微碌) | 2012-11-10 13:40

@xsser 其实相关身份验证参数还可以放到redirect_uri上(某些不提供state参数的还真只能放在这里面);只不过state参数在各平台更加通用。

 

[点此围观更多白帽子评论]

相关内容:

从开发角度也说《互联网最大规模帐号劫持漏洞即将引爆》

互联网最大规模帐号劫持漏洞即将引爆

留言评论(旧系统):

佚名 @ 2012-11-12 16:00:33

CSRF问题,开放平台文档里写的很明白了。新浪:http://t.cn/a3jeVD 腾讯:http://t.cn/zjPcXUr 人人:http://t.cn/zjPc8fk

本站回复:

Good!