零时科技 || Equilibria 攻击事件分析

我们监控到 Ethereum 上针对 Equilibria 的攻击事件,本次攻击共造成约 63k USD 的损失。

登链封面(事件).jpg

<!--StartFragment-->

背景介绍

2025年8⽉23⽇,我们监控到 Ethereum 上针对 Equilibria 的攻击事件

https\://etherscan.io/tx/0x185a16017fb4d9b2fefdf5935435253d53d4758238275426b507fe54eb4fe97a

攻击共造成约 63k USD 的损失。

<!--EndFragment-->

<!--StartFragment-->

攻击及事件分析

⾸先,攻击者攻击者创建了⼀个攻击合约,转入 0.1 ETH 后通过 Deposit 获取了 0.1 WETH 。

<!--EndFragment-->

1.png <!--StartFragment-->

然后,攻击者使⽤了 0.1 WETH 兑换了 7.9 PENDLE ⽤于后续的攻击。

<!--EndFragment-->

2.png

<!--StartFragment-->

接着,攻击者通过 deposit 和 harvest 来将 PENDLE 存入 Equilibria 和获取收益再投资,⽬的是获取更多的 ePendle 。

<!--EndFragment-->

3.png <!--StartFragment-->

接着,攻击者⼜利⽤ flashloan 借了 17029 ePendle ,便开始真正的攻击流程。

<!--EndFragment-->

4.png

<!--StartFragment-->

在攻击者真正的攻击流程中,⾸先通过 depositAll 来存入从 flashloan 借来的 ePendle 和之前使⽤ 0.1 ETH 兑换的 ePendle 获得了 stake-ePendle 。随后创建⼀个新的合约,将 stake-ePendle 转给新合约,利⽤ getReward 获取奖励。

<!--EndFragment-->

5.png

<!--StartFragment-->

我们⾸先看⼀下 depositAll 函数:

<!--EndFragment-->

function depositAll() external returns (uint256) {
    return deposit(ependle.balanceOf(msg.sender));
}

<!--StartFragment-->

发现函数其实调⽤了 deposit ,那我们看⼀下 deposit 的具体实现:

<!--EndFragment-->

function deposit(
    uint256 _amount
)
    public
    nonReentrant
    updateReward(msg.sender, userHarvest)
    returns (uint256)
{
    require(
        _amount > 0,
        "VaultEPendle deposit: amount must be greater than zero"
    );

    uint256 balanceBefore = balance();
    ependle.safeTransferFrom(msg.sender, address(this), _amount);
    uint256 shares = 0;

    if (totalSupply() == 0) {
        shares = _amount
    } else {
        shares = (_amount * totalSupply()) / balanceBefore;
    }

    _mint(msg.sender, shares);
    ePendleRewardPool.stake(_amount);
    emit Deposited(msg.sender, _amount);

    return shares;
}

<!--StartFragment-->

可以看出,这个 deposit 逻辑比较简单,通过 modifier 函数 updateReward 在 deposit 时更新⽤户的 reward ,最后再 stake ,接下来我们看⼀下 updateReward 的具体实现:

<!--EndFragment-->

modifier updateReward(address _account, bool needHarvest) {
    if (needHarvest) {
        harvest();
    }
    for (uint256 i = 0; i &lt; rewardTokens.length; i++) {
        address rewardToken = rewardTokens[i];
        UserReward storage userReward = userRewards[_account][rewardToken];
        userReward.rewards = earned(_account, rewardToken);
        userReward.userRewardPerTokenPaid = rewards[rewardToken]
        .rewardPerTokenStored;
    }
    _;
} 
function earned(
    address _account,
    address _rewardToken
) public view returns (uint256) {
    Reward memory reward = rewards[_rewardToken];
    UserReward memory userReward = userRewards[_account][_rewardToken];
    return
        ((balanceOf(_account) *
            (reward.rewardPerTokenStored -
                userReward.userRewardPerTokenPaid)) / 1e18) +
        userReward.rewards;
}

<!--StartFragment-->

问题就出现在 earned 函数中,在该函数中,计算⽤户的 reward 时,参数包含了⽤户 stake-ePendle 的 balance 。所以,⽤户可以通过 flashloan 获取⼤量的 ePendle token 后,通过 stake 获取 stake-ePendle ,再 transfer 给新的地址来获取重复的 stake 收益。

<!--EndFragment-->

6.png

<!--StartFragment-->

攻击者通过反复进⾏ transfer stake-ePendle 到新地址,然后再 getReward 获取收益,最终获利 63k USD 。

<!--EndFragment-->

<!--StartFragment-->

总结

本次漏洞的成因是函数 updateReward 函数在计算 Reward 时, Reward 的值和⽤户的 stake-ePendle 的 token 余额有关,且stake-ePendle 可以通过 transfer 到另外⼀个地址。最终,导致攻击者通过反复进⾏ transfer stake-ePendle 到新的合约地址再 getReward 获取奖励,循环操作提取了项⽬所有资⾦。建议项⽬⽅在设计经济模型和代码运⾏逻辑时要多⽅验证,合约上线前审计时尽量选择多个审计公司交叉审计。

<!--EndFragment-->

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
零时科技
零时科技
0xbD0b...A354
专注区块链生态安全