Fallback
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
题目要求
- 获取合约的 owner 权限
- 转走合约的所有余额
题目解析
想要转走合约的所有余额,需要调用withdraw
方法。而withdraw
方法加了onlyOwner
的modifier
。所以需要先获取的合约的owner
权限。只有contribute
和receive
才能获取到owner
权限。contribute
方法中需要满足contributions[msg.sender] > contributions[owner]
条件需要至少调用 1000000 以上。显然这样不是最优解决办法。下面receive
只需要满足msg.value > 0 && contributions[msg.sender] > 0
.所以攻击步骤如下
- 调用
contribute
并支付大于 0,小于0.001
的 ETH - 直接给合约转账任意大于 0 数量的 ETH
- 调用
withdraw
方法
攻击步骤
await contract.contribute({value: 10000})
await sendTransaction({from: player,to: instance,value: 1000})
await contract.withdraw()
该题目涉及到的知识点
receive()
方法
当给一个合约直接转账时,合约必须实现receive()
或者fallback()
方法,并且方法要加上payable
修饰词