- A+
撰文:CertiK
去中心化区块链网络的主要核心之一治理,指的是「用户就去中心化协议的管理达成共识,或达成多数同意」。基于区块链的治理系统的独特功能为其带来了诸多优势,但也伴随着众多安全风险。
本文将主要为大家讲解关于区块链治理系统的一些常见安全问题,但在这之前,我们需要先了解一下它的具体功能。
区块链治理系统是一个基于规则的决策过程。它允许社区提出建议、讨论和实施更改及改进。在区块链世界中,有两种主要的治理类型:链层面治理和 dApp 层面的治理。
链层面治理
链层面治理旨在管理和实施对区块链本身的更改。在这种类型的治理中,进行更改的规则会嵌入在区块链协议中。开发人员通过代码更新提出更改,由每个节点投票决定是接受还是拒绝。
dApp 层面治理
应用程序层面(或是去中心化应用程序 dApp 层面)治理类似于链层面治理,但范围仅限于dApp 类的项目,处理时间通常要短得多。
![“前有狼后有虎”,去中心化的区块链治理的安全思路和考虑](https://www.luyuan.io/wp-content/uploads/2023/01/Fn6sMV31l6kdiyFsSfE3R6wtF-2y.png)
下方是一个 dApp 治理项目的简述。
在查看这一简述前,我们先了解一下主要术语:
abstain:弃权,是一种计入法定人数但不计入阈值的投票类型。在大多数协议中并不常见。
Deposit:存款,是一种旨在避免恶意用户破坏治理的机制。提议者(以及对提案感兴趣的社区用户)需要在存款期间向提案存入一定数量的 token。虽然某些提案可能需要检查用户的 token 余额,但这也验证了用户的意图——如果提案失败,用户的 token 将面临无法取回的风险。一旦提案筹集到确定数量的 token,提案就可以进入投票阶段。
Proposal:提案,是一个治理系统组件,旨在对系统进行更改并由用户投票。用户通过提交提案、存款和投票来与提案进行交互。每个用户都可以提交提案。提案可以是参数更改、代码升级更改、分发算法更改、新功能请求等。
Proposer:提议者,是提交提案的用户。提案人可以但非必要提供全额存款资金。
Quorum:法定人数,是在投票期结束时需要投票的 token 总数的百分比。
Tally:记录,是计算提案结果的过程。确定提案是否通过的等式是:Pass = Quorum && Threshold (&& Veto)。例如,对于法定人数=40% 且阈值=66.7% 的协议,提案的「通过」要求至少有 40% 的治理 token 持有者参与提案,并且他们中至少⅔的人投了赞成票。
Threshold:阈值,是提案通过则需要「for/yes」投票的参与 token 所占的百分比。
Veto:否决,是一种投票类型,当否决票的百分比超过特定阈值时,会导致提案被拒绝。可以看作是更强烈的「against/no」。在大多数协议中并不常见。在某些协议中,提案的「否决」结果可能会导致一些惩罚。
Vote:投票,是治理参与者(token 持有者)与提案互动的方式。每个参与者都可以在投票期间对提案进行投票。投票可以是「for/yes」和「against/no」,但是一些协议也有「abstain」和「no-with-veto」这样的选项。
![“前有狼后有虎”,去中心化的区块链治理的安全思路和考虑](https://www.luyuan.io/wp-content/uploads/2023/01/FjQAzz53N-iGIRxmL5C82Si7eTZF.png)
来源:Lucid
首先,任何持有治理 token 的用户都可以提交一个提案,该提案需要一个持续两天的审查期。在两天的时间内,创建者可以无代价取消提案。之后,该提案将进入为期三天的投票期。在投票期间,治理 token 持有用户可以根据其投票权对活跃提案投「for/yes」或「against/no」票。持有的治理 token 数量决定投票权及其投票的总权重。投票期结束后,治理系统将开始统计投票结果。系统将根据预先配置的法定人数和阈值得出「拒绝」或「通过」的结果。如果提案被拒绝,它将被取消,如果通过,则在 timelock 后由系统执行。timelock 有助于留出时间来通知用户即将发生的更改。
上图底部是一个带有存款步骤和「否决」投票选项的版本。这是一些链采用的稍微复杂的解决方案。存款期有点像审查期的延长版。每个提案都有最小所需存款的要求,包括提案人在内的任何用户都可以向提案存款。如果提案在两周的存款期限内没有收集到足够的存款,它将被取消。投票周期类似于上图中顶部位置的系统,但现在的投票期更长。这里的区别在于投票选项。第一种机制只有「or/yes」和「against/no」两个选项,现在增加了两个选项,分别是「abstain」和「no-with-veto」。
选择「abstain」即代表弃权,会被计入法定人数检查,但不计入阈值和否决检查。因此,在计票结束时,除了「拒绝」或「通过」以外,还会多出一个结果,称为「rejected with veto」,意味着投给「no-with-veto」的有效票数超过了否决检查。
如果这一提案的结果是「rejected with veto」,那么将会进行一定的惩罚,例如押金将可能被直接销毁,不予退还。
链上与链下治理
链上和链下治理的一个直观区别是去中心化程度。链下治理通常取决于开发或管理组织的决策。诚然,在区块链世界,或者说是在开源项目世界,链下治理也可以通过社区会议和公共代码审查变得更加透明。
透明度不等于去中心化。在很多情况下,数量更多但话语权较小的社区用户并没能积极有效地参与治理。
但是对区块链或应用层面项目的更改,并非由核心开发社区来评估利弊进行。相反,每个节点都可以对提议的更改进行投票,并可以探讨它们的优缺点——它是去中心化的,依靠社区来达成共识。
链下治理系统需要验证者之间花费时间和精力来达成共识;而由于基于规则的决策制定反馈循环,链上治理可以在相对较短的时间内就提议的变更达成共识。链下操作可能导致情况混乱,某些节点可以允许不同意且不运行提议的更改。算法投票机制相对较快,因为其实现的测试结果可以通过代码更新看到。
基于规则的决策可以自动化并加快变化速度,但它不能减少冲突。
例如,如果一群社区用户坚持必须修改分配算法以增加其 token 的流动性和供应,这可能会造成通货膨胀;而另一派坚持认为,流动性较低的货币带来的金融代价是抵御通货膨胀危害的必要条件。
在这些情况下,为了推进这个项目,需要有一个人或一个团体站出来推翻规则从而做出决定。当然,这与区块链的去中心化精神背道而驰。
尽管踏入链上治理仍然存在障碍,但与链下治理相比,链上治理的门槛通常较低。
针对几乎所有应用层项目的链上治理,唯一的门槛就是成为治理 token 的持有者。一些项目的治理 token可以购买或交易,而另一些项目只能通过参与项目获得。
大多数区块链项目不要求用户进行 KYC 认证,以便投票和参与治理。再加上社区池提供的一些投票奖励,或者一些项目的奖励分配,链上投票可以极大地激发用户的参与和积极性。
链层面与 dApp 层面
对于链层面治理,token 持有者的投票有时用于决定谁操作运行网络的验证节点(例如 EOS、Cosmos 等中的权益委托证明(DPoS)),有时用于对协议参数(例如以太坊 gas 限制)进行投票,有时用于投票并直接批量实现协议升级(例如 Tezos)。在所有这些情况下,执行都是自动的——协议本身包含更改验证器集或更新其自己的规则所需的所有逻辑,并根据投票结果自动执行。
dApp层面治理的理念源自链层面治理。同时,由于 dAppp 没有区块链那么复杂,因此提案涵盖了更多方面。
其中一些提案与链层面治理提案类似,如重新选举管理委员会或更新参数。有些提案并非设计为自动触发,比如:偿还采用新发布功能的费用、与其他项目建立合作伙伴关系,甚至建立合资项目等。
常见安全问题
缺乏防御闪电贷机制
部分项目的治理 token 可以被任何用户通过闪电贷借出。因此如果在投票时没有对持有时间进行限制,用户可以随时借出治理 token,创建或投票给恶意提案并执行提案。这一类型的典型案例是Beanstalk漏洞利用事件,稍后将深入探讨。
提案缺少审查期
为了简化流程,一些项目选择跳过审查或存款期,这意味着无论提案是否合法,所有提案都将直接进入投票期。这将增加用户对这些恶意提议投「拒绝」票的工作量,或者更会发生更糟糕的情况:恶意提案以某种方式通过并被执行。
配置错误
治理系统中的参数很敏感,需要谨慎设置。一些项目分配了不适当的值,这可能会导致攻击。例如,如果提案通过的门槛太低,攻击者就更容易控制提案的结果。如果恶意提案通过但是timelock/delay 时间太短,合法用户将没有足够的时间做出反应。他们无法在提案执行前及时解决这一恶意提案。
治理系统的错误实现
不正确的系统设计和实现也会导致协议出现严重问题。与传统 token 不同,治理 token 的核心功能是对提案进行投票。一些项目使用他们的项目 token 进行治理,这使得治理 token 可以像普通的 ERC 20 token 一样自由交易。这将可能导致下列严重问题:
- 同一地址重复投票
- 当投票权被取消时,投票不会被清除
- 取消委托调用不会删除委托的投票权
为了解决上述问题,一些项目要求用户在投票时将 token 转移到合约中,这将导致另一个常见问题:投票权不能重复用于不同的提案。在这种情况下,治理 token 在同一时期只能对一个提案进行投票。这里的问题是,如果在投票期间有多个提案,一些提案很难达到阈值,导致一些合法的提案被拒绝。
最后但同样重要的是,我们在某些系统中看到,投票提案在计票过程之后仍然可以更新或投票。这将扰乱系统的工作流程,并根据系统的实施情况产生不可预测的后果。例如,如果一项提案从未最终确定,则存款 token 将保留在合约中,何时应将其返还给存款人或销毁,具体取决于提案的投票结果。
案例分析:Beanstalk Finance
Beanstalk 是一个「去中心化的基于信用的稳定币协议」,于 2021 年上线。
Beanstalk 的主要目标是激励独立市场参与者以可持续的方式定期将1 Bean 的价格与美元挂钩。它的治理机制由两个不同的部分组成:BeanstalkDAO 和 Stalk 系统。
BeanstalkDAO 是协议的管理机构,对软件升级的执行提出建议和投票。要加入,用户必须存入任何列入白名单的资产。此外,还存在参与 Silo 以赚取被动收益的激励。
Stalk 系统是 Silo 的经济激励。当列入白名单的资产存入 Silo 时,Beanstalk 会用 Stalk 和 Seed 奖励存款人。Stalk 是允许用户参与 DAO 投票和投提案的治理 token。
每一季,Seed产量为新Stalk的 1/10000。Stalk 持有者有权参与 Beanstalk 治理并获得一部分 Bean 铸币。治理权和 Bean mints 的分配与每个 Stalk持有者的 Stalk 余额相对于未偿还的 Stalk 总量成正比。
准备阶段
为了发起这个特殊的攻击,攻击者为他们的账户注资,将 token 交换为 BEAN 并将其存入 Silo 以获得 Stalk,这使他们能够创建提案并为提案投票。然后他们在两个交易中创建了两个提案,Beanstalk 改进提案 18(BIP-18)和 19(BIP-19)。
BIP-18 最初是空白的,BIP-19 则包含一份经过验证的合约,提议向乌克兰钱包地址捐赠 25 万美元,并向提议者捐赠 1 万美元。该提案用于将资产转移给攻击者,并需要 24 小时才能进行调用emergencyCommit()。
攻击流程
1. 攻击者通过闪电贷借取了 3.5 亿枚 Dai、5 亿枚 USDC、1.5 亿枚 USDT、3200 万枚 Bean 和 1160 万枚 LUSD
2. 闪电贷资产转换为 795,425,740 枚 BEAN3Crv-f 和 58,924,887 枚 BEANLUSD-f:
a. 10 亿(约 3.5 亿 Dai、5 亿 USDC、1.5 亿 USDT)作为流动性被添加到 Curve.fi 池中,获得了 979,691,328 枚 DAI/USDC/USDT 3Crv token。
b. 将上述步骤中的 1500 万 3Crv 替换为 15,251,318 LUSD,将剩余 Crv兑换为95,425,740 BEAN3Crv-f。
c. 添加 32,100,950BEAB 和 26,894,383LUSD 作为流动性,并获得 58,924,887 BEANLUSD-f 作为回报。
3. 攻击者将从闪电贷中获得的所有资产存入 Diamond 合约,并投票支持 BIP-18 提案。
4. 立即调用emergencyCommit()来执行 BIP-18 提案。
5. 在步骤 3 和步骤 4 之后,攻击者能够耗尽 36,084,584 枚BEAN、0.54 UNIV2(BEAN-WETH)、874,663,982 枚BEAN3Crv 和60,562,844 枚 BEANLUSD-f。
6. 攻击者使用耗尽的资产(在步骤 5 中)偿还闪电贷款并获得剩余的利润:
a. 874,663,982 枚 BEAN3Crv 因 1,007,734,729枚3Crv 而从流动性中移除
b. 60,562,844枚BEANLUSD-f 从流动性中移除,换取 28,149,504枚LUSD
c. 返还 11,678,100枚LUSD 和 32,197,543枚BEAN 到相应的矿池
d. 16,471,404枚LUSD 兑换成 16,184,690枚3Crv
e. 销毁所有 3Crv 以获得 522,487,380枚USDC、365,758,059枚DAI 和 156,732,232枚USDT
f. 偿还 350,315,000 枚 DAI、500,450,000 枚 USDC 和 150,135,000USDT 到相应的资金池
g. 0.54枚UNIV2(BEAN-WETH) 从流动性中移除,获得 10,883枚WETH 和 32,511,085枚BEAN
h. 250,000枚USDC 被转移到乌克兰数字货币捐赠
i. 15,443,059枚DAI 兑换成 11,822枚WETH,37,228,637枚USDC 兑换成 2,124枚WETH
j. 最后,24,830枚WETH 被转移给了攻击者。
但是攻击者如何使协议将 token 转移给自己呢?要回答这个问题,我们需要深入研究一下emergencyCommit() 函数。
emergencyCommit()
通常情况下,一旦 BIP 被提出,它需要至少 7 天的投票时间才能在链上执行。
这被认为是一种伪时间锁定机制,以允许适当的时间来验证提案的安全性。然而,emergencyCommit()函数允许在等待 1 天而不是 7 天后立即在链上执行提案,emergencyCommit()的阈值为⅔。
当达到阈值时,该emergencyCommit()函数允许人们「执行指定的 BIP、创建与 BIP 相关的 diamond cut、暂停 BIP、并以未经证明的奖励奖励提议者」。
![“前有狼后有虎”,去中心化的区块链治理的安全思路和考虑](https://www.luyuan.io/wp-content/uploads/2023/01/FttvnOxJpM2tyFyYnWOOoqg9qCmu.png)
执行emergencyCommit()的提议者创建了一个diamond cut,并可以委托给一个地址,该地址将被_init()执行并执行其逻辑。这允许提议者执行他们想要的任何代码。
![“前有狼后有虎”,去中心化的区块链治理的安全思路和考虑](https://www.luyuan.io/wp-content/uploads/2023/01/Fks1WjzzQ3as3q482tFWAP80rAi-.png)
提案通过后,攻击者创建了另一个合约,其中包含将 Silo 存放的白名单资产转移给自己的代码。
由于 Diamond在合约上执行_init()(在上图的cutBip()函数中),底层代码通过_calldata执行其函数,攻击者能够取出价值大约 7600 万美元的 token。
漏洞分析
有两个问题为漏洞利用打开了大门。第一个是 Silo 系统中的 BEAN3Crv-f 和 BEANLUSD-f(用于投票)可以被闪电贷。
由于 Beanstalk 协议中缺少防御闪电贷机制,攻击者可以借用协议支持的大量 token,并对恶意提案进行投票。
第二个问题是emergencyCommit()函数过于强大。如上所述,当提案通过时,治理系统允许提案人为所欲为,而无需任何形式的验证。该emergencyCommit()功能允许提案立即执行,导致没有留下任何时间来检查提案的有效性。
接下来的两个事件并没有直接利用治理系统中的漏洞,但治理系统在利用中仍起到了「关键」作用。
其他治理漏洞案例
Audius
Audius 治理合约利用 OpenZeppelin proxy upgradability pattern,并重写 AudiusAdminUpgradabilityProxy 合约中的标准实现。
在其实现中,AudiusAdminUpgradebilityProxy 使用 slot 0 作为 proxyAdmin 的地址。Audius 协议的 proxyAdmin 设置为治理系统地址 0x4deca517d6817b6510798b7328f2314d3003abac。
这导致 proxyAdmin 地址中的最后两个字节与 OpenZeppelin 的 Initializable 合约中的两个布尔状态变量发生冲突。也就是说,最后两个字节和两个布尔值「initialized」和「initializing」都存储在 slot 0 中(第一个和第二个字节)。鉴于 proxyAdmin 地址的最后一个字节是 0xac,由于冲突,initialized 被赋予了 true。同样,因为 proxyAdmin 地址的第二个字节是 0xab,所以 `initializing`也被赋予了 true。这导致了 initializer() 总是返回 true:
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
攻击者能够调用已部署的 Audius 合约的初始化方法,这些合约实现了 Initializable 并更改了本应在初始化中仅设置一次的存储状态。攻击者随后提交恶意提案并从合约中窃取 1800 万枚 AUDIO,当时价值 705 枚 ETH。
Fortress协议
Fortress 是一种具有链上治理系统的借贷协议。治理合约可以执行修改借贷相关配置的成功提案(即添加抵押品及其对应的抵押品因素)。但是,要成功执行提案,投票所需的最低 FTS 数量为400,000。
由于 FTS 代币的价格较低,攻击者在攻击发生时只需要用约 11 枚 ETH 兑换超过 400,000 枚 FTS。攻击者使用超过 400,000 个 FTS 创建恶意提案并将其成功执行。
另一个问题是链合约的「submit」功能存在允许任何人更新价格。
攻击者在这里所做的是将这两个问题联系在一起。他们先是借出大量的 ETH,购买 FTS 用于投票和抵押,然后提交了一份恶意提案,并对其进行了投票,该提案因门槛较低而改变了抵押因素。
随后在预言机中更新了 FTS 的价格,并从合约中借入大量其他 token,获利约 300 万美元。
写在最后
自被引入区块链系统以来,治理系统发生了巨大的变化,但安全挑战仍然存在。
作为高获利的攻击渠道,治理系统是攻击者的主要目标之一,因此项目在开发过程中应格外注意其安全性。
CertiK 建议智能合约开发人员审查并采用成熟的开源治理框架,以「避免 reinventing the wheel」(指代避免重复创造一个已经存在的基本方法,要充分利用已有的经验和成果,避免不必要的投入和浪费)。
- 我的微信
- 这是我的微信扫一扫
-
- 我的电报
- 这是我的电报扫一扫
-