链眼社区:专注于区块链安全,区块链数据分析, 区块链信息整合,区块链技术服务和区块链技术咨询。

solidity智能合约安全-溢出攻击
扫地僧
2021-08-06 23:54:17

攻击回顾
1
2
3
4
5
6
7
2016年6月,以太坊最大众筹项目The DAO被攻击,黑客获得超过350万个以太币,最终导致以太坊分叉为ETH和ETC。
2016年拒绝服务攻击:GovernMental's 1100 ETH
2016年KotET(“纷争时代”)合约遭受攻击。
2017年Parity钱包,遭受delecate call注入,销毁了合约。损失513,774.16 Ether
2017年ANT Token遭受重入漏洞。
2017年Simoleon合约被攻击。黑客通过部署攻击合约获得了超过700万的token,从57万账户中脱颖而出,一举成为该合约token的第四大持有者。
2018年BEC代币遭到袭击,攻击手法被披露的24小时内,就有30多个合约遭受攻击
溢出

孔子曾经说过 过犹不及。做事情都有限度,一旦超过了限度就会适得其反。理解溢出问题最好的是在千禧之年爆发的千年虫事件。过去,由于计算机程序中使用两个数字来表示年份,如1998年被表示为“98”、1999年被表示为“99”;而2000年被表示为“00”,这样将会导致某些程序在计算时得到不正确的结果,如把“00”误解为1900年。在嵌入式系统中可能存在同样的问题,这有可能导致设备停止运转或者发生更加灾难性的后果。

solidity中的溢出问题

下面是一个简单的函数,其功能是将桉树加1.例如传递4,返回5。传递200,返回201。但是里面暗藏着陷阱。例如当传递255的时候,会返回0…这就是溢出。这是由于uint8的最大值为255,在内存中:为1111 1111。一旦加1之后,变为了
1 0000 0000,但是最大的位数为8位。截断之后,变为了0000 0000 因此返回的结果为0。

1
2
3
function add(uint8 a) public pure returns(uint8){
   return a+1;
}
safeMath避免溢出问题

因此我们可以看到,对于4则运算,很容易的发生溢出问题。OpenZeppelin 建立了一个叫做 SafeMath 的 库。这就规避掉溢出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
library SafeMath {
   function add(uint a, uint b) internal pure returns (uint c) {
       c = a + b;
       require(c >= a);
   }
   function sub(uint a, uint b) internal pure returns (uint c) {
       require(b <= a);
       c = a - b;
   }
   function mul(uint a, uint b) internal pure returns (uint c) {
       c = a * b;
       require(a == 0 || c / a == b);
   }
   function div(uint a, uint b) internal pure returns (uint c) {
       require(b > 0);
       c = a / b;
   }
}
BEC代币攻击全纪实

如下为BEC代币的源代码抽离出来的部分。2018年4月份BEC代币遭到黑客的溢出袭击。黑客为自己的两个账号转移了2^255次方的代币。导致市场的恐慌,币价一度一文不值。攻击手法被披露的24小时内,就有类似30多个合约遭受攻击

在TokenExample合约中,有一个batchTransfer函数。此函数的功能为对账户进行转账操作。第一个参数为动态长度地址,明确要转账的账户。第二个参数为转账的金额。 要转账成功,必须要保证在balance资金表中,发送者必须有超过总金额(账户数量转账金额)。但是 uint256 amount = uint256(cnt) value;这段代码并没有做安全的乘法,导致可能会发生溢出攻击。当黑客调用合约的时候,在remix中:
输入地址数组以及:[“0xb4D30Cac5124b46C2Df0CF3e3e1Be05f42119033”,“0x0e823fFE018727585EaF5Bc769Fa80472F76C3d7”],
以及
value"0x8000000000000000000000000000000000000000000000000000000000000000"即2*255,
使得amount=2^255 * 2,超出uint256类型的范围[0,2**256-1],溢出为0,发送者账户余额不减少,并且,本例中,发送者的代币可以为零,实现"无中生有"。

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

library SafeMath{
   function sub(uint256 a,uint256 b) internal pure returns(uint256){
       assert(b<=a);
       return a-b;
   }

   function add(uint256 a,uint256 b) internal pure returns(uint256 c){
       c=a+b;
       assert(c>=a);
       return c;
   }
}

