跳转到主要内容
此示例将简要展示如何使用 Tenderly 分析甚至预防像 Cover Protocol 所发生的黑客攻击。 Cover Protocol 遭到黑客攻击是因为代码无法更新存储中的缓存。这本可以通过在存款/提款等重要函数和特定状态变化上设置警报来预防攻击。 为了手动分析此黑客攻击,我们需要导入智能合约 Blacksmith,漏洞就在其中被发现。如果您知道受影响协议或易受攻击合约的确切名称,也可以直接搜索它。
注意——您只能搜索已在 Tenderly 或 Etherscan 上公开验证的合约。请在此处阅读更多关于如何在 Tenderly 上验证合约的信息。
如果合约未公开验证,您可以自行验证以便能够在 Tenderly 中使用它。如果项目是开源的(如 Cover),您可以转到该项目的 GitHub 仓库,找到您要使用的合约(及其源代码)并自行验证。 现在我们有了要导入到 Tenderly 的合约,转到您的 Dashboard 并点击左侧的 Contracts 选项卡,然后点击右上角的 Add Contract 按钮: 您可以通过 Dashboard 上传并验证合约的代码,或通过 CLI [更多信息在此] 现在,在导入并验证合约后,让我们分析黑客攻击本身。我们知道漏洞存在于 deposit 函数的缓存中,所以我们模拟该函数。如下面的截图所示,执行后它失败,报错为 Blacksmith: pool does not exist 现在,让我们在 Tenderly Debugger 中打开这个错误。在第 118 行,我们看到 deposit 函数中的 Pool memory pool = pools[_lpToken];,数据在此被缓存,然后在第 125 行更新,我们看到 _claimCoverRewards(pool, miner); 合约使用了缓存数据,因为数据本身留在内存中;这就是为什么后来合约使用这些数据计算函数。缓存没有更新导致了这次黑客攻击:
example.sol
miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);
example.sol
miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);
example.sol
function deposit(address _lpToken, uint256 _amount) external override {
    require(block.timestamp >= START_TIME, "Blacksmith: not started");
    require(_amount > 0, "Blacksmith: amount is 0");

    Pool memory pool = pools[_lpToken];

    require(pool.lastUpdatedAt > 0, "Blacksmith: pool does not exists");
    require(IERC20(_lpToken).balanceOf(msg.sender) >= _amount, "Blacksmith: insufficient balance");

    updatePool(_lpToken);

    Miner storage miner = miners[_lpToken][msg.sender];
    BonusToken memory bonusToken = bonusTokens[_lpToken];

    _claimCoverRewards(pool, miner);
    _claimBonus(bonusToken, miner);

    miner.amount = miner.amount.add(_amount);

    // update writeoff to match current acc rewards/bonus per token
    miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);
    miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);

    IERC20(_lpToken).safeTransferFrom(msg.sender, address(this), _amount);
    emit Deposit(msg.sender, _lpToken, _amount);
}

  • 在攻击发生前几个小时,一个新池被批准用于流动性挖矿。这个池完全正常,但由于它是新的,Blacksmith 合约中没有此池的任何 LP 代币。
  • 攻击者将此池的一些代币存入了 Blacksmith 合约。
  • Blacksmith 合约按每代币基础跟踪奖励。如果锁定了大量代币,每代币的奖励将很小。如果锁定的代币很少,每代币的奖励将很大。相关变量称为 accRewardsPerToken,计算为 totalPoolRewards / totalTokenBalance
  • 然后攻击者从 Blacksmith 合约中提取了几乎所有的 LP 代币,将 totalTokenBalance 数量减少到几乎为零。
  • 然后攻击者再次将此池的一些代币存入 Blacksmith 合约。这就是漏洞暴露出真面目的地方。由于 totalTokenBalance 在前一交易中被大幅减少,新计算的 accRewardsPerToken 飙升。合约使用 rewardWriteoff 来控制 accRewardsPerToken 的影响。然而,由于漏洞,在计算 rewardWriteoff 值时使用了 accRewardsPerToken 的旧(小)值。因此,accRewardsPerToken 的大值没有被控制。
  • 然后攻击者提取了他们的奖励。由于 accRewardsPerToken 中存在一个大的、未被控制的值,从系统支付的总奖励被抬高,合约最终铸造了 40,796,131,214,802,500,000 枚 COVER 代币
感谢 Mudit Gupta 对此次黑客攻击最后部分的分析——在他的博客上阅读更多信息 :)