43

MakerDAO治理合约升级背后的安全风波

 5 years ago
source link: https://www.8btc.com/article/406815?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

北京时间 2019年05月07日,区块链安全公司 Zeppelin 对以太坊上的 DeFi 明星项目 MakerDAO 发出安全预警,宣称其治理合约存在安全漏洞,希望已锁仓参与投票的用户尽快解锁 MKR 提并出。MakerDAO 的开发者 Maker 公司亦确认了漏洞存在,并上线了新的治理合约,并宣称漏洞已修复。

该安全威胁曝出后,PeckShield 全程追踪了 MKR 代币的转移情况,并多次向社区发出预警,呼吁 MKR 代币持有者立即转移旧合约的 MKR 代币。截止目前,绝大多数的 MKR 代币已经完成了转移,旧治理合约中尚有 2,463 个 MKR 代币(价值约 128万美元)待转移。

05月07日当天,经 PeckShield 独立研究发现,确认了该漏洞的存在(我们命名为 itchy DAO),具体而言:由于该治理合约实现的投票机制(vote(bytes32))存在某种缺陷,允许投票给尚不存在的 slate(但包含有正在投票的提案)。 等用户投票后,攻击者可以恶意调用 free()退出,达到减掉有效提案的合法票数,并同时锁死投票人的 MKR 代币。

次日05月08日,PeckShield 紧急和 Maker 公司同步了漏洞细节,05月10日凌晨,MakerDAO 公开了新版合约。Zeppelin 和 PeckShield 也各自独立完成了对其新合约的审计,确定新版本修复了该漏洞。

BZNjMvF.jpg!web

在此我们公布漏洞细节与攻击手法,也希望有引用此第三方库合约的其它 DApp 能尽快修复。

细节

在 MakerDAO 的设计里,用户是可以通过投票来参与其治理机制,详情可参照 DAO 的 FAQ。

以下是关于 itchy DAO 的细节,用户可以通过 lock / free 来将手上的 MKR 锁定并投票或是取消投票:

uUfEZjB.jpg!web

在 lock 锁定 MKR 之后,可以对一个或多个提案 (address 数组) 进行投票:

fEJNjif.jpg!web

注意到这里有两个 vote 函数,两者的传参不一样 (address 数组与 byte32),

而 vote(address[] yays) 最终亦会调用 vote(bytes32 slate),其大致逻辑如下图所示:

EFbyIvr.jpg!web

简单来说,两个 vote 殊途同归,最后调用 addWeight 将锁住的票投入对应提案:

6Vfu6zn.jpg!web

可惜的是,由于合约设计上失误,让攻击者有机会透过一系列动作,来恶意操控投票结果,甚致让锁定的 MKR 无法取出。

这里我们假设有一个从未投过票的黑客打算开始攻击:

  • 调用 lock() 锁仓 MKR,此时 deposits[msg.sender] 会存入锁住的额度。
  • 此时黑客可以线下预先算好要攻击的提案并预先计算好哈希值,拿来做为步骤 3 的传参,因为 slate 其实只是 address 数组的 sha3。 这里要注意挑选的攻击目标组合必须还不存在于 slates[] 中 (否则攻击便会失败),黑客亦可以自己提出一个新提案来加入组合计算, 如此便可以确定这个组合必定不存在。 NzquumV.jpg!web
  • 调用 vote(bytes32 slate),因为 slate 其实只是 address 数组的 sha3,黑客可以线下预先算好要攻击的提案后传入。这时因为 votes[msg.sender] 还未赋值,所以 subWeight() 会直接返回。接下来黑客传入的 sha3(slate) 会存入 votes[msg.sender], 之后调用 addWeight()。从上方的代码我们可以看到,addWeight() 是透过 slates[slate] 取得提案数组,此时 slates[slate] 获取到的 一样是未赋值的初始数组,所以 for 循环不会执行(由于 yays.length = 0)

    iYniuu2.jpg!web

  • 调用 etch() 将目标提案数组传入。注意 etch() 与两个 vote() 函数都是 public,所以外部可以随意调用。这时 slates[hash] 就会存入对应的提案数组。
  • 调用 free() 解除锁仓。这时会分成以下两步:
  • deposits[msg.sender] = sub(deposits[msg.sender], wad)解锁黑客在 1. 的锁仓
  • subWeight(wad, votes[msg.sender])从对应提案中扣掉黑客的票数,然而从头到尾其实攻击者都没有真正为它们投过票

从上面的分析我们了解,黑客能透过这种攻击造成以下可能影响:

一、恶意操控投票结果

二、因为黑客预先扣掉部份票数,导致真正的投票者有可能无法解除锁仓

时间轴

ja6bMfZ.png!web

PeckShield(派盾科技)是面向全球顶尖的区块链数据与安全服务提供商。商业与媒体合作(包括智能合约审计需求), 请通过 Telegram、Twitter 或邮件 ([email protected])与我们联系。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK