Skip to main content

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

题目要求

  1. 获取合约的 owner 权限
  2. 转走合约的所有余额

题目解析

想要转走合约的所有余额,需要调用withdraw方法。而withdraw方法加了onlyOwnermodifier。所以需要先获取的合约的owner权限。只有contributereceive才能获取到owner权限。contribute方法中需要满足contributions[msg.sender] > contributions[owner]条件需要至少调用 1000000 以上。显然这样不是最优解决办法。下面receive只需要满足msg.value > 0 && contributions[msg.sender] > 0.所以攻击步骤如下

  1. 调用contribute并支付大于 0,小于0.001的 ETH
  2. 直接给合约转账任意大于 0 数量的 ETH
  3. 调用withdraw方法

攻击步骤

await contract.contribute({value: 10000})
await sendTransaction({from: player,to: instance,value: 1000})
await contract.withdraw()

该题目涉及到的知识点

  1. receive()方法

当给一个合约直接转账时,合约必须实现receive()或者fallback()方法,并且方法要加上payable修饰词