EGD_finance Reentry

背景

2022年08月07日 BSC链上智能合约EGD_finance受到重入攻击被黑,攻击者操纵了类似于质押奖励价格从而获取了高额奖励。

EGD_finance contract address: 0x93c175439726797dcee24d08e4ac9164e88e7aee

攻击TX: 0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3

分析

问题出现在EGD_finance合约中的claimAllReword()函数中,函数实现如下:可以看到合约会根据用户质押的代币数量:userInfo[msg.sender].userStakeList 质押时间:block.timestamp - info.claimTime 以及一个比例量 info.rates 来计算用户可以获得的代币奖励:

function claimAllReward() external {
        require(userInfo[msg.sender].userStakeList.length > 0, 'no stake');
        require(!black[msg.sender],'black');
        uint[] storage list = userInfo[msg.sender].userStakeList;
        uint rew;
        uint outAmount;
        uint range = list.length;
        for (uint i = 0; i < range; i++) {
            UserSlot storage info = userSlot[msg.sender][list[i - outAmount]];
            require(info.totalQuota != 0, 'wrong index');
            uint quota = (block.timestamp - info.claimTime) * info.rates;
            if (quota >= info.leftQuota) {
                quota = info.leftQuota;
            }
            rew += quota * 1e18 / getEGDPrice(); // 这一行是计算奖励的核心
            info.claimTime = block.timestamp;
            info.leftQuota -= quota;
            info.claimedQuota += quota;
            if (info.leftQuota == 0) {
                userInfo[msg.sender].totalAmount -= info.totalQuota;
                delete userSlot[msg.sender][list[i - outAmount]];
                list[i - outAmount] = list[list.length - 1];
                list.pop();
                outAmount ++;
            }
        }
        userInfo[msg.sender].userStakeList = list;
        EGD.transfer(msg.sender, rew);
        userInfo[msg.sender].totalClaimed += rew;
        emit Claim(msg.sender,rew);
    }
}

具体的计算公式为:

rew += quota * 1e18 / getEGDPrice();

也就是说,getEGDPrice()所获得的值越小,用户获得的奖励数量rew就越大。所以就可以看下getEGDPrice()的实现:这里的实现非常简单,仅仅是计算了EGD-USD这个交易对pair中存储的EGD和USD的数量并作除法。所以要想减小getEGDPrice()的值,有两个办法:

  1. 增加池子中EGD token的数量

  2. 减少池子中USD token的数量

function getEGDPrice() public view returns (uint){
        uint balance1 = EGD.balanceOf(pair);
        uint balance2 = U.balanceOf(pair);
        return (balance2 * 1e18 / balance1);
    }

而我们又知道,EGD-USD这个合约对是一个pancakeswap pair因此,他是继承有闪电贷swap函数并具有闪电贷功能的,具体实现可以见pancakeswap合约:

PancakePair.sol
PancakePair.sol

所以 如果我们通过闪电贷的方法从池子中借出usd token并在调用合约中实现pancakeCall方法并立马执行EGD_finance的claimAllreward方法获取奖励,这时候由于EGD-USD pair中被借出了大量的USD导致getPrice获取的价格被操纵。

通过这种方式,攻击者可以获取远超预期的EGD token奖励,之后再归还闪电贷使getPrice的价格恢复即可。

整体流程为:

1.Attack合约调用 EGD_USD pair合约,借出大量USD,并触发回调pancakeCall

2.getprice()由于USD数量减少而减小

3.在pancakeCall中调用EGD_finance合约的claimAllReward获取奖励

4.在pancakeCall中归还EGD_USD pair合约中借出的USD以及利息(0.3%)

5.getprice()价格回归正常

6.攻击者卖出EGD token获利

可以参考:

https://explorer.phalcon.xyz/tx/bsc/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3

post image