contract TokenExample{
   //使用safemath库
   using SafeMath for uint256;

   //资金表
   mapping(address=>uint256) public balance;
   function batchTransfer(address[] _receivers,uint256 _value) public returns(bool){
       //要转移的地址的数量  
       uint cnt = _receivers.length;
       //转账总金额
       uint256 amount = uint256(cnt)*_value;
       //判断转账地址必须大于0
       require(cnt >0 && cnt <=20);
       //判断发送者拥有的金额必须大于转账的总金额
       require(_value >0 && balance[msg.sender]>=amount);
       //发送者账户金额减少 20
       balance[msg.sender] = balance[msg.sender].sub(amount);
       //接受者金额增加   10
       for(uint i =0 ;i<cnt;i++){
           balance[_receivers[i]] = balance[_receivers[i]].add(_value);
       }

       return true;
   }
}
减法溢出案例

调用distribute函数,传入地址数组:
[“0x14723a09acff6d2a60dcdf7aa4aff308fddc160c”,“0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db”]
owner分别向这两个地址发送2000 * 108代币,超过owner余额,balances[owner]产生减法溢出;查询owner账户余额,等于2256-2000*10**8

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
contract TokenExample{
   address public owner;
   mapping(address =>uint256) public balances;
   mapping(address =>mapping(address=>uint256)) public allowed;

   event Transfer(address _from,address _to,uint256 _value);

   modifier onlyOwner{
       require(msg.sender==owner);
       _;
   }

   constructor() public {
       owner = msg.sender;
       balances[owner] = 2000*10**8;
   }

   function distribute(address[] addresses) public onlyOwner{
       for(uint i =0;i<addresses.length;i++){
               balances[owner] -=2000*10**8;
               balances[addresses[i]] +=2000*10**8;
               emit Transfer(owner,addresses[i],2000*10**8);
       }
   }
}
加法溢出

1、部署合约;

2、向target预先转一部分代币,模拟目标账户中已有的代币:调用transfer函数,传入target地址:
0x14723a09acff6d2a60dcdf7aa4aff308fddc160c,
以及转账额度,比如:
2000000000000000000(2 * 10**uint256(decimals));
3、如果owner想控制target的余额减半,那么,他只需要向target增发
2*256-balanceOf[target]+10*18=0xfffffffffffffffffffffffffffffffffffffffffffffffff21f494c589c0000,
现在调用mintToken函数,向target地址转入上述数量的代币:
4、查询target余额



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
contract TokenExample{

   address public owner;
   mapping(address=>uint256) public balanceOf;
   uint256 public totalSupply;
   uint8 public decimals = 18;

   event Transfer(address _from,address _to,uint256 _value);

   modifier onlyOwner{
       require(msg.sender==owner);
       _;
   }

   constructor() public{
       owner = msg.sender;
       totalSupply = 2000 * 10**uint256(decimals);
       balanceOf[owner] = totalSupply;
   }

   function transfer(address _to,uint256 _value) public returns(bool success){
      require(balanceOf[msg.sender] >=_value);
      require(balanceOf[_to] +_value >=balanceOf[_to]);
      balanceOf[msg.sender] -= _value;
      balanceOf[_to] +=_value;
      emit Transfer(msg.sender,_to,_value);
      return true;
   }
   function mintToken(address target,uint256 mintedAmount) public onlyOwner{
       balanceOf[target] +=mintedAmount;
       totalSupply +=mintedAmount;
       emit Transfer(0,this,mintedAmount);
       emit Transfer(this,target,mintedAmount);
   }
}
溢出列表

溢出运算列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
exception on overflow in unsigned->signed conversion
 exception on overflow in signed->unsigned conversion
 exception on overflow in size-decreasing implicit conversion
 exception on overflow in addition of two signed numbers
 exception on overflow in addition of two unsigned numbers
 exception on underflow in subtraction of two signed numbers
 exception on underflow in subtraction of two unsigned numbers
 exception on overflow in multiplication of two signed numbers
 exception on overflow in multiplication of two unsigned numbers
 exception on overflow in shifts
 exception on overflow in ++ on a signed number
 exception on overflow in ++ on an unsigned number
 exception on underflow in -- on a signed number
 exception on underflow in -- on an unsigned number
 exception on overflow in +=
 exception on overflow in -=
 exception on overflow in *=
 exception on overflow in /=
 make sure no optimizations are relying on (a + b - b == a); lest they remove overflow exceptions


合作伙伴