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()的值,有两个办法:
增加池子中EGD token的数量
减少池子中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合约:

所以 如果我们通过闪电贷的方法从池子中借出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获利
可以参考:

