Skip to content

Exploit a Smart Contract⚓︎

Direct link: Bored Sporc Rowboat Society website



Exploit flaws in a smart contract to buy yourself a Bored Sporc NFT. Find hints for this objective hidden throughout the tunnels.


Psst. Hey, slick - over here. Myeah.
You look like a sucker ahem I mean, savvy..
I got some exclusive, very rare, very valuable NFTs for sale..
But I run a KringleCoin-only business. Kapeesh?.
Ever buy somethin' with cryptocurrency before?.
Didn't think so, but if you wheel and deal with ya' pal Luigi here, now you can!.
But we're currently in pre-sale, and you gotta be on the list. Myeah, see?.
BSRS NFTs are a swell investment. They'll be worth a pretty penny, and that's a promise..
So when they're purchasable, you better snatch 'em up before the other boneheads ahem I mean, eggheads do..
I got a business to run. You can't buy nothin' right now, so scram. Kapeesh?


Merkle Tree Arboriculture

You're going to need a Merkle Tree of your own. Math is hard. Professor Petabyte can help you out.

Plant a Merkle Tree

You can change something that you shouldn't be allowed to change. This repo might help!


Finding the application flaw⚓︎

Merkle trees, how do they work?

The second and third bullet points in the instructions on the BSRS presale page state that very high-techy-techy Merkle trees are being used to validate if someone's wallet address is on the approved list to buy a Bored Sporc NFT. To help understand what they are and how they can be applied to the use case of an NFT pre-order approval list, we can use Professor Qwerty Petabyte's excellent explanation of the concept.

In short, storing information in the blockchain is expensive. To work around this, Merkle tree root and leaf node values are calculated from the list of approved wallet addresses and only the root value is stored in the blockchain. Users on the approved list are then provided with a series of leaf node values called a proof which can be used in combination with the user's wallet address to calculate the root value. If the calculated root value matches the original value stored in the blockchain, the user is confirmed to be on the approved list.

The NFT presale is backed by the BSRS_nft.sol smart contract which we can find in block #2 on the blockchain. When we submit the form on the BSRS presale page, the leaf node value of our wallet address and proof are used as parameters for the smart contract's verify function. The function takes these values, calculates the Merkle tree root value, and then compares it to the root value which is also provided as a function parameter. All is well until we look at the client-side code though. The do_presale function in the bsrs.js JavaScript file not only submits our wallet address and proof, but it also sends along a root value. 🤔

JavaScript do_presale function

Solidity verify function

In other words, the verify function in the BSRS_nft.sol smart contract isn't necessarily comparing its calculated Merkle tree root value to the root value of the actual list of approved wallet addresses. It's comparing to whatever root value is sent along when submitting the presale HTML form. Since we have control over the client-side JavaScript, this can be exploited by making the smart contract validate against the root value of a Merkle tree we generate ourselves.

Validating the vulnerability⚓︎

Using Professor Petabyte's Python script we can generate the smallest possible Merkle tree based on just two wallet addresses, our own personal wallet address and a randomly chosen value. As a result, the Merkle tree will only contain two leaf nodes and a root node. Start by cloning the repository using git clone and replace the first item in the allowlist variable on line 149 with the wallet address we want to appear as being on the approved list.


Next, run the script using python which will generate the Merkle tree values shown below.

Merkle tree

We can now validate the generated root (R) and proof (N2) by using our web browser's developer tools to first update the root variable in the do_presale function and then submitting our wallet hash (1) and generated proof (N2) via the HTML form on the BSRS presale page. Alternatively, we can use a tool like curl to manually submit the POST request to /cgi-bin/presale. As we're just testing, make sure to keep the Validate Only option checked or the Validate field set to true in the HTTP request.

Merkle tree

Looks like we successfully tricked the website into thinking we are on the allowed list. It's time to go shopping!

Running the exploit⚓︎

