当前位置:
首页DEFI这31万傣族人是怎么从defi Saver偷走的?慢雾技术拆除攻击细节
31万Dai用户被盗与DeFi-saver今年6月披露的一个漏洞有关,该漏洞也将影响授权给saverexchange的其他合约代币。
原标题:“慢雾:31万台Dai是如何从DeFi Saver用户窃取的?》
,为慢雾安全小组工作
2020年10月8日,去中心化钱包imtoken在推特上发布消息称,有31万个Dai被盗,这与DeFi-Saver交换漏洞有关。对此,DeFi Saver表示,被盗资金仍然安全,正在联系受害者。截至目前,所有资金已全部归还受害用户。早在今年6月,DeFi-Saver就表示,团队在DeFi-Saver系列应用程序中发现了自己的交易平台存在漏洞。31万Dai被盗也与之前saverexchange合同的漏洞有关。接到情报后,慢雾保安队对31万戴某被盗案进行了具体分析。
攻击过程分析
看看这个攻击交易
可见被盗用户0xc0直接传输310000 Dai攻击合同0x5b。
我们可以使用Oko浏览器查看具体的交易详情:
通过调用token,攻击者可以从token函数中看到,exchangeAddress,hide,Dest是Dai合约地址,选择_Exchangetype为4和一个自定义的_callData,可以推测这是攻击成功的关键函数。接下来,我们将对其进行详细分析
函数swaptokentoken(address_usrc,address_Tdest,uint_Uuamount,uint_minPrice,uint_uExchangeType,address_uExchangeAddress,bytes memory_callData,uint_xPrice)公共应付{//使用此选项可避免堆栈太深错误address[3]memory orderAddresses=[uExchangeAddresses,src,U dest];if(orderAddresses[1]==KYBER_
ETH_2;ADDRESS){需要(消息值gt;=金额消息值小于amount“);}else{require(ERC20(orderAddresses[1]).transferFrom(消息发送者,address(this),“无法提取想要的金额”);}uint fee=takeFee(u amount,orderAddresses[1]);_amount=sub(amount,fee);//[tokensReturn,tokensLeft]uint[2]内存代币;address wrapper;uint price;bool success;//在beging tokens[1]=amount;if(exchangeType==4){if(orderAddresses[1]!=KYBER_
ETH_Uaddress{ERC20(orderAddresses[1]).approve(ADDRESS(ERC20_Uproxy_0X),amount);}(success,tokens[0],)=takeOrder(orderAddresses,callData,ADDRESS(this.balance,u amount);//要么恢复,要么订单不再存在,我们恢复为明确要求的交换请求(successamp;tokens[0]gt;0,“0x transaction failed”);wrapper=address(exchangeAddress);}if(tokens[0]==0){(wrapper,price)=getBestPrice(amount,orderAddresses[1],orderAddresses[2],exchangeType);require(pricegt;_minPrice | | 0xPricegt;minPrice,“slipage hit”);//处理0x交换,如果价格相等,请尝试0x,如果(0xPricegt;=price){if(orderAddresses[1]!=KYBER_ETH_Uaddress{ERC20(orderAddresses[1])。approve(ADDRESS(ERC20_Uproxy_0X),Uamount);}(success,tokens[0],tokens[1])=takeOrder(orderAddresses,callData,ADDRESS(this.balance,u amount);//如果(successamp;tokens[0]>0){wrapper=address(exchangeAddress);发出Swap(orderAddresses[1],orderAddresses[2],amount,tokens[0],wrapper);}}if(tokens[1]gt;0){//如果0x只交换了一定数量的代币,并返回了其他所有if(tokens[1])!=U amount{(wrapper,price)=getBestPrice(tokens[1],orderAddresses[1],orderAddresses[2],exchangeType);}//如果0x失败,其他交易所的价格仍需高于minPrice require(pricegt;minPrice,“滑动命中链价格”);if(orderAddresses[1]==KYBER_ETH_Uaddress{(代币[0],)=ExchangeInterface(包装器)。swapETHertoken.value(tokens[1])(tokens[1],orderAddresses[2],uint(-1));}else{ERC20(orderAddresses[1]).transfer(wrapper,tokens[1]);if(orderAddresses[2]==KYBER_ETH_2;ADDRESS){tokens[0]=ExchangeInterface(wrapper).swapTokenToETHer(orderAddresses[1],tokens[1],uint(-1));}else{tokens[0]=ExchangeInterface(wrapper).swaptokentoken(orderAddresses[1],orderAddresses[2],tokens[1]);}发出交换(orderAddresses[1],orderAddresses[2],amount,tokens[0],wrapper);}}//如果(address(this).balancegt;0)返回合同中剩余的内容{消息发送方传输(address(this.balance);}//如果(orderAddresses[2]还有任何标记,则返回!=KYBER_ETH_Ua地址){if(ERC20(orderAddresses[2])。balanceOf(ADDRESS(this))>0){ERC20(orderAddresses[2])。传输(消息发送者,ERC20(orderAddresses[2]).balanceOf(address(this));}}if(orderAddresses[1]!=KYBER_ETH_Ua地址{if(ERC20(orderAddresses[1])。balanceOf(ADDRESS(this))>0{ERC20(orderAddresses[1])。传输(消息发送者,ERC20(orderAddresses[1]).balanceOf(address(this));}}}
1在第5行中,您可以看到orderaddresses[1]是否为kyber_ETH_uaddress Address。由于orderaddresses[1]是Dai合同地址,因此将直接调用transferfrom函数,并将数量为_Dai的金额转移到该合同中。
2接下来,在代码的第11行和第12行,通过takefee函数计算费用,最终计算结果为0。这里没有扩张。
3由于攻击者的传入值为4,因此如果(exchangetype==4),则转到第22行。在代码中,我们可以看到在这个逻辑中调用了takeOrder函数,并引入了攻击者的自主性。呼叫数据。请注意,这将是这次攻击的关键点。接下来,我们将分析takeorder函数
函数takeOrder(地址[3]内存地址,字节内存数据,uint值,uint数量)私有返回(bool,uint,uint){bool success;(success,)=U地址[0]。调用值(value)(data);uint tokensLeft=U amount;uint tokensReturn=0;if(success){检查{src if(addresses[1]==KYBER_ETH_uaddress){tokensLeft=ADDRESS(this).balance;}else{tokensLeft=ERC20(addresses[1]).balanceOf(address(this));}//如果(addresses[2]==KYBER_ETH_address){TokenInterface(WETH_address).draft(TokenInterface(WETH_address).balanceOf(address(this)),则检查返回多少个代币;tokensreturn=地址(this).balance;}else{tokensreturn=ERC20(addresses[2])。balanceOf(address(this));}}return(success,tokensreturn,tokensLeft);}
4在takeorder函数的第4行中,我们可以直观地看到这个逻辑可以应用到目标_U中,address[0]函数被称为_Addresses[0]是_U交换地址是Dai合约地址,具体调用是攻击者自己输入的调用数据,因此,如果持有Dai的用户已授权Dai合同中的saverexchange合同,则可以使用_Calldata调用Dai合同的transferfrom函数直接传输用户的Dai。您可以使用_Call数据。
5接下来,由于返回的标记[0]为1,因此将遵循swaptokentoken函数代码块第76行以下的逻辑。你可以看到,逻辑是建立在如果判断的基础上的,而且它无疑是可以工作的。
分析思路验证
让我们通过攻击者的操作来验证这个过程是否如我们所想的那样:
1从链记录可以看出,被盗用户在历史上拥有saverexchange合同的Dai授权。事务哈希如下:
0xDCF7384802EC1F730D9FDB90F4E8563F0DFF48D9191AAB19FC51241708EACF0
2通过链上的数据,传入的uCallData是:
23b872dd//slowmist//从函数签名转移:0000000000000000c001cd7a 370524209626e28eca6abe6cfc09b0e5 00000000000000005bb456cd 09d85156e182d2c77797eb49a43840187 00000000000000000000000000000041a52386d9b95c0000//slowlist//310000e18
可以看出,23b872dd是转移自函数的签名。
3通过链上的调用过程可以看出,攻击者直接调用Dai契约的transferfrom函数来转移被盗用户的31万Dai
完整的攻击过程如下
1攻击者调用swaptokentoken函数传入_uexchangeaddress为Dai协定地址,select_uexchange type为4,攻击有效载荷放在_Call数据中。
2这将转到_Exchangetype==4,它调用takeorder函数并传入_callData
3takeorder函数设置专门调用的uCallData。因此,如果持有Dai的用户已经在Dai合同中授权了saverexchange合同,则可以使用_Calldata调用Dai合同的transferfrom函数直接转移用户的Dai。您可以使用_Call数据。
4通过构造_Calldata和以前的用户,Dai已经授权saverexchange合同。saverexchange协定可以通过调用Dai协定的transferfrom函数将用户帐户中的Dai转移到攻击者指定的地址。
**的想法
该漏洞的关键在于攻击者可以利用takeorder函数任意调用地址[0]的任何函数,传入takeorder函数的参数由用户控制,对参数没有检查或限制。因此,为了避免此类问题的发生,建议项目方使用白名单策略对调用数据等参数进行检查,或者结合项目方的具体业务场景寻找更好的调用方式,而不是不加限制地任意调用。
此漏洞不仅影响已通过Dai合同授权saverexchange合同的用户。如果用户的历史记录为saverexchange合同授权了其他代币,则存在帐户代币被任意转出的风险。建议之前已授权saverexchange合约的用户应尽快取消授权(建议使用本网站查看授权情况),避免恶意转移账户资产。
相关参考链接如下:
https://medium.com/DeFi-saver/discovering-a-recent-discovered-exchange-vulnerability-fcd0b61edffe
https://twitter.com/imtokenofficer/status/1314126579971186688