在Web3领域,密钥托管(crypto custody)是维护用户资产安全的关键环节,业界广泛应用多种先进技术,如硬件钱包和可信执行环境(TEE),用于保障用户资产安全。
本次演讲将围绕硬件钱包以及基于TrustZone的移动端钱包的实现方案,在肯定其安全优势的同时,也剖析其存在的内存损坏漏洞等潜在威胁。最后,针对过往的风险,演讲嘉宾为安全开发者和Web3爱好者提供了密钥托管的开源方案和示例应用。
一起来回顾下庄园 在SDC2024 上发表的议题演讲:《从硬件钱包到TrustZone:Web3密钥托管的安全挑战与解决方案》
【庄园:CertiK Skyfall团队安全研究员】
*以下为速记全文
大家好,我叫庄园,来自CertiK Skyfall团队。CertiK是一家Web3领域的安全公司,Skyfall团队是CertiK的一个研究团队,我们会做一些Web3领域的比如说链上的安全问题的研究,还会做一些传统安全和Web3领域结合的一些研究。我个人主要是做TrustZone相关的,包括漏洞的发现、开源项目开源社区的一些工作,还有在Web3领域的解决方案的一些工作。
今天分享的主题是Web3的密钥托管,密钥托管英文是Key Custodian,它其实就是去管理我们Web3用户的密钥,包括生成还有使用它的整个流程,确保用户的Web3密钥在整个生命周期都是安全的。
这次分享主要分成3个部分:
1. 介绍Web3密钥保护的相关背景,也强调对于Web3的用户来说,它是非常重要的,也是保护他们资产的非常关键的问题。
2. 分析几个主流做密钥托管的方案,包括我们现在用的比较多的硬件钱包、TrustZone钱包等,它们提供了非常重要的安全保证,但是在实现的时候仍然可能会面临一些安全风险,最主要的就是内存破坏漏洞。
3. 分享我们设计的一个跨平台的开源密钥保护解决方案,并且我们在实现的时候去确保内存安全,从而降低内存破坏漏洞导致的安全风险。
一、Web 3 密钥保护背景
首先我们先介绍一下Web3相关的背景,最左边的这张图是Apple Pay的截图,它是Web2的钱包,还有我们平时用的比较多的比如支付宝微信等,它的用户数据,包括余额、转账、用户账号的信息都是存在厂商的数据库上。
右边是Web3的钱包,是Metamask的页面,同样是有账号、余额,转账这几个概念,但是它和Web2钱包不一样的地方,第一个就是它的转账、交易都是存在去中心化的区块链上。
第二个不一样就是它的账号、还有转账交易的进行,本质上是不一样的。比如我们初始化一个Web3钱包的时候,首先会生成一串随机数,然后通过随机数来派生私钥,再生成公钥,最后公钥哈希之后是账号的地址。为了方便去备份这个随机数,我们会把它转换成助记词,现在用的比较多的是12或24个英文单词,助记词就可以用来恢复密钥。
也就是说,如果我们助记词泄露了,也是相当于密钥也泄露了。正常使用Web3钱包,在set up的时候,一般都会提示你去生成助记词,最好找一张纸把它写下来。
Web3钱包的私钥对于用户来说非常重要,在创建交易的时候用户会使用私钥对交易进行签名,然后签名完之后就把它发给区块链网络,网络只验证签名的有效性。如果私钥泄露的话,其实就等同于整个资产都有风险。
那么私钥的存储就是一个很重要的问题了,现在移动端上面的环境是非常复杂的,用户可能会装各种各样的应用,我们的攻击场景就是攻击者在用户的手机上安装了一些恶意软件,并且能够通过一些提权能够拿到root权限或者其他的特殊权限。私钥如果是存在终端上面,那么它用什么方式存在上面是更安全的呢?
我们去调研了一些当前密钥存储的方案,并且把它们分成了几个不同的等级。首先S0和S1这两个等级,第一个密钥是直接明文存储在终端文件里面,第二个密钥是加密存储在终端的文件里面,使用的时候是在内存里面解密之后再用的,这两个显然都是不安全的,因为攻击者如果拿到了设备的root权限,它很容易从内存里面去dump出明文的密钥。
第二种和第三种是用了TrustZone,比如说安卓的Keystore,安卓的Keystore是 TrustZone里面的一个应用,它会提供一些密钥的加密解密,还有签名验证等基本的功能。
但是它有一个问题,就是Keystore它并不支持Web3的签名算法,所以这就导致了这个钱包的APP即使是用了Keystore去保护了它的私钥,但是它实际上去做签名的时候,还是在外面把密钥解密之后在一个明文的状态存在在内存里面再去做签名。这样的话我们的root权限攻击者也能够去dump出来用户的密钥。(参考链接:https://www.certik.com/zh-CN/resources/blog/4YByvvbbq8vCj1dxdulTXr-web3-mobile-wallet- apps-a-secret-key-protection-perspective)
那么下一个等级可以为用户的密钥做一个安全隔离。第一个是硬件钱包的方案,第二个是TrustZone钱包的方案。首先硬件钱包思路很简单,就是我们把私钥存在另外一个硬件设备上,只有在使用的时候才能够访问,其他时间都是不能去访问里面的密钥,所以它就能够做到一个密钥的有效隔离。
第二个TrustZone的钱包,就是我们刚才说的,TrustZone里面Keystore它是没有提供Web3的签名相关的功能。如果自己在TrustZone里面去实现一个、去写一个应用,然后提供这些Web3的签名功能。
我们刚才说的硬件钱包和TrustZone钱包这两种方案都是提供了必要的、对密钥隔离的安全保证的,那么它们在实现的时候也可能会有一些风险。
下面我们就分别介绍从硬件钱包还有TrustZone钱包,它们的实现、它们的安全保证,它们的流程以及它们可能的风险,也会介绍一些特定漏洞的例子。
二、硬件钱包和 TrustZone钱包的安全保证及风险
2.1 硬件钱包
首先第一部分是硬件钱包,除了一个像USB一样的硬件设备之外,厂商还会提供一套对应的客户端软件,安装在我们自己的终端上,比如安装在手机上或电脑上,通过这个软件和硬件钱包做交互。
这个软件相当于是客户端,然后硬件钱包相当于服务端,去处理客户端发给它的命令和请求。
对于硬件钱包,安全保证有3点:
1.密钥的隔离存储;
2.确认交易之后才能签名,因为我们是不能直接信任客户端那边传过来的交易,用户必须要在硬件钱包的屏幕上面确认;
3.访问以及使用硬件钱包的时候,需要有一个用户的认证,用户需要先输入PIN才能够使用。这个就是为了防止别人拿到了用户的硬件钱包,然后直接操作用户的资产。
我们用硬件钱包去发起一个交易,它的工作流程是什么样的?首先我们的个人设备,比如说手机或电脑作为客户端,安装了厂商提供的客户端的软件,那么现在比如说要发起一个转账,我们想要给Alice发10个BTC,然后我们就在APP里面做操作了,初始化了这一个交易。
然后第二步,交易的信息通过蓝牙或者通过USB会传给硬件钱包,然后硬件钱包会处理这一个请求。
第四步就是硬件钱包,它会在屏幕上显示用户的交易信息,然后用户要先认证,认证之后确认交易信息,确认之后,第五步就是硬件钱包会调用Secure Element里面的私钥,对这个交易进行签名,签完之后就会把签名的交易返回给客户端。
刚才那个流程来看,攻击面大概有这几个:
1.客户端和服务端之间的通信。第一点,我们可以篡改他们通信请求的数据,比如说发送一些畸形的数据,让硬件钱包去处理,看它处理过程中会不会有问题。
2.配置错误。硬件钱包可能是有一些用于Debug或者是在工厂模式下才能调用的命令,如果它的配置是不当的,让我们的客户端在任何情况下都可以去调命令的话,它也是可能会有问题。
3.用户的交互。我们刚才说到用户会在屏幕上或硬件钱包的屏幕上去确认这个交易,但是如果我们在确认的时候,对于整个交易的信息显示不全,比如我们知道有的地址可能很长,显示不全的话,它也有可能会欺骗用户去算它一些非预期的交易。
下面有两个漏洞的例子。
第一个是OneKey这个例子,这个是Offside Labs在去年年底报给OneKey的。它这个例子主要的问题是两个,第一个就是OneKey它有一个配置错误的问题,导致client端可以去调用OneKey的非预期的命令。
然后第二个问题,它在处理命令时有内存破坏漏洞,所以这两个问题最终可以导致代码执行,能够拿到用户的助记词。
OneKey它的代码是fork的Trezor代码,所以它的底层通信协议也是用的Trezor的协议。Trezor通信协议是基于protobuf的,最上层是protobuf,下层是Trezor它的wire protocol,就是底层的协议,这个协议会控制客户端去调用它的一些命令属性。
Factory它本来的意思是想在工厂模式下才可以调用这一个命令,但是这里有一个问题,“facotry”的拼写是错的,所以这就导致它自己的权限控制是失效的,我们的client端在任何状态下都可以调用这两个命令。flash读写的这两个命令有越界读,还有任意地址写,结合这几个漏洞,最终实现了任意代码执行,并且能够拿到用户的助记词。(参考链接:https://blog.offside.io/p/one-key-bug-in-onekey-mini)
所以这个就是通信协议层面对于message handler相关的内存安全的问题。
第二个问题是Ledger,Ledger去年年底发了一个安全事故的报告,说它们遭受了供应链攻击,主要是他们自己官方的SDK被植入了一些恶意代码,最后的效果就可以去欺骗用户,去签一些非预期的交易。除了供应链攻击之外,这里还想强调一个问题,就是我们刚才说到用户的交易完整确认问题。在Ledger用户确认的时候,其实没有显示完整的交易信息,所以才会有事故的存在。(参考链接:https://www.ledger.com/blog/security-incident-report )
2.2 TrustZone钱包
下一部分就是TrustZone的钱包,TrustZone是ARM芯片的一个硬件级安全拓展,它可以把同一个设备分成安全状态和非安全状态两种模式,底层的Secure Monitor负责状态的切换,比如说Untrusted World,运行的是Rich OS,比如说安卓上面跑了一些各种各样用户的程序。
Trusted World运行的是TrustZone OS,上面跑的刚才说的Keystore,用来做密钥管理的TrustZone的应用,或者是我们新开发的一些Web3的应用。
那么TrustZone Web3钱包的安全保证,首先它可以提供和硬件钱包同等的安全保证,包括以下3点:
1.密钥隔离存储,密钥可以存储在TrustZone的Secure Storage里面。
2.用户可以确认交易之后再签名,在TrustZone可以发起Trusted UI,在当前的用户手机屏幕上会显示当前的交易信息,这个显示是TrustZone直接连到屏幕上面的。
3.用户身份认证,我们同样可以通过设置PIN,来控制用户对TrustZone里面APP使用时候的权限。
除此之外TrustZone钱包相比较硬件钱包还有一个优势,它的使用更加方便,用手机就可以了,不需要去另外携带一个硬件设备。
那么TrustZone钱包它有什么风险,有什么漏洞的例子?我们可以看三星的Blockchain Keystore的例子。
下面两个图是三星官网上介绍他们方案的图,最左边的图是他们自己的方案架构,最上面就是运行在Rich OS上面的一些DAPP、Wallet等等。中间这一层也是运行在Rich OS上面的,它主要是用来向TrustZone的TA发送一些数据,最终会发到TrustZone APP里面,然后TrustZone APP来处理。第三层就是Trusted Apps它们在TrustZone里面的APP提供比如说密钥的生成、管理、使用、签名,整个流程的管理功能。(参考链接:https://developer.samsung.com/blockchain/keystore/understanding-keystore/keystore-architecture.html)
它的攻击面首先和硬件钱包是类似的,最主要的攻击面就是CA和TA的通信,CA就是运行在Rich OS里面,去向TA发请求的程序。TA是运行在TrustZone里面负责处理这些请求的应用。所以TA就相当于是一个server,它会提供各种各样的命令,让外面的CA来调用。
对于这些攻击面,我们在去年也做过一些研究,发现了一些问题,同时我们上报给了三星,也帮助三星Blockchain Keystore修复了,对于评级为Critical的漏洞,都是能够获取到用户保存在TrustZone里面的私钥。
我们把这些漏洞的根本原因分成了3点:
1. TrustZone里面对于外界的输入,我们都应该把它视作不可信的输入,对于这些输入我们必须要有严格的验证,但是它的这些漏洞,它的原因都是没有做过完全的验证。
2. 就是我们数学运算的时候可能会缺少检查。
3. 我们通过正常的log和panic log就可以泄露出来系统的一些重要信息。
下面有3个漏洞的例子,我们会对应和这些漏洞的根本原因一起讲。
第一个是验证不完善的问题,这个漏洞是OOB Write全局数据漏洞,左边就是我们在正常的场景下,它这一块逻辑是在做什么事情。
首先我们把分成了Normal World和Secure World两个部分,Normal World就相当于我们在CA,在Rich OS里面的程序,它要准备一个buffer,传给TrustZone里面的TA去处理。
buffer的结构是这样,最上面是一个totalcount,相当于我要传几个数据,下面是data_size,每一个data的大小还有内容。那么buffer传到TEE里面,有一个全局的数组,存data1_size到dataN_size,第二个全局的数组,去存data1在TEE堆上面的地址,一直到dataN在TEE堆上面的地址。
全局的数组它有一个固定的长度是36,所以说这就有一个问题,我们的totalcount也是从外界传过来的,比如说它现在固定是n就是36,它在TrustZone里面对totalcount是没有有效验证的,这就导致了我们能从外面传一个很大的totalcount,但是它在TrustZone里面,只有一个固定大小的数组来存所有的这些size和指针,这就导致了如果totalcount比较大,它就可以去覆盖右边data_size的值,这个数组下面的内容也可以覆盖到data的指针,这样后期也可以转成一个Free任意地址漏洞。
第二个例子是OOB Read全局数据。
首先Normal World还是准备了一个buffer传给了Secure World,然后buffer的内容就是每一个data的size,还有每一个data的内容,每个data它的数据,这一块input_buffer是在TrustZone里面的global,有一个全局的buffer,然后去存它,它的大小是和input_buffer大小是一样的。
但还是有这个问题,就是input_buffer它整个的大小是有校验过的,并且和TrustZone里大小是一致的,但是它input_buffer里面的data_size没有校验,这个data_size是有可能超过我们整个input_buffer的大小,这样就可能导致一个OOB。另外还有一个信息泄露的问题,我们可以去结合malloc的一个error message,去读一些内存的数据。
第三个漏洞分两个步骤,第一个步骤就是去外面传一个sum_size,然后第二个步骤就是外面去传大buffer里面的某一块小buffer,这相当于是把初始化一个大buffer分成了好几个步骤。
第一个步骤先是把一个大buffer的size传过来,之后再把一些小buffer传过来,然后把这些小buffer去set到大buffer里面的某个offset的位置。
这个地方有一个问题,就是我们的小buffer的offset和size应该是需要验证的,offset加上size是不能够大于整个sum_size。如果大于的话,它肯定就会越界。在这个地方我们是可以通过一个underflow来绕过它的check,最终的效果是达到TEE heap上面的OOB Write。
根据前面的几个漏洞,我们也可以总结出来TrustZone钱包的风险,我们认为主要的风险就是在TrustZone里面的数据去处理外面的数据传过来的请求的时候,它有可能会有一些内存安全问题,有可能会导致拿到用户的mnemonic等。
下面部分就是,我们想要去设计一种解决方案。首先我们想要它能够达到同等的安全保证,然后我们希望它是开源的,可以经过开源社区大家共同的打磨。另外我们希望它能够保证内存安全,解决我们刚才说的硬件钱包和TrustZone钱包目前存在的内存安全问题。
三、密钥托管解决方案的设计和实现
首先这个设计我们是分成4个主体,左面这张图,除了我们的用户,还有最右面的TEE。TEE是用来存密钥的,中间Service Provider主要是用来做我们任务之间的同步还有管理。
在下面我们引入了一个比较重要的主体——多点认证的设备。这个设备主要是为了替代硬件钱包、TrustZone钱包它们交易,用户确认的环节。
我们认为这个方案更加的可扩展,它可以适配多种体系平台,并不需要和硬件绑定。另外也适用于多种场景,比如说TEE是由用户自己管理的,或者是由用户托管在云端的都可以。另外我们也希望它是一个开源项目,而且构建在一个开源的SDK上。
工作流程第一步是用户需要注册多点认证的设备给TEE,TEE它需要认识多点认证的设备。
第二步用户想要去发起一个交易,初始化一个交易的内容,然后把交易的内容传给TEE。
第三步用户在他的多点认证设备上去确认交易的内容。
然后第四步就是TEE去验证所有的,包括多点认证设备、确认的内容,还有用户最早发起的内容,这些是不是一致的,一致的话就会使用TrustZone里面的私钥对交易签名。
我们刚才提到这个方案在提供相同安全保证的同时,我们希望在实现的时候能够确保内存安全的。
下面就是介绍我们在实现时候的考虑,第一个我们是使用了Rust, Rust也是现在比较火的一个内存安全的系统级的语言,比如说Linux、安卓都在尝试使用Rust开发他们自己的项目。
然后Rust我们主要想强调它内存安全特性,我们举两个非常简单的例子,它和C代码的对比。
第一个例子就是我们刚才说的OOB,在C里面这个就会Segmentation fault。如果是Rust,我们就尝试使用类似的两个函数,一个是allocate,一个是use_buffer,我们也同样尝试进行同样的操作,它会有一个runtime的OOB的check,会有一个runtime panic。
然后第二个例子就是UAF,我们先用C写了一个例子,它没有什么报错。
对于Rust来说,它有一个核心的设计,就是它的所有权系统。我们在第一个use and free_buffer的时候,再把一个vector传到了这个函数里面,就相当于我们把buffer的所有权转移到了这个函数里面,然后在这个函数结束的时候,这个buffer就会自动的被释放,所以我们在第二次尝试去use buffer的时候, buffer已经不在了,所以直接会导致一个编译时的报错。
所以Rust语言比较重要的两个特性,第一个就是所有权,第二个就是边界检查,这两个特性都可以帮助我们去避免我们用C开发的时候的一些内存安全问题。
下一步就是选择我们实现的平台,我们选择是在TrustZone上面实现,并且我们使用开源的OP-TEE作为TrustZone OS,OP-TEE是目前使用最广泛的一个开源的TrustZone OS。但它有一个问题,它以前是用C编写的,然后同样可能会有内存破坏问题,开发的时候也不太方便,所以这就引出了我们的Rust SDK,我们一直有维护一个Rust TrustZone SDK,叫Teaclave TrustZone SDK ,目前也是由我们和OP-TEE来共同维护,它是给TrustZone可信应用开发者来使用的,就是我们用Rust去写OP-TEE TrustZone里面运行的可信应用的时候来使用。
Teaclave TrustZone SDK提供了一个开发TrustZone应用的能力,除了用在我们的Web3的场景里面,也可以识别出来其他的各种场景,比如说尝试把AI模型保护的一些方案,用TrustZone SDK去实现。(SDK链接:https://github.com/apache/incubator-teaclave-trustzone-sdk)
最后,我们在Teaclave TrustZone SDK里面开源了一个Web3场景的应用,然后也在逐步计划去开源更多的应用示例。 (链接:https://github.com/apache/incubator-teaclave-trustzone-sdk/tree/release- v0.3.0/projects/web3 )
大家如果有兴趣的话也可以关注,也欢迎大家可以提出一些建议或PR,都可以在Teaclave TrustZone SDK里面提,我们都会回复的。
四、总结
本次分享主要围绕密钥托管,先讲了它的背景,对比了几种密钥托管的方案,然后有实际漏洞的例子。我们尝试着去解决当前的密钥托管内存安全的问题,所以我们引入了Rust,并且使用了Teaclave TrustZone SDK,我们开源了一些Web3相关的示例程序。
PPT及回放视频
峰会议题PPT及回放视频已上传至【看雪课程】:https://www.kanxue.com/book-leaflet-195.htm
已购票的参会人员可免费获取,主办方已通过短信将“兑换码”发至手机,按提示兑换即可~