“前有狼后有虎”,去中心化的区块链治理的安全思路和考虑
撰文:CertiK
去中心化区块链网络的主要核心之一 治理 ,指的是「用户就去中心化协议的管理达成共识,或达成多数同意」。基于区块链的治理系统的独特功能为其带来了诸多优势,但也伴随着 众多安全风险 。
本文将主要为大家讲解关于区块链治理系统的一些 常见安全问题 ,但在这之前,我们需要先了解一下它的具体功能。
区块链治理系统是一个 基于规则的决策过程 。它允许社区提出建议、讨论和实施更改及改进。在区块链世界中,有两种主要的治理类型: 链层面治理和 dApp 层面的治理。
链层面治理
链层面治理旨在管理和实施对区块链本身的更改。在这种类型的治理中,进行更改的规则会 嵌入在区块链协议 中。开发人员通过代码更新提出更改,由每个节点投票决定是接受还是拒绝。
dApp 层面治理
应用程序层面(或是去中心化应用程序 dApp 层面)治理类似于链层面治理,但范围仅限于 dApp 类的项目 ,处理时间通常要短得多。
下方是一个 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」这样的选项。
来源: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、并以未经证明的奖励奖励提议者」。
执行emergencyCommit()的提议者创建了一个diamond cut,并可以委托给一个地址,该地址将被_init()执行并执行其逻辑。这允许提议者执行他们想要的任何代码。
提案通过后,攻击者创建了另一个合约,其中包含将 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 设置为治理系统地址
这导致 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」(指代避免重复创造一个已经存在的基本方法,要充分利用已有的经验和成果,避免不必要的投入和浪费)。
Bitcoin Price Consolidates Below Resistance, Are Dips Still Supported?
Bitcoin Price Consolidates Below Resistance, Are Dips Still Supported?
XRP, Solana, Cardano, Shiba Inu Making Up for Lost Time as Big Whale Transaction Spikes Pop Up
XRP, Solana, Cardano, Shiba Inu Making Up for Lost Time as Big Whale Transaction Spikes Pop Up
Justin Sun suspected to have purchased $160m in Ethereum
Justin Sun suspected to have purchased $160m in Ethereum