Just like when we purchased our hat, we first need to use a KTM to approve a payment of 100 KC to the BSRS wallet address at 0xe8fC6f6a76BE243122E3d01A1c544F87f1264d3a. Next, we head back to the BSRS presale page and repeat the steps from the validation phase by first changing the root variable in the do_presale JavaScript function to the value generated by the script (i.e., 0x888856bc24d1b1d29efbbe11252822381fc6827aed392db58c423b3262309594).

Update root variable

Finally, we enter our wallet address and the 0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a proof value generated by Professor Petabyte's script, uncheck the Validate Only option, and submit the form!

Purchase completed

The success message confirms we are now the proud and official owner of a Bored Sporc NFT! 😄

BSRS Token #000013
    "name": "BSRS Token #000013",
    "description": "Official Bored Sporc Rowboat Society Sporc #000013",
    "image": "",
    "external_url": "",
    "token_id": 13
Blockchain transactions - Blocks #23207, #23219, and #23220

Block #23207 - Payment approval⚓︎

First, the details of the payment approval are stored in block #23207. This block holds the transaction from our personal wallet address to the KringleCoin.sol smart contract address where the approve function is called with the BSRS wallet address and 100 KringleCoin as input parameters. Go on, take our money!

Block 23207

Block #23219 - Transfer funds⚓︎

Next, block #23219 shows the transaction from the BSRS wallet address to the KringleCoin.sol smart contract address where the transferFrom function is called to transfer the approved 100 KringleCoin from our wallet to the BSRS wallet. If the exploit fails, we're out 100 KC and have nothing to show for it!

Block 23219

Block #23220 - Verification and NFT minting⚓︎

Last but not least, the NFT minting transaction is stored in block #23220. This block contains the transaction from the BSRS wallet address to the BSRS_nft.sol smart contract address where the presale_mint function is called with our wallet address, the generated proof, and the generated root as input parameters. The presale_mint function in turn calls the verify function to confirm our wallet address and proof add up to the root value we also submitted. The proof and root values are stored in the blockchain as bytes. We can convert them back to hex and confirm they are indeed the values we submitted.

Convert bytes to hex using Python
>>> b'\x88\x88V\xbc$\xd1\xb1\xd2\x9e\xfb\xbe\x11%("8\x1f\xc6\x82z\xed9-\xb5\x8cB;2b0\x95\x94'.hex()
>>> b'S\x80\xc7\xb7\xae\x81\xa5\x8e\xb9\x8d\x9cx\xdeJ\x1f\xd7\xfd\x955\xfc\x95>\xd2\xbe`-\xaa\xa4\x17g1*'.hex()

Block 23220


Buy a Bored Sporc NFT by exploiting a flaw in the smart contract.



What!? How'd you get on the list? What's that? You's a double agent, and you're actually workin' for us?
I don't know if I buy that, but you're on the list, so... myeah.
Somethin' about this ain't sittin' right with me, but there's no reversing transactions with cryptocurrency.
That NFT is yours to keep, but if I find out you're lyin' to me, Palzari's gonna pay you a visit. Kapeesh?


How was a plebeian such as yourself granted access to the pre-sale?
I present thee with a proffer to purchase the NFT you've acquired for twice the price.
Hwhat? You shan't vend to me? Have you any idea who I am?
You just refused the abhorrent Count Chorizo!
I shall ensure you are nevah able to transact with that NFT agayn!


Hmph... this is so boring...
"This is a serious task" he said, "not a sporc headbutting-party" he said.
"Mess this up, Slicmer, and I'll tie a rock to your feet and throw you down a well!" he said.
I think this job was just to keep me out of his way. Luigi thinks I'm a blockhead.
Well I think he's a -- Huh? Wait a minute...
Hey! Boss! I think I see somethin'!


Tsk tsk tsk, I thought I told you to play nice?
The only reason you're not in time-out is because Luigi doesn't seem convinced that you're a little rascal.
He's not as clever as he likes to think. But once he comes around...
You better watch out, dear. And when I catch you, you better not cry. Or do, not even Santa will hear you.