LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 新闻观点 > DeFi Saver 用户 31 万 DAI 是如何被盗的?慢雾技术拆解攻击细节

DeFi Saver 用户 31 万 DAI 是如何被盗的?慢雾技术拆解攻击细节

2020-10-10 慢雾科技 来源:火星财经
用户 31 万 DAI 被盗与 DeFi Saver 今年 6 月公布的漏洞有关,此漏洞还将影响其它向 SaverExchange 授权的合约代币。

用户31万DAI被盗与DeFiSaver今年6月公布的漏洞有关,此漏洞还将影响其它向SaverExchange授权的合约代币。

原文标题:《慢雾:DeFiSaver用户的31万枚DAI是如何被盗的?》
撰文:Kong,就职于慢雾安全团队

2020年10月8号,去中心化钱包imToken发推表示,用户报告称31万枚DAI被盗,这与DeFiSaverExchange漏洞有关。DeFiSaver对此回应称,被盗资金仍旧安全,正在联系受害用户。截至目前,资金已全部归还受害用户。早在今年6月份DeFiSaver就表示该团队发现DeFiSaver应用系列中自有交易平台的一个漏洞,此次31万枚DAI被盗也与此前的SaverExchange合约漏洞有关。慢雾安全团队在收到情报后,针对此次31万枚DAI被盗事件展开具体的分析。

攻击过程分析

查看这笔攻击交易

其中可以看到被盗用户0xc0直接转出31万枚DAI到攻击合约0x5b。

我们可以使用OKO浏览器查看具体的交易细节:

从中可以看出攻击者通过调用swapTokenToToken函数传入_exchangeAddress,_hide,_dest为DAI合约地址,选择_exchangeType为4,并传入自定的_callData。可以猜测这是攻击成功的关键函数,接下来对其进行具体的分析:

