
移动应用程序的受欢迎程度持续增长。 移动应用程序上的OAuth 2.0协议也是如此。 仅仅实现标准以使OAuth 2.0协议在那里安全是不够的。 需要考虑移动应用程序的细节并应用一些其他安全机制。
在本文中,我想分享用于防止此类问题的移动OAuth 2.0攻击和安全机制的概念。 所描述的概念不是新概念,但是缺少有关此主题的结构化信息。 本文的主要目的是填补这一空白。
OAuth 2.0的性质和目的
OAuth 2.0是一种
授权协议,描述了客户端服务获得对服务提供商上用户资源的安全访问的方法。 借助OAuth 2.0,用户无需在服务提供商外部输入密码:整个过程简化为单击“我同意提供对...的访问权限”按钮。
提供者是拥有用户数据的服务,并在用户许可下,为第三方服务(客户端)提供对此数据的安全访问。 客户端是想要获取提供者存储的用户数据的应用程序。
OAuth 2.0协议发布后不久,它就进行了
身份验证 ,尽管它并不是为了实现此目的。 使用OAuth 2.0进行身份验证会将攻击向量从服务提供商处存储的数据转移到客户端服务用户帐户。
但是身份验证只是一个开始。 在移动应用和赞美转化的时代,仅需一个按钮即可访问应用。 开发人员将OAuth 2.0修改为可移动使用。 当然,很少有人担心移动应用程序的安全性和特性:zap进入他们的生产! 再说一次,OAuth 2.0在Web应用程序之外无法正常运行:移动和桌面应用程序中都存在相同的问题。
因此,让我们找出如何使移动OAuth 2.0安全。
如何运作?
存在两个主要的移动OAuth 2.0安全问题:
- 不受信任的客户。 某些移动应用程序没有OAuth 2.0的后端,因此协议流的客户端部分位于移动设备上。
- 从浏览器重定向到移动应用程序的行为会有所不同,具体取决于系统设置,应用程序的安装顺序和其他功能。
让我们深入研究这些问题。
移动应用程序是公共客户端
为了了解第一个问题的根源和后果,让我们看看在服务器到服务器交互的情况下OAuth 2.0是如何工作的,然后在客户机到服务器交互的情况下将其与OAuth 2.0进行比较。
在这两种情况下,都以客户端服务在提供者服务上的注册开始,并接收
client_id
和
,
在某些情况下为
, client_secret. client_id
, client_secret. client_id
是一个公共值,它是客户端服务标识所必需的,而
client_secret
值是私有的。 您可以在
RFC 7591中阅读有关注册过程的更多信息。
以下方案显示了在服务器到服务器交互的情况下OAuth 2.0的运行方式。
图片来源: https : //tools.ietf.org/html/rfc6749#section-1.2OAuth 2.0协议可分为三个主要步骤:
- [步骤AC]接收
authorization_code
(以下称为code
)。
- [步骤DE]将
code
交换到access_token
。
- 通过
access_token
获取资源。
让我们详细说明获得
code
值的过程:
- [步骤A]客户端将用户重定向到服务提供商。
- [步骤B]服务提供商向用户请求许可以向客户端提供数据(箭头B向上)。 用户提供数据访问权限(右侧的箭头B)。
- [步骤C]服务提供商将
code
返回到用户浏览器,后者将code
重定向到客户端。
让我们更多地讨论获取
access_token
的过程:
- [步骤D]客户端服务器发送一个
access_token
请求。 Code
, client_secret
和redirect_uri
包含在请求中。
- [步骤E]在有效
code
client_secret
和redirect_uri
情况下,提供access_token
。
对
access_token
请求是根据服务器到服务器的方案完成的:因此,通常,攻击者必须破解客户端服务服务器或服务提供者服务器才能窃取
access_token
。
现在,让我们看一下没有后端(客户端到服务器的交互)的移动OAuth 2.0方案。
图片来源: https : //tools.ietf.org/html/rfc8252#section-4.1主要方案分为相同的主要步骤:
- [图片中的步骤1-4]获取
code
。 - [图片中的步骤5-6]交换
code
到access_token
- 通过
access_token
获得资源访问
但是,在这种情况下,移动应用程序还具有服务器功能。 因此,
client_secret
将被嵌入到应用程序中。 结果,
client_secret
无法在移动设备上对攻击者隐藏。 嵌入式
client_secret
可以通过两种方式提取:通过分析应用程序到服务器的流量或通过反向工程。 两者都可以轻松实现,这就是为什么
client_secret
在移动设备上无用的原因。
您可能会问:“为什么我们不立即获得
access_token
?” 您可能认为这多余的步骤是不必要的。 此外,还有一个
隐式授予方案,该方案允许客户端立即接收
access_token
。 即使可以在某些情况下使用它,
隐式授予也不适用于安全的移动OAuth 2.0。
在移动设备上重定向
通常,
自定义URI方案和
AppLink机制用于浏览器到应用程序的重定向。 这两种机制都无法像浏览器自行重定向一样安全。
自定义URI方案 (或深层链接)以以下方式使用:开发人员在部署之前确定应用程序方案。 该方案可以是任何方案,并且一个设备可以具有相同方案的多个应用程序。
当设备上的每个方案都与一个应用程序相对应时,这使事情变得容易。 但是,如果两个应用程序在一个设备上注册相同的方案怎么办? 通过自定义URI方案联系时,操作系统如何决定打开哪个应用程序? Android将显示一个窗口,其中包含一个应用程序的选择以及一个链接。 iOS
没有为此提供程序 ,因此,可以打开任何一个应用程序。 无论如何,攻击者
就有机会拦截代码或access_token 。
与自定义URI方案不同,
AppLink保证打开正确的应用程序,但是此机制有几个缺陷:
- 每个服务客户都必须经过验证程序 。
- Android用户可以在设置中关闭特定应用程序的AppLink。
- 早于6.0的Android版本和早于9.0的iOS版本不支持AppLink。
所有这些AppLink缺陷都会增加潜在服务客户端的学习难度,并且在某些情况下可能导致用户OAuth 2.0失败。 这就是为什么许多开发人员不选择AppLink机制来替代OAuth 2.0协议中的浏览器重定向的原因。
好,那里有什么要攻击的?
移动OAuth 2.0问题导致了一些特定的攻击。 让我们看看它们是什么以及它们如何工作。
授权码拦截攻击
让我们考虑用户设备具有合法应用程序(OAuth 2.0客户端)和恶意应用程序注册了与合法应用程序相同的方案的情况。 下图显示了攻击方案。
图片来源https://tools.ietf.org/html/rfc7636#section-1这就是问题所在:第四步,浏览器通过“自定义URI方案”返回应用程序中的
code
,因此,恶意应用程序可能会截获该
code
(因为它注册的程序与合法应用程序相同)。 然后,恶意应用将
code
更改为
access_token
并接收对用户数据的访问。
有什么保护? 在某些情况下,您可以使用进程间通信。 我们稍后再讨论。 通常,您需要一个称为
代码交换证明密钥的方案。 在下面的方案中进行了描述。
图片来源: https : //tools.ietf.org/html/rfc7636#section-1.1客户请求有几个额外的参数:
code_verifier
,
code_challenge
(在方案
t(code_verifier)
)和
code_challenge_method
(在方案
t_m
)。
Code_verifier
是一个随机数
,最小长度为256位 ,
只能使用一次 。 因此,客户端必须为每个
code
请求生成一个新的
code_verifier
。
Code_challenge_method
这是转换函数的名称,主要是SHA-256。
Code_challenge
是对
code_challenge_method
转换并在URL Safe Base64中进行编码的
code_challenge_method
。
请求
code
时,必须根据
code_verifier
拦截(例如,来自设备系统日志),将
code_challenge
转换为
code_verifier
以拒绝攻击向量。
如果用户设备
不支持 SHA-256,
client is allowed to use plain conversion of code_verifier
。 在所有其他情况下,必须使用SHA-256。
此方案的工作方式如下:
- 客户端生成
code_verifier
并将其存储。
- 客户选择
code_challenge_method
并从code_verifier
接收code_verifier
。
- [步骤A]客户请求
code
,其中code_challenge
和code_challenge_method
添加到请求中。
- [步骤B]提供程序将
code_challenge
和code_challenge_method
存储在服务器上,并将code
返回给客户端。
- [步骤C]客户端请求
access_token
,并添加了code_verifier
。
- 提供者从传入的
code_verifier
接收到code_verifier
,然后将其与保存的code_challenge
进行比较。
- [步骤D]如果值匹配,则提供程序将给客户端
access_token
。
为了理解为什么
code_challenge
代码拦截,让我们从攻击者的角度来看协议流的外观。
- 首先,合法的应用程序请求
code
( code_challenge
和code_challenge_method
与请求一起发送 )。
- 恶意应用会拦截
code
(但不会拦截code_challenge
,因为代码_challenge
不在响应中)。
- 恶意应用程序请求
access_token
(具有有效code
,但没有有效code_verifier
)。
- 服务器注意到
code_challenge
不匹配,并引发错误消息。
请注意,攻击者无法猜测
code_verifier
(随机256位值!)或在日志中的某个位置找到它(因为第一个请求实际上发送了
code_challenge
)。
因此,
code_challenge
回答了服务提供者的问题:“
access_token
是由请求
code
的同一应用程序客户端请求的,还是由不同的应用程序客户端请求的?”。
OAuth 2.0 CSRF
使用OAuth 2.0进行授权时,OAuth 2.0 CSRF相对无害。 使用OAuth 2.0进行身份验证时,情况完全不同。 在这种情况下,OAuth 2.0 CSRF通常会导致帐户接管。
让我们通过的士应用程序客户端和provider.com提供程序示例,进一步讨论符合OAuth 2.0的CSRF攻击。 首先,攻击者使用自己的设备登录到
attacker@provider.com
帐户,并接收出租车
code
。 然后,他中断OAuth 2.0进程并生成一个链接:
com.taxi.app://oauth? code=b57b236c9bcd2a61fcd627b69ae2d7a6eb5bc13f2dc25311348ee08df43bc0c4
然后,攻击者将该链接发送给受害者,例如以出租车物品的邮件或文本消息的形式。 受害者单击链接,出租车应用程序打开并接收
access_token
。 结果,他们发现自己进入了
攻击者的出租车帐户。 受害人不知道这一点,而是使用该帐户:出差,输入个人数据等。
现在,攻击者可以随时登录到受害者的出租车帐户,因为它已链接到
attacker@provider.com
。 CSRF登录攻击使违反者可以窃取帐户。
通常使用CSRF令牌(也称为
state
)来拒绝CSRF攻击,OAuth 2.0也不例外。 如何使用CSRF令牌:
- 客户端应用程序生成CSRF令牌并将其保存在客户端的移动设备上。
- 客户端应用程序将CSRF令牌包含在
code
访问请求中。
- 服务器在响应中返回带有
code
的相同CSRF令牌。
- 客户端应用程序比较传入和保存的CSRF令牌。 如果它们的值匹配,则过程继续。
CSRF令牌要求:
随机数必须至少为256位,并且必须从良好的伪随机序列源中接收。
简而言之,CSRF令牌允许应用程序客户端回答以下问题:“是发起
access_token
请求的我还是有人试图欺骗我?”。
硬编码的客户机密
没有后端的移动应用程序有时会存储硬编码的
client_id
和
client_secret
值。 当然,可以通过逆向工程应用程序轻松提取它们。
公开
client_id
和
client_secret
很大程度上取决于信任服务提供者对特定的
client_id
和
client_secret
对的信任程度。 一个使用它们只是为了将一个客户端与另一个客户端区分开,而其他客户端则打开隐藏的API端点或对某些客户端设置较慢的速率限制。
为什么移动应用中的OAuth API密钥和机密不安全一文详细介绍了该主题。
恶意应用程式充当合法客户
某些恶意应用程序可以模仿合法应用程序,并代表它们显示同意屏幕(同意屏幕是用户看到的屏幕:“我同意提供对...的访问权限”)。 用户可以单击“允许”,然后向恶意应用程序提供其数据。
Android和iOS提供了应用程序交叉检查的机制。 应用程序提供商可以确保客户端应用程序是合法的,反之亦然。
不幸的是,如果OAuth 2.0机制通过浏览器使用线程,则无法防御这种攻击。
其他攻击
我们仔细研究了移动OAuth 2.0专有的攻击。 但是,我们不要忘记原始的OAuth 2.0:
redirect_uri
替换,通过不安全连接进行的流量拦截等。 您可以
在此处了解更多信息。
如何安全地做?
我们已经了解了OAuth 2.0协议的工作原理以及它在移动设备上的漏洞。 现在,让我们将各个部分放在一起,以形成一个安全的移动OAuth 2.0方案。
OAuth 2.0的好坏
让我们从正确的方式使用同意屏幕开始。 移动设备有两种在移动应用程序中打开网页的方式。

第一种方法是通过“浏览器自定义”选项卡(在图片的左侧)。
注意 :适用于Android的浏览器自定义标签称为Chrome自定义标签,适用于iOS的名称为SafariViewController。 它只是应用程序中显示的浏览器选项卡:在应用程序之间没有视觉切换。
第二种方法是通过WebView(在图片的右侧),对于移动OAuth 2.0而言,我认为它很糟糕。
WebView是用于移动应用程序的嵌入式浏览器。
“
嵌入式浏览器 ”表示WebView禁止访问Cookie,存储,缓存,历史记录以及其他Safari和Chrome数据。 反之亦然:Safari和Chrome无法访问WebView数据。
“
移动应用程序浏览器 ”表示运行WebView的移动应用程序具有对Cookie,存储,缓存,历史记录和其他WebView数据的
完全访问权限。
现在,想象一下:用户单击“输入...”,恶意应用程序的WebView向服务提供商请求其登录名和密码。
史诗失败:
- 用户在应用程序中输入其服务提供商帐户的登录名和密码,可以轻松窃取此数据。
- OAuth 2.0最初是为了不输入服务提供商的登录名和密码而开发的。
用户习惯于在任何地方输入他的登录名和密码,从而增加了钓鱼的可能性。
考虑到WebView的所有缺点,可以得出一个明显的结论:将“浏览器自定义选项卡”用于同意屏幕。
如果有人赞成使用WebView而不是“浏览器自定义选项卡”,那么如果您在注释中写到它,将不胜感激。
安全的移动OAuth 2.0方案
我们将使用授权代码授予方案,因为它允许我们添加
code_challenge
以及
state
和防御代码拦截攻击和OAuth 2.0 CSRF。
图片来源: https : //tools.ietf.org/html/rfc8252#section-4.1代码访问请求(步骤1-2)如下所示:
https://o2.mail.ru/code? redirect_uri=com.mail.cloud.app%3A%2F%2Foauth& state=927489cb2fcdb32e302713f6a720397868b71dd2128c734181983f367d622c24& code_challenge=ZjYxNzQ4ZjI4YjdkNWRmZjg4MWQ1N2FkZjQzNGVkODE1YTRhNjViNjJjMGY5MGJjNzdiOGEzMDU2ZjE3NGFiYw%3D%3D& code_challenge_method=S256& scope=email%2Cid& response_type=code& client_id=984a644ec3b56d32b0404777e1eb73390c
3D%3D& https://o2.mail.ru/code? redirect_uri=com.mail.cloud.app%3A%2F%2Foauth& state=927489cb2fcdb32e302713f6a720397868b71dd2128c734181983f367d622c24& code_challenge=ZjYxNzQ4ZjI4YjdkNWRmZjg4MWQ1N2FkZjQzNGVkODE1YTRhNjViNjJjMGY5MGJjNzdiOGEzMDU2ZjE3NGFiYw%3D%3D& code_challenge_method=S256& scope=email%2Cid& response_type=code& client_id=984a644ec3b56d32b0404777e1eb73390c
在第3步中,浏览器将获得带有重定向的响应:
com.mail.cloud.app://outh? code=b57b236c9bcd2a61fcd627b69ae2d7a6eb5bc13f2dc25311348ee08df43bc0c4& state=927489cb2fcdb32e302713f6a720397868b71dd2128c734181983f367d622c24
在步骤4,浏览器打开“自定义URI方案”,并将CSRF令牌传递给客户端应用。
access_token
请求(步骤5):
https://o2.mail.ru/token? code_verifier=e61748f28b7d5daf881d571df434ed815a4a65b62c0f90bc77b8a3056f174abc& code=b57b236c9bcd2a61fcd627b69ae2d7a6eb5bc13f2dc25311348ee08df43bc0c4& client_id=984a644ec3b56d32b0404777e1eb73390c
最后一步带来了带有
access_token
的响应。
这种方案通常是安全的,但是在某些特殊情况下OAuth 2.0可以更简单,更安全。
Android IPC
Android具有进程之间的双向数据通信机制:IPC(进程间通信)。 IPC优于自定义URI方案有两个原因:
- 打开IPC频道的应用程序可以通过其证书确认其打开的应用程序的真实性。 反之亦然:打开的应用程序可以确认打开它的应用程序的真实性。
- 如果发送方通过IPC通道发送请求,则可以通过同一通道接收答复。 与交叉检查(项目1)一起,这意味着没有外部进程可以拦截
access_token
。

