Holiday Hack 2022: Burning Ring of Fire
Orienting
Two Sporcs are conspiring at the top of the ladder down to the next level:
Brozeek:
Cro! Slicmer got me on the BSRS pre-sale!
Now all we gotta do is swap outfits, then you can go back in there as me.
Tell Slicmer you lost your wallet key, so you made a new wallet and need to add it to the list.
Then give him your wallet address, and we’ll both be able to buy an NFT!
Social engineering at its finest, Cro.
Crozag:
Bro, you usually have good ideas, but this one is really terrible.
Manipulating friends with social engineering isn’t cool, Bro.
Let’s do it!
At the bottom of the ladder, there’s a door to the Burning Ring of Fire:
Buy a Hat
Challenge
The badge defines the next challenge:
Travel to the Burning Ring of Fire and purchase a hat from the vending machine with KringleCoin. Find hints for this objective hidden throughout the tunnels.
Inside the Burning Ring of Fire Wombley Cube is standing next to the HATS vending machine, and informs me of the mission he’s on for Santa:
Hey there! I’m Wombley Cube. It’s so nice to see a friendly face.
What’s an elf doing all the way down here with all these sporcs, you ask?
I’m selling snazzy, fancy-pants hats! You can buy them with Kringlecoin.
The reason I set up shop here is to gather intel on that shady Luigi.
I’m a member of the STINC: Santa’s Team of Intelligent Naughty Catchers.
He and his gang are up to no good, I’m sure of it. We’ve got a real Code Brown here.
Purchase a hat so we look inconspicuous, and I’ll clue you in on what we think they’re scheming.
Of course, have a look at my inventory!
Oh, and if you haven’t noticed, I’ve slipped hints for defeating these Sporcs around the tunnels!
Keep your eyes open, and you’ll find all five of them. Wait, maybe it’s six?
The hints in the tunnels are useful later.
A bit further down, Palzari is next to another KTM, and greets me condescendingly:
Hello, dear. Come down to visit your tiddly elf friend?
You two are just adorable, playing hero and braving our flaming domain.
Sure, we’ll tolerate you playing here, but please behave, won’t you?
Use this KTM to buy your darling little hats, and nothing more. If you decide to be a brat, well…
I’ll disappear you into the Devnull Chasm, and nobody will ever see you again. Do we have an understanding?
Very good. Run along now, dear.
The badge has three hints for buying a hat:
- To purchase a hat, first find the hat vending machine in the Burning Ring of Fire. Select the hat that you think will give your character a bold and jaunty look, and click on it. A window will open giving you instructions on how to proceed with your purchase.
- Before you can purchase something with KringleCoin, you must first approve the financial transaction. To do this, you need to find a KTM; there is one in the Burning Ring of Fire. Select the Approve a KringleCoin transfer button. You must provide the target wallet address, the amount of the transaction you’re approving, and your private wallet key.
- You should have been given a target address and a price by the Hat Vending machine. You should also have been given a Hat ID #. Approve the transaction and then return to the Hat Vending machine. You’ll be asked to provide the Hat ID and your wallet address. Complete the transaction and wear your hat proudly!
Solution
Get Hat Info
Clicking on the vending machine pops a window with the interface:
There are many types of hats. Clicking on one loads a window of hat options:
Clicking on a hat gives the details about the hat:
I’ll note the address and the Hat ID.
Pay For Hat
Clicking on the KTM opens the interface:
I’ll click “Approve a KringleCoin transfer”, and fill in the form, and hit “Approve Transfer”. It works:
Get Hat
Now back to the vending machine, I’ll click “Approved a transaction? Know your Hat ID? Click here to buy”, which loads this form:
Clicking “Make your purchase!” writes the transaction to the blockchain:
And completes the task:
Chests
Background
There are several clues about the hidden chests throughout the subterranean tunnels Grinchum first mentions them after I get the Tolkien Ring. Some are quite noticeable while walking around in the tunnels. There are six in total:
All the rings can be discovered walking around, zooming out a bit, and brute forcing when walking blind. But I can also get more information about them to learn the exact paths and their numbering (see Hacking Holiday Hack).
Chest #1
Chest #1 is in the Hall of Talks, and can be reached by walking through the wall at the left end of the hall:
This chest offers:
- 13 KringleCoins
- A hint for the Blockchain Divination objective
Chest #2
Chest #2 is off the ladder between the talks level and the Tolkien Ring level:
The tunnel (highlighted in yellow) is a bit winding. The chest provides:
- 27 KringleCoins
- A hint for the Blockchain Divination objective
Chest #3
Chest #3 is in the Tolkien Ring, at a trap door under the table:
This chest contains:
- 15 KringleCoins
- A hint for the Smart Contract Objective
Chest #4
Chest #4 is hidden in the main tunnels, between the Elfen Ring and Web Ring levels:
The chest provides:
- 25 KringleCoins
- A hint for the Smart Contract objective
Chest #5
Chest #5 is very obvious going down the ladder to the Burning Ring of Fire, but it’s less obvious how to access it. The path starts on level with the Burning Ring of Fire door, and winds around to reach the chest:
This chest contains:
- 20 KringleCoins
- Special Hat
The special hat is pretty cool:
Chest #6
Chest #6 is off the left side of the Cloud Ring, starting on the ground, then going up a few steps before continuing to the left:
This chest contains:
- 10 KringleCoins
Blockchain Divination
Hints
Wombley is happy and offers the context for the rest of the challenges in this ring:
Nice hat! I think Ed Skoudis would say the same. It looks great on you.
So, here’s what we’ve uncovered so far. Keep this confidential, ok?
Earlier, I overheard that disgruntled customer in the office saying he wanted in on the “rug pull”.
If our suspicions are correct, that’s why the sporcs want an invite to the presale so badly.
Once the “Bored Sporc Rowboat Society” NFTs officially go on sale, the sporcs will upsell them.
After most of the NFTs are purchased by unwitting victims, the Sporcs are going to take the money and abandon the project.
Mission #1 is to find a way to get on that presale list to confirm our suspicions and thwart their dastardly scheme!
We also think there’s a Ring hidden there, so drop Mission #2 on them and rescue that ring!
Thank you for your business, dear customer!
Challenge
At the bottom of the Burning Ring of Fire, Slicmer is standing by the Blockchain Explorer, watching the blockchain:
Don’t bug me, kid. Luigi needs me to keep an eye on these offers you can’t refute.
The boss told me to watch them for any shifty transactions from wallets that aren’t on the pre-sale list.
He said to use this Block Explo… Exploder… thing.
With this, I can see all the movement of the uh… non-fungusable tokens.
Once on the blockchain, it’s there forever for the whole world to see.
So if I spot anything that don’t look right, I can let Luigi know, and Palzari will get to the bottom of it.
She looks sweet, but she’s actually the boss’ enforcer. Have you talked to her yet? She even scares me!
It sure would be fun to watch you get on her bad side. Heh heh.
There are two hints collected from chests (see above for details):
- Look at the transaction information. There is a From: address and a To: address. The To: address lists the address of the KringleCoin smart contract.
- Find a transaction in the blockchain where someone sent or received KringleCoin! The Solidity Source File is listed as
KringleCoin.sol
. Tom’s Talk might be helpful!
The badge challenge has an input box for me to enter the address of the KringleCoin smart contract:
Use the Blockchain Explorer in the Burning Ring of Fire to investigate the contracts and transactions on the chain. At what address is the KringleCoin smart contract deployed? Find hints for this objective hidden throughout the tunnels.
Solution
The Blockchain Explorer opens a window (also accessible at prod-blockbrowser.kringle.co.in) that shows blocks on the blockchain.
Putting in the block where I purchased the hat (107795), I can see the KringleCoin.sol
file that defines this contract, as well as the parameters passeed to the transferFrom
function, like my address and the hats address:
I’ll go to block number 1, and scrolling down a bit, in the transactions section, there’s one that “creates a contract”:
The contract address is there, 0xc27A2D3DE339Ce353c0eFBa32e948a88F1C86554.
Looking at the block where I bought the hat, the contract address is the to
address in the transaction (not to be confused with the _to
parameter passed to the function):
Entering this solves the task:
Exploit a Smart Contract
Hints
There are two more hints in the badge collected from chests around the tunnels (details above):
- You’re going to need a Merkle Tree of your own. Math is hard. Professor Petabyte can help you out.
- You can change something that you shouldn’t be allowed to change. This repo might help!
Challenge
Two more Sporcs are standing by a computer with the Bored Sporc Rowboat Society page up. Luigi tells me about the pre-sale:
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?
Chorizo is just angry not to be on the list:
Do you not have the slightest inclination of who I am?
How did I, Count Chorizo, Herald of Rrrrepugnance, not receive an invitation to the presale?
I could purchase every one of your wares, but now you shan’t have a single cent from me!
I will see to it that you nevah do business in these warrens agayn!
My badge says I need to figure out how to buy one of these presale NFTs:
Exploit flaws in a smart contract to buy yourself a Bored Sporc NFT. Find hints for this objective hidden throughout the tunnels.
The computer opens up the BSRS page (shown in it’s entrity because it’s awesome):
The Presale page lays out what I need to do to acquire one:
- Validate in the form on this page that my wallet is on the presale list, giving it both the wallet address and the “Proof Values”.
- Approve a 100 KC transaction to their provided wallet address at a KTM.
- Come back and enter my wallet address and “Proof Values” again (without the “Validate Only” box checked).
Merkle Trees
A Merkle tree is a data structure for storing hashes in such a way that with limited information, the validity of an item on the tree can be checked. The link from the hints and Professor Petabyte’s KringleCon talk go into more detail.
If we build a tree where all the objects we care about at at the lowest layer, and then each layer above is the hash of two items below it concatenated, it might look something like this:
For 8 items, this tree will have four levels. For 16 it’ll have five, 32 will have six, etc.
If I want to verify that h3
(the hash of object 3) is valid, I can send just that hash, along with h4
, h1,2
, h5,8
, and the root value (h1,8
).
The verifier can then compute h3,4
with h3
and h4
. Then it can compute h1,4
with h1,2
and h3,4
. Then it can compute h1,8
with that result and h5,8
. If it matches the known good root, then the hashes are all valid.
As long as there are no ways to exploit the hash function, and the root is trusted as known good, this system is much less overhead than having to compute a hash across something like all nodes.
BSRS Smart Contract
I’ll want a copy of the .sol
file associated with the BSRS NFT. In the blockchain explorer , I can scroll through until I find a transaction, or jump back to block 2, where it is deployed:
I’ll download a copy of the contract. There’s a function called presale_mint
that’s interesting:
function presale_mint(address to, bytes32 _root, bytes32[] memory _proof) public virtual {
bool _preSaleIsActive = preSaleIsActive;
require(_preSaleIsActive, "Presale is not currently active.");
bytes32 leaf = keccak256(abi.encodePacked(to));
require(verify(leaf, _root, _proof), "You are not on our pre-sale allow list!");
_mint(to, _tokenIdTracker.current());
_tokenIdTracker.increment();
}
It calls verify
on a hash of the to
address, the _root
, and the _proof
.
The verify
function implements the Merkle Tree verification, combining items up the tree until it reaches the root, and then seeing if the roots match:
function verify(bytes32 leaf, bytes32 _root, bytes32[] memory proof) public view returns (bool) {
bytes32 computedHash = leaf;
for (uint i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash == _root;
}
Website Requests
I’ll try to validate my wallet:
It fails.
Looking in Burp at the request that is sent when I click “Go!”, it’s a POST to /cgi-bin/presale
, with the following parameters:
{
"WalletID":"0xF4040fDea1d60705AA0b2118240A683f83Fc72df",
"Root":"0x52cfdfdcba8efebabd9ecc2c60e6f482ab30bdc6acf8f9bd0600de83701e15f1",
"Proof":"0xdf,0xdf",
"Validate":"true",
"Session":"70ff2cc8-4858-41b4-9486-2d95c3afe8cf"
}
Looking at the JavaScript loaded by the page, this request comes from bsrs.js
:
function do_presale(){
...[snip]...
var address = document.getElementById("wa").value;
var proof = document.getElementById('proof').value;
var root = '0x52cfdfdcba8efebabd9ecc2c60e6f482ab30bdc6acf8f9bd0600de83701e15f1';
var xhr = new XMLHttpRequest();
xhr.open('Post', 'cgi-bin/presale', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
var jsonResponse = JSON.parse(xhr.response);
ovr.style.display = 'none';
in_trans = false;
resp.innerHTML = jsonResponse.Response;
};
};
xhr.send(JSON.stringify({"WalletID": address, "Root": root, "Proof": proof, "Validate": val, "Session": guid}));
};
}
The root is hard-coded into the page. That’s not good.
Strategy
It’s safe to assume that the server is taking the proof
, root
, and wallet
variables and making a request to the blockchain to either verify
or presale_mint
. If can control the root that’s being checked against, then I can generate any Merkle tree with proof and my own address, and send it all, and it will return valid.
Generate Merkle Tree
I’ll use the Merkle Tree repo from Professor Petabyte. After cloning it, I’ll take a look at merkle_tree.py
. It has a bunch of mathy functions, and then at the bottom the code that runs as main. It hardcodes the list of nodes to build the list from:
allowlist = ['0x1337133713371337133713371337133713371337','0x0000000000000000000000000000000000000000']
Then it loops over them, recording the Keccak hash of each into the leaves
list, using the leaves to generate a tree:
for address in allowlist:
leaves.append(Web3.solidityKeccak(['bytes'], [address]))
mt = MerkleTreeKeccak(leaves)
Then it prints the root and the proofs needed for the first item in the list:
print('Root:', mt.root_hash)
print('Proof:', mt.get_proof(Web3.solidityKeccak(['bytes'], [allowlist[0]])))
Running it like this returns a single proof (which makes sense for a two node tree):
$ python merkle_tree.py
Root: 0x431aa5796d9dcb4f660d5693a60130628c39fcbe6b83648a572929b1625f5332
Proof: ['0x3fc27be7d8c4428e63a6da81ea42dbde2939babb79dac2b3bab1afd02c5a711c']
I’ll replace the first allow list item with my address, and run:
$ python merkle_tree.py
Root: 0x5fb06db010a8061bb2c53c6b2d4fd67945bc931c0273177c1d1ad76072ad1aef
Proof: ['0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a']
Buy an NFT
Validate
To test this theory, I’ll run the page through Burp Proxy with interception on. I’ll enter my wallet and the proof from the script:
Clicking “Go!” leads to a request at Burp:
I’ll change the Root
value to what was output by the script, and forward the request.
It worked!
Approve Transfer
I’ll grab the wallet address of 0xe8fC6f6a76BE243122E3d01A1c544F87f1264d3a
from the page, and head to the KTM. My wallet has plenty of KC to make a 100KC approval:
I’ll approve a transfer of 100 coins to the Sporcs:
Buy
I’ll head back and do the same thing again, turning on Burp Proxy intercept, and this time not checking the “Validate Only” box. I’ll enter my address and the same proof values from the script output:
I’ll submit, catching the request in Burp, and editing the Root
value to what the script generated:
It works:
And the challenge is solved:
I can get details about my NFT:
$ curl -s https://boredsporcrowboatsociety.com/TOKENS/BSRS662 | jq .
{
"name": "BSRS Token #000662",
"description": "Official Bored Sporc Rowboat Society Sporc #000662",
"image": "https://boredsporcrowboatsociety.com/TOKENS/TOKENIMAGES/BSRS662.png",
"external_url": "https://boredsporcrowboatsociety.com/TOKENS/BSRS662",
"token_id": 662
}
And see it:
Story
Burning Ring of Fire
I’ve now recovered the fifth ring, the Burning Ring of Fire:
The story is 89% complete:
Five Rings for the Christmas king immersed in cold
Each Ring now missing from its zone
The first with bread kindly given, not sold
Another to find ‘ere pipelines get owned
One beneath a fountain where water flowed
Into clouds Grinchum had the fourth thrown
The fifth on blockchains where shadows be bold
One hunt to seek them all, five quests to find them
Chorizo is furious:
Well…I…never…
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!
Luigi suspects foul play:
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?
Slicmer might be on to me:
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’!
Grinchum tells me to go to the castle:
😠 We wants them… we needs them… Must.. have.. the Preciouses.
They stole them from us, sneaky little humanses.
🙂 No, not the humanses, they’re my friends.
😏 You don’t have any friends. NOBODY likes YOU. You’re a liar, and a thief, and a…. grriiiiiiinch.
😢 Go away… we don’t need you anymore. The humanses protect us now.
😠 Go away? I protected us. The preciouses are safe because of ME!
🙂 Leave now, and never.. come back. 😃 Leave now, and never.. come back!
😁LEAVE NOW, AND NEVER.. COME BACK!😬
Friendly human, please go to jolly human’s castle! Go on, we will meet you there!
Conclusion
The castle doors are now clear:
Inside I’ll find Santa, elves and Fobbins, the birds from last year, and Smilegol, transformed from Grinchum now that the rings are recovered and no longer controlling him.
Santa congratulates me:
Congratulations! You have foiled Grinchum’s foul plan and recovered the Golden Rings!
And by the magic of the rings, Grinchum has been restored back to his true, merry self: Smilegol!
You see, all Flobbits are drawn to the Rings, but somehow, Smilegol was able to snatch them from my castle.
To anyone but me, their allure becomes irresistible the more Rings someone possesses.
That allure eventually tarnishes the holder’s Holiday Spirit, which is about giving, not possessing.
That’s exactly what happened to Smilegol; that selfishness morphed him into Grinchum.
But thanks to you, Grinchum is no more, and the holiday season is saved!
Ho ho ho, happy holidays!
And the story is complete:
Five Rings for the Christmas king immersed in cold
Each Ring now missing from its zone
The first with bread kindly given, not sold
Another to find ‘ere pipelines get owned
One beneath a fountain where water flowed
Into clouds Grinchum had the fourth thrown
The fifth on blockchains where shadows be bold
One hunt to seek them all, five quests to find them
One player to bring them all, and Santa Claus to bind them