最近,Ledger Donjon团队发现了Tangem钱包存在一个安全漏洞,并对此进行了公开披露。本文将从学习的角度对这份漏洞报告进行拆解,帮助读者理解事件的经过、影响、解决方案,以及我们可以从中学到的经验。
TLDR
首先,简要总结一下事件经过。Ledger Donjon团队发现了Tangem Android SDK中的一个实现漏洞,该漏洞允许绕过Tangem钱包的真实性检查,从而伪造Tangem卡片以盗取用户资产。Ledger Donjon团队已将此漏洞报告给Tangem团队,后者已修复该漏洞,并在Android版本5.18.3中发布了更新。如果您是Tangem的用户,请尽快升级到最新版本,以免受到攻击。这就是漏洞的简要概述,如果您对技术细节感兴趣,请继续阅读。
漏洞分析
真实性检查
在介绍漏洞之前,首先需要了解什么是固件/设备的真实性检查。简单来说,真实性检查是验证设备或设备中运行的代码是否为官方设备或官方发布版本的一种方法。通常,真实性检查的实现方式多种多样,例如:
- 设备中包含官方的公钥信息,用于在固件升级时验证固件的来源。
- 设备生产商在生产过程中生成一对公私钥,用于标记设备的来源,设备可以对外部信息进行签名,以证明其真实性。
Tangem的真实性检查
通常,硬件钱包制造商都会实施相应的真实性检查,Tangem也不例外。以下是Tangem钱包在真实性检查方面的具体实现。引自Ledger Donjon文章
- Tangem App向Tangem卡片发送一个16字节的消息。
- Tangem卡片生成一个salt,并使用其私钥对消息和salt进行签名。
- Tangem卡片将salt和签名返回给Tangem App。
- Tangem App将消息和salt拼接,然后使用Tangem卡片的公钥进行验证。
- Tangem App向 https://verify.tangem.com/card/verify-and-get-info 发送请求,使用Tangem卡片的ID和公钥。
- Tangem服务器检查卡片的ID和公钥是否匹配,如果匹配则返回相关信息。
Tangem钱包的这套真实性检查流程是在Tangem Android SDK中实现的。Tangem Android SDK已在GitHub上开源,具体代码在这里:Tangem Android SDK。
那么问题出在哪里呢?我分析了一下Tangem AttestationTask的代码,大致的调用逻辑如下:
- 入口点:验证过程从
ScanTask.runAttestation()
开始ScanTask.kt:93-96
- 主要任务执行:
AttestationTask.run()
方法启动整个过程AttestationTask.kt:46-52
- 卡片密钥验证:
attestCard()
方法运行AttestCardKeyCommand
来验证卡片的密钥AttestationTask.kt:54-55
- 基于模式的认证:根据认证模式(离线、普通或完整),采取不同的验证路径
AttestationTask.kt:87-98
- 在线验证:对于普通和完整模式,执行在线认证
AttestationTask.kt:90-96
- 钱包验证:对于完整模式,还会验证钱包密钥
AttestationTask.kt:215-223
- 状态处理:
processAttestationReport()
方法处理不同的认证状态AttestationTask.kt:154-199
- 用户交互:如果认证失败,向用户显示对话框以继续或取消
AttestationFailedDialog.kt:16-24
- 最终更新:
complete()
方法使用最终状态更新Card对象的认证字段AttestationTask.kt:287-289
Tangem的实现漏洞
那么Tangem的实现漏洞到底出在哪里呢?我们可以发现Tangem的在线验证是在卡片密钥验证之后进行的,即便卡片验证失败,在线验证仍然会继续进行。
卡片密钥验证的代码如下:
1
2
3
4
5
6
if (result.error is TangemSdkError.CardVerificationFailed) {
currentAttestationStatus = currentAttestationStatus.copy(
cardKeyAttestation = Attestation.Status.Failed,
)
continueAttestation(session, callback)
}
在线验证的代码如下:
1
2
3
4
5
6
7
8
val isAttestationFailed = card.attestation.cardKeyAttestation == Attestation.Status.Failed
if (isDevelopmentCard || isAttestationFailed) { // We won't go in this statement
onlineAttestationChannel.send(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
return@launch
}
when (val result = onlineCardVerifier.getCardInfo(card.cardId, card.cardPublicKey)) {
[...]
}
这里判断isAttestationFailed
是根据card.attestation.cardKeyAttestation
的状态。然而,该状态并未在卡片密钥验证中更新,因此即便卡片密钥验证失败,在线验证仍会继续进行,并且在线验证通过后,会将卡片的验证结果标记为成功。
漏洞利用
根据以上实现逻辑,可以很容易想到如何利用漏洞伪造一个Tangem卡片。
- 拿一个真的Tangem卡片,通过截取数据流或网络流的方式获取其公钥和ID。
- 构建一个假的Tangem卡片,写入第一步获取的公钥和ID,然后响应Tangem App的请求。
- 使用假的卡片与Tangem App进行交互,即使卡片的密钥验证失败,在线验证仍会将卡片的验证结果标记为成功。
- 假的卡片通过验证后,Tangem App会认为卡片是真实的,从而进行后续操作。
总结
Tangem钱包漏洞可以说是实现逻辑上的一个问题,作为一个安全工具的开发者和使用者,我们应当意识到安全工具的实现逻辑和安全机制的重要性,持续的利用安全建模等工具来不断的进行安全分析和持续的优化。
Comments powered by Disqus.