因此,我们可以使用
隐式授予来简化移动OAuth 2.0方案。 没有
code_challenge
和
state
也意味着更少的攻击面。 我们还可以降低恶意应用像合法客户端试图窃取用户帐户的风险。
客户专用SDK
除了实施此安全的移动OAuth 2.0方案外,提供商还应为其客户开发SDK。 它将简化客户端的OAuth 2.0实施,同时减少错误和漏洞的数量。
结论
让我为您总结一下。 这是OAuth 2.0提供程序的
安全OAuth 2.0的(基本)
清单 :
- 坚实的基础至关重要。 在移动OAuth 2.0的情况下,基础是为实现选择的方案或协议。 在实施自己的OAuth 2.0方案时很容易出错。 其他人已经敲门并吸取了教训。 从他们的错误中学习并一劳永逸地进行安全实施并没有错。 如何安全地进行操作中介绍了最安全的移动OAuth 2.0方案。
Access_token
和其他敏感数据必须存储在iOS的钥匙串中和Android的内部存储中。 这些存储是专门为此而开发的。 Content Provider可以在Android中使用,但必须进行安全配置。
Client_secret
是无用的 ,除非它存储在后端。 不要将其赠送给公共客户。
- 不要将WebView用于同意屏幕; 使用浏览器自定义标签。
- 要防御代码拦截攻击,请使用
code_challenge
。
- 要防御OAuth 2.0 CSRF,请使用
state
。
- 随处使用HTTPS,禁止降级为HTTP。 这是3分钟的演示,说明原因(并附有漏洞赏金示例)。
- 遵循加密标准 (算法选择,令牌长度等)。 您可以复制数据并弄清楚为什么要这样做,但不要自行加密。
Code
只能使用一次,并且使用寿命很短。
- 在应用客户端,检查您为OAuth 2.0打开的内容; 然后从应用提供商的角度,检查谁为您打开了OAuth 2.0。
- 请记住常见的OAuth 2.0漏洞 。 移动OAuth 2.0会放大并完善原始OAuth 2.0,因此,
redirect_uri
检查是否完全匹配,并且其他针对原始OAuth 2.0的建议仍然有效。
- 您应该为客户提供SDK。 他们的错误和漏洞更少,并且他们可以更轻松地实现您的OAuth 2.0。
进一步阅读
- “移动OAuth 2.0的漏洞” https://www.youtube.com/watch?v=vjCF_O6aZIg
- OAuth 2.0竞争状况研究https://hackerone.com/reports/55140
- 关于OAuth 2.0的几乎所有内容都集中在一个地方https://oauth.net/2/
- 为什么OAuth API密钥和秘密在移动应用程序中不安全https://developer.okta.com/blog/2019/01/22/oauth-api-keys-arent-safe-in-mobile-apps
- [RFC]适用于本机应用程序的OAuth 2.0 https://tools.ietf.org/html/rfc8252
- [RFC] OAuth公共客户端用于代码交换的证明密钥https://tools.ietf.org/html/rfc7636
- [RFC] OAuth 2.0威胁模型和安全注意事项https://tools.ietf.org/html/rfc6819
- [RFC] OAuth 2.0动态客户端注册协议https://tools.ietf.org/html/rfc7591
- 适用于移动和桌面应用程序的Google OAuth 2.0 https://developers.google.com/identity/protocols/OAuth2InstalledApp
学分
感谢所有帮助我写这篇文章的人。 特别是谢尔盖·贝洛夫(Sergei Belov),安德烈·苏敏(Andrei Sumin),安德烈·拉布涅(Andrey Labunets)提供了有关技术细节的反馈,帕维尔·克鲁格洛夫(Pavel Kruglov)提供了英文翻译,达里亚·雅科夫列娃(Daria Yakovleva)提供了本文俄语版的帮助。