区块链安全

Pancake-SyrupBar 双倍 votes 漏洞

@snowming 2022/04/14

漏洞描述

这个漏洞是审计过程中独立发现的没有被披露过 poc 的知名项目 PanCake 的漏洞,文中附有 poc。但是后文有说原因为什么披露出来问题不大,仅仅作为交流学习用。

在审计工作中,发现了 Pancake 的 SyrupBar 合约中, burn() 方法中的 _moveDelegates 方向反了:

会导致,user 只要在 masterchef 中通过 enterStaking 和 leaveStaking 一进一出,就能以很小的攻击成本(仅仅是gas)双倍放大他们对任何所需代表或提案的投票。

但是这里有一点需要绕过,就是本身如果是通过 mint 方法进而调用 _moveDelegates() 方法给用户加 votes。我们来看看这个过程是怎么样的:

  • masterchef.enterStaking(_amount)
  • -> syrup.mint(msg.sender,_amount)
  • – -> _mint(msg.sender, amount)
  • – -> moveDelegates(0, _delegates[msg.sender],_amount)

注意最后一步里面的 _delegates[msg.sender]。
因为这个 msg.sender ,本身没有被存储对应的 key-value 对进去这个 _delegates mapping。所以这样查出来 _delegates[msg.sender] 会等于0!也就是 address(0)。
那怎么去绕过呢?刚好有个外部可以调用的函数可以更新这个 mapping。

所以 msg.sender 先调用这个函数,delegate(msg.sener),而 _delegate 这个函数实际上做的事情就是更新 delegates 这个 mapping,将原来的 address(0) 的 votes 转给新传入的 delegatee 这里就是 msg.sender 自己。所以最终查出 _delegates[msg.sender] = msg.sender,见 1134 行。

此时就会怎么样,如果 msg.sender 去调用 masterchef.enterStaking(_amount),进而调用_mint(tx.origin, amount), 最终会导致 moveDelegates(0, tx.origin,_amount)。

leaveStaking 也是同理,一次 delegate(self),这两个_moveDelegates(0,tx.origin,_amount)都可以用了。

测试过程

使用 truffle+remix 进行测试:

Truffle fork 主网,模拟这笔交易:https://bscscan.com/tx/0x3737c977c472d84e5ec8149bc94445d41212ecb9d577be0616d85de563cc0d71,所以模拟账户 0xd31ea11b1614a2399129530efae0d18230ec95c0。

ganache-cli –fork https://speedy-nodes-nyc.moralis.io/{key}/bsc/mainnet/archive@16927703 –wallet.unlockedAccounts 0xd31ea11b1614a2399129530efae0d18230ec95c0

remix将两个合约映射到对应地址上:

然后接下来按照以下步骤测试:

STEP 1:来看一下攻击开始前的 attacker 投票权:调用 SyrupBar 合约的 getCurrentVotes(0xd31ea11b1614a2399129530efae0d18230ec95c0) 函数。当前投票权为0。

STEP 2: 0xd31ea11b1614a2399129530efae0d18230ec95c0 调用 syrupbar.delegate(0xd31ea11b1614a2399129530efae0d18230ec95c0) 函数更新 mapping。

STEP 3: 0xd31ea11b1614a2399129530efae0d18230ec95c0 调用 masterchef.enterStaking,参数为参考交易的参数 3585474188500000。

STEP4: 调用 SyrupBar 合约的 getCurrentVotes(0xd31ea11b1614a2399129530efae0d18230ec95c0) 函数。查看mint之后的投票权。此时是503585474188500000。

这个数字需要解释下,这个地址本身的  SyrupBar Token (SYRUP) 这个币的余额是 500000000000000000。
所以实际上通过 enterStaking 获得的奖励是 3585474188500000。

STEP5:0xd31ea11b1614a2399129530efae0d18230ec95c0 调用 msaterchef.leaveStaking,参数为复现交易的参数 3585474188500000。

STEP6: 调用 SyrupBar 合约的 getCurrentVotes(0xd31ea11b1614a2399129530efae0d18230ec95c0) 函数。查看burn之后的投票权。此时是507170948377000000,获得的奖励是:7170948377000000

很明显看到 burn 之后获得的 votes 是 mint 之后的两倍。理应burn之后没有投票权的。所以足以说明存在漏洞。

漏洞后记

本身这个漏洞是我们团队在审计过程中独立发现的,但是呢,发现这个合约其实已经被废弃了:(https://docs.pancakeswap.finance/help/faq

所以虽然这个合约看上去还在链上运行,但是应该已经不会造成啥危害了。

外加后面发现 Certik 也发现过这个问题,所以发出来当作怡情了。咱就说:咱们团队还是很不错的:)