智能合约安全陷阱和开发建议

本文介绍了智能合约常见的安全陷阱,如函数可重入、跨函数资源共享、整数溢出、gas限制和循环等问题,并给出了相应的解决建议,如使用CEI模式、避免依赖时间戳、使用SafeMath库等。同时,文章强调了智能合约开发规范的重要性,包括错误处理、使用最新的安全规范和充分测试等。
摘要由CSDN通过智能技术生成

Solidity作为一种图灵完备的高级语言,可以支持逻辑比较复杂的智能合约的编写。一般情况下,Solidity开发者可以根据自己的意愿和预期开发一个智能合约项目。同时也不能保证,没有黑客攻击的存在。因此呢,确保安全性在Solidity开发中极为重要。

今天我们要介绍的常见智能合约安全陷阱包括如下:

  • 函数可重入
  • 跨函数资源共享
  • 整数溢出漏洞
  • gas限制和循环
  • tx.orign和msg.sender引发安全漏洞
  • 依赖时间戳漏洞
  • 利用revert发送dos攻击
  • 短地址攻击

智能合约安全陷阱举例

漏洞1:函数可重入

**可重入性:**函数被调用,没有执行完成,又一次被调用

示例:

pragma solidity ^0.5.0;

contract Game{
   
    //记录各个玩家余额
    mapping(address => uint256) private balance;
    
    constructor()payable public{
   
        
    }
    
    //玩家下注函数,向游戏合约充值
    function deposit()public payable{
   

        balance[msg.sender] += msg.value;
    }
    
    //玩家提款函数
    function withdraw()public{
   
        bool ret;
        bytes  memory data;
        
        uint256 amount = balance[msg.sender];
       
        (ret, data) = msg.sender.call.value(amount)("");
     
        if (ret){
   
            balance[msg.sender] = 0;
        }
    }
    //获取合约总余额函数
    function gameBalance() public view returns(uint256){
   
        return address(this).balance;
    }
}


contract GamePlayer{
   
    //记录游戏合约地址
    address payable target;
    
    constructor()payable public{
   
        
    }
    //攻击函数,外部账户调用这个合约对游戏合约进行攻击
    function attack(address payable addr) public{
   
       target = addr;
       
       Game g = Game(target);
       //先向合约充值1个以太币
       g.deposit.value(1000000000000000000)();
       //然后替换
       g.withdraw();
    }
    
    //fallback函数中进行重入提款
    function()payable external{
   
       Game g = Game(target);
       g.withdraw();
    }
    //查看玩家合约余额
    function palyerBalance() public view returns(uint256){
   
        return address(this).balance;
    }
}

从Game游戏合约中我们可以看出,它的withdraw函数有下面几方面的安全隐患:

-1. 使用了底层的call函数进行转账,同时没有指定gas消耗
-2. 提款操作时,没有将用户的余额状态变量清空,就向用户转账

由于游戏合约的withdraw函数,使用了call函数向玩家进行转账。call函数默认是将剩余的gas全部传入, 也就是说call函数默认允许使用全部剩余的gas。这样的话,如果call函数引发了重入的话,会执行很多代码,无法及时终止。另外withdraw函数是在call调用之后再将用户的余额的状态变量清空,如果call函数引发了重入再次调用withdraw函数,代码会判断用户余额状态,发现还能进行转账。

解决方法:

  • 对于第1点安全隐患的解决方案是使用send或者transfer函数进行转账,transfer函数只传入2300个gas,即便发生重入,也无法调用修改状态变量等操作,因为一个修改状态变量的操作所消耗的gas至少是5000。这样可以保证本次转账重入是安全的。
  • 对于第2点安全隐患的解决方案是使用CEI模式,全称是Checks-Effects-Interactions,先检查,再生效,最后交互,对于这个例子,游戏合约的withdraw函数应该先检查用户的状态变量是否是大于0,如果等于0就不用转账,如果是大于0的,将用户的状态余额先清空,最后进行进行转账。
  • 使用互斥锁对balance资源进行保护,如下:
pragma solidity ^0.5.0;
contract Game{
   
   mapping(address => uint256) private balance;
   bool balanceLock = false;
   
   constructor()payable public{
   
       
   }
   
   function deposit()public payable{
   

       balance[msg.sender] += msg.value;
   }
   
   function withdraw()public{
   
       require(!balanceLock);
       balanceLock = false;
       
       bool ret;
       bytes  memory data;
      
       uint256 amount = balance[msg.sender];
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值