functionswapTokenToToken(address_src,address_dest,uint_amount,uint_minPrice,uint_exchangeType,address_exchangeAddress,bytesmemory_callData,uint_0xPrice)publicpayable{//usethistoavoidstacktoodeeperroraddress[3]memoryorderAddresses=[_exchangeAddress,_src,_dest];if(orderAddresses[1]==KYBER_ETH_ADDRESS){require(msg.value>=_amount,"msg.valuesmallerthanamount");}else{require(ERC20(orderAddresses[1]).transferFrom(msg.sender,address(this),_amount),"Notabletowithdrawwantedamount");}uintfee=takeFee(_amount,orderAddresses[1]);_amount=sub(_amount,fee);//[tokensReturned,tokensLeft]uint[2]memorytokens;addresswrapper;uintprice;boolsuccess;//atthebegginingtokensLeftequals_amounttokens[1]=_amount;if(_exchangeType==4){if(orderAddresses[1]!=KYBER_ETH_ADDRESS){ERC20(orderAddresses[1]).approve(address(ERC20_PROXY_0X),_amount);}(success,tokens[0],)=takeOrder(orderAddresses,_callData,address(this).balance,_amount);//eitheritrevertsororderdoesn'texistanymore,werevertsasitwasexplicitelyaskedforthisexchangerequire(success&&tokens[0]>0,"0xtransactionfailed");wrapper=address(_exchangeAddress);}if(tokens[0]==0){(wrapper,price)=getBestPrice(_amount,orderAddresses[1],orderAddresses[2],_exchangeType);require(price>_minPrice||_0xPrice>_minPrice,"Slippagehit");//handle0xexchange,ifequalprice,try0xtouselessgasif(_0xPrice>=price){if(orderAddresses[1]!=KYBER_ETH_ADDRESS){ERC20(orderAddresses[1]).approve(address(ERC20_PROXY_0X),_amount);}(success,tokens[0],tokens[1])=takeOrder(orderAddresses,_callData,address(this).balance,_amount);//eitheritrevertsororderdoesn'texistanymoreif(success&&tokens[0]>0){wrapper=address(_exchangeAddress);emitSwap(orderAddresses[1],orderAddresses[2],_amount,tokens[0],wrapper);}}if(tokens[1]>0){//incase0xswappedjustsomeamountoftokensandreturnedeverythingelseif(tokens[1]!=_amount){(wrapper,price)=getBestPrice(tokens[1],orderAddresses[1],orderAddresses[2],_exchangeType);}//incase0xfailed,priceonotherexchangesstillneedstobehigherthanminPricerequire(price>_minPrice,"Slippagehitonchainprice");if(orderAddresses[1]==KYBER_ETH_ADDRESS){(tokens[0],)=ExchangeInterface(wrapper).swapEtherToToken.value(tokens[1])(tokens[1],orderAddresses[2],uint(-1));}else{ERC20(orderAddresses[1]).transfer(wrapper,tokens[1]);if(orderAddresses[2]==KYBER_ETH_ADDRESS){tokens[0]=ExchangeInterface(wrapper).swapTokenToEther(orderAddresses[1],tokens[1],uint(-1));}else{tokens[0]=ExchangeInterface(wrapper).swapTokenToToken(orderAddresses[1],orderAddresses[2],tokens[1]);}}emitSwap(orderAddresses[1],orderAddresses[2],_amount,tokens[0],wrapper);}}//returnwhateverisleftincontractif(address(this).balance>0){msg.sender.transfer(address(this).balance);}//returnifthereisanytokensleftif(orderAddresses[2]!=KYBER_ETH_ADDRESS){if(ERC20(orderAddresses[2]).balanceOf(address(this))>0){ERC20(orderAddresses[2]).transfer(msg.sender,ERC20(orderAddresses[2]).balanceOf(address(this)));}}if(orderAddresses[1]!=KYBER_ETH_ADDRESS){if(ERC20(orderAddresses[1]).balanceOf(address(this))>0){ERC20(orderAddresses[1]).transfer(msg.sender,ERC20(orderAddresses[1]).balanceOf(address(this)));}}}

1、在代码第5行可以看到先对orderAddresses[1]是否为KYBER_ETH_ADDRESS地址做了判断,由于orderAddresses[1]为DAI合约地址,因此将直接调用transferFrom函数将数量为_amount的DAI转入本合约。

2、接下来在代码第11、12行,通过takeFee函数计算fee,最终计算结果都为0,这里不做展开。

3、由于攻击者传入的_exchangeType为4,因此将走代码第22行if(_exchangeType==4)的逻辑。在代码中我们可以看出在此逻辑中调用了takeOrder函数,并传入了攻击者自定的_callData,注意这将是本次攻击的关键点,接下来切入分析takeOrder函数:

functiontakeOrder(address[3]memory_addresses,bytesmemory_data,uint_value,uint_amount)privatereturns(bool,uint,uint){boolsuccess;(success,)=_addresses[0].call.value(_value)(_data);uinttokensLeft=_amount;uinttokensReturned=0;if(success){//checkhowmanytokensleftfrom_srcif(_addresses[1]==KYBER_ETH_ADDRESS){tokensLeft=address(this).balance;}else{tokensLeft=ERC20(_addresses[1]).balanceOf(address(this));}//checkhowmanytokensarereturnedif(_addresses[2]==KYBER_ETH_ADDRESS){TokenInterface(WETH_ADDRESS).withdraw(TokenInterface(WETH_ADDRESS).balanceOf(address(this)));tokensReturned=address(this).balance;}else{tokensReturned=ERC20(_addresses[2]).balanceOf(address(this));}}return(success,tokensReturned,tokensLeft);}

4、在takeOrder函数中的第4行,我们可以直观的看出此逻辑可对目标_addresses[0]的函数进行调用,此时_addresses[0]为_exchangeAddress即DAI合约地址,而具体的调用即攻击者自定传入的_callData,因此如果持有DAI用户在DAI合约中对SaverExchange合约进行过授权,则可以通过传入的_callData调用DAI合约的transferFrom函数将用户的DAI直接转出,具体都可以在_callData中进行构造。

5、接下来由于返回的tokens[0]为1,所以将走swapTokenToToken函数代码块中第76行以下的逻辑,可以看到都是使用if判断的逻辑,毫无疑问都能走通。

分析思路验证

让我们通过攻击者的操作来验证此过程是否如我们所想:

1、通过链上记录可以看到,被盗的用户历史上有对SaverExchange合约进行DAI的授权,交易哈希如下:

0xdcf73848022ec1f730d9fdb90f4e8563f0dff48d9191aab19fc51241708eacf0

2、通过链上数据可以发现传入的_callData为:

23b872dd//SlowMist//transferFrom函数签名000000000000000000000000c001cd7a370524209626e28eca6abe6cfc09b0e50000000000000000000000005bb456cd09d85156e182d2c7797eb49a438401870000000000000000000000000000000000000000000041a522386d9b95c00000//SlowMist//310000e18

其中可以看出23b872dd为transferFrom函数签名。

3、通过链上调用过程可看出攻击者直接调用DAI合约的transferFrom函数将被盗用户的31万枚DAI转走:

完整的攻击流程如下

1、攻击者调用swapTokenToToken函数传入_exchangeAddress为DAI合约地址,选择_exchangeType为4,并将攻击Payload放在_callData中传入。

2、此时将走_exchangeType==4的逻辑,这将调用takeOrder函数并传入_callData。

3、takeOrder函数将对传入的_callData进行具体调用,因此如果持有DAI用户在DAI合约中对SaverExchange合约进行过授权,则可以通过传入的_callData调用DAI合约的transferFrom函数将用户的DAI直接转出,具体都可以在_callData中进行构造。

4、通过构造的_callData与此前用户对SaverExchange合约进行过DAI的授权,SaverExchange合约可以通过调用DAI合约的transferFrom函数将用户账户中的DAI直接转出至攻击者指定的地址。

最后思考

此漏洞的关键在于攻击者可以通过takeOrder函数对目标合约_addresses[0]的任意函数进行任意调用,而传入takeOrder函数的参数都是用户可控的,且未对参数有任何检查或限制。因此,为避免出现此类问题,建议项目方使用白名单策略对用户传入的_callData等参数进行检查,或者结合项目方具体的业务场景寻找更好的调用方式,而不是不做任何限制的进行随意调用。

此漏洞不仅只影响到通过DAI合约对SaverExchange合约授权过的用户,如果用户历史对SaverExchange合约有进行过其他Token的授权,则都会存在账户Token被任意转出风险。建议此前有对SaverExchange合约进行过授权的用户尽快取消授权(推荐使用这个网站自查授权情况),避免账户资产被恶意转出。

相关参考链接如下:

https://medium.com/defi-saver/disclosing-a-recently-discovered-exchange-vulnerability-fcd0b61edffe

https://twitter.com/imTokenOfficial/status/1314126579971186688

来源链接:mp.weixin.qq.com

本文来源:慢雾科技
原文标题:DeFi Saver 用户 31 万 DAI 是如何被盗的?慢雾技术拆解攻击细节

—-

编译者/作者:慢雾科技

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

知识 钱包 DeFi
LOADING...
LOADING...