Files
CTF/DownUnderCTF 2023/blockchain/ Another Please/README.md
2023-09-02 14:08:40 +02:00

3.5 KiB

Another Please

A smart contract system is selling DownUnderLand tickets! Can you get them all?

Goal: Own all 30 tickets to DownUnderLand in your player wallet.

Author: BlueAlder

Estimated startup time: 90 seconds

setup

Siehe "Eight Five Four Five"

Source

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

/**
 * ______                    _   _           _           _                     _
 * |  _  \                  | | | |         | |         | |                   | |
 * | | | |_____      ___ __ | | | |_ __   __| | ___ _ __| |     __ _ _ __   __| |
 * | | | / _ \ \ /\ / / '_ \| | | | '_ \ / _` |/ _ \ '__| |    / _` | '_ \ / _` |
 * | |/ / (_) \ V  V /| | | | |_| | | | | (_| |  __/ |  | |___| (_| | | | | (_| |
 * |___/ \___/ \_/\_/ |_| |_|\___/|_| |_|\__,_|\___|_|  \_____/\__,_|_| |_|\__,_|
 */

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract AnotherPlease is ERC721Enumerable {
    uint256 public constant TICKETS_TO_GIVE_AWAY = 10;
    uint256 public constant PURCHASABLE_TICKETS = 20;

    uint256 public constant TICKET_PRICE = 10000000000000 ether;

    mapping(address => bool) public freeTicketReceivers;
    uint256 public ticketsGivenAway;

    error FreeTicketAlreadyClaimed();
    error FreeTicketsExhausted();
    error NotEnoughFunds();
    error SoldOut();

    constructor() ERC721("DownUnderLand Tickets", "DUCTF_ENTRY") {}

    modifier ticketNotClaimed() {
        if (freeTicketReceivers[msg.sender]) revert FreeTicketAlreadyClaimed();
        _;
    }

    // The first 20 people to claim a ticket get it freeeeeeeeeeeeeeeee!
    function claimFreeTicket() external ticketNotClaimed {
        if (ticketsGivenAway >= TICKETS_TO_GIVE_AWAY) revert FreeTicketsExhausted();
        _gibTicket(msg.sender);
        ticketsGivenAway++;
        freeTicketReceivers[msg.sender] = true;
    }

    // Buy a ticket to the exclusive DownUnderLand Party!!!!!
    // Cheap price!
    function buyATicket() external payable {
        if (msg.value < TICKET_PRICE) revert NotEnoughFunds();
        _gibTicket(msg.sender);

        uint256 change = TICKET_PRICE - msg.value;
        if (change > 0) {
            (bool success,) = msg.sender.call{value: change}("");
            require(success, "Bruh do u not want ur money back?");
        }
    }

    function totalTicketsAvailable() public pure returns (uint256) {
        return TICKETS_TO_GIVE_AWAY + PURCHASABLE_TICKETS;
    }

    function _gibTicket(address to) internal {
        if (totalSupply() >= totalTicketsAvailable()) revert SoldOut();
        _safeMint(to, totalSupply());
    }
}

Analyse

Ziel ist es alle 30 Tickets zu besitzen. Glücklicherweise kriegen wir die ersten 10 gratis, aber die letzten 20 sind teurer als wir es uns leisten können. Die Methode claimFreeTicket prüft erst, ob noch gratis Tickets vorhanden sind, und sendet dann das Ticket und decrementiert dann den Counter. Und hier liegt die Schwachstelle: Der Angreifer Callt schnell hintereinander die claimFreeTicket Methode. Das Übertragen der Tickets via _safeMint triggert vermutlich einen call auf uns, was uns erlaubt die methode erneut aufzurufen. Wenn der Counter heruntergesetzt wird, werden wir schon alle Tickets besitzen.

_safeMint(address to, uint256 tokenId)
internal

Safely mints tokenId and transfers it to to.

Requirements:

    tokenId must not exist.

    If to refers to a smart contract, it must implement IERC721Receiver.onERC721Received, which is called upon a safe transfer.

Emits a Transfer event.

Lösung

#TODO