Photo by Neil Rosenstech on Unsplash

SmartNFT Drop 01 — Hack Postmortem

Tyler van der Hoeven
8 min readOct 13, 2021

Ladies and gentlemen we have our very first Stellar smart contract hack postmortem!

📌 tl;dr
Everything is patched, zero funds were lost and a valuable lesson was learned.

The SmartNFT series is a public experiment of the Stellar Turrets protocol. Its intention is to provide a safe yet exposed place to build and test production level smart contracts in the hopes that design patterns can arise, common gotchas can be addressed (like this one!) and confidence in the entire protocol can grow.

We’ve been making steady progress on the Turrets protocol and reference implementation thanks to massive efforts led primarily by the Script3 team as they press on with their YieldBlox project. YieldBlox is a huge protocol with a lot of complex market mechanics. Thus it’s likely the wrong project to pave the way into a production Stellar Turrets world. For that we need something less serious, more fun, simpler and modular. Something exposed and vulnerable yet also innocuous and experimental. Enter SmartNFTs!

NFTs by their nature are a bit experimental to begin with. They’re weird, unexpected and expand beyond the bounds of a simple profit market model. There’s something game-like, and community centered about them, making them the perfect avenue for experimentation. SmartNFT is the tokenized frontlines of DeFi built on Stellar. They are the Stellar community's collection of "I was here, and it got weird" badges of honor.

I am not a smart contract protocol designer — I don’t even have a CS degree — and I while I do pride myself on my ability to produce hard to kill code, these smart contracts are an entirely new beast. Which leads us to the topic of today’s postmortem. I wrote some really poor contract code. More specifically I didn’t write some simple code which allowed for a basic vulnerability to be exploited.

⚠️ Disclaimer
This post will be somewhat technical but if you’re planning on writing smart contracts for the Stellar Turrets network it would serve you well to grok it as best you can.

The Background

The Stellar Turrets protocol operates on the basic fundamental capability of the Stellar protocol to enable multisig on Stellar accounts. The concept is simple, by default each Stellar account is an asymmetric keypair. So you have the account’s public key (also know as the source or master key) which can be used by signing transactions or operations it is the source of by signing with the account’s private key (also known as the secret key). So if I want account ABC to make a payment I'll need to sign that payment operation with the secret key counterpart of account ABC. Multisig is added to an account by attaching the permissions of another public:secret keypair to the account in question. So if ABC wanted account DEF to be able to sign for transactions and operations on its behalf it would simply add account DEF as a signer on its account.

Using this feature of Stellar we can give signing power of accounts (known as ctrlAccounts in the Turrets protocol) over to the Turrets by adding Turret smart contract signers to the accounts we want them to control through those smart contracts.

Take the original SmartNFT00 issuing account for example GCRHEEBJQ5FLJPHIGIQWJ7YLBT64MK7TS7W4K7PDIZQC5HCFN7KVKOWF. You'll notice two additional w:1 signers GAUPT4VNDXOSXVRGADHI2GYNAFKNLLWWWF4ON43GXXTCBH3AFW2EI4RV and GBSOHYMDNL4DL2J62DMTFXRIFU7KU4G6SRTGTZD2KVFPUF5LMTNISZHX which if you look up the contract hash for SmartNFT00 on the two Turrets I uploaded the contract to those are the signing keys I was given.

As mentioned both of those keys have been added to the G...KOWF ctrlAccount with a weight of 1. The operations thresholds for that account have all been set to 2 meaning that for the Turrets to be able to sign for any operations they both need to coordinate together to gain sufficient signing power to use the account. How do they coordinate? Through the smart contract!

This is the fundamental backbone of the Stellar Turrets protocol. It works well, so where is the issue?

The Attack

Any ctrlAccount under signing control of the Turrets can generate a sufficiently signed transaction such that no other signatures are needed. Duh, yeah we just covered that. Okay so riddle me this smarty pants, what if the contract accepts a source account intended to be a user's Stellar account public key but rather than inputing their own key they input one of contract's ctrlAccount keys? Oof, the contract would execute its logic on the ctrlAccount as if it were a user account wreaking potential havoc into the dependencies and flow of the contract.

This is exactly the vulnerability Nebolsin exploited on SmartNFT01. The mitigation is incredibly simple, on the contract side just don't allow inputs to be ctrlAccount addresses. Those address in my case are known and baked into the contract so I can just create if statements to disallow those address as inputs.

Oddly enough this vulnerability is exposed on the SmartNFT00 contract as well but cannot be exploited as the transaction that contract builds includes adding a trustline for an asset issued by the ctrlAccount and you cannot add trustlines for assets issued by the issuer. So the submission of the transaction would fail. Built in derived protection! It's worth calling that out as there's more than one way to close off a vulnerability, sometimes with simple if statements and other times with protocol level side affects and features. What used to be a gotcha just saved SmartNFT00 from buying itself its own asset and escalating payments back to the most recent 95 buyers which would not at all have been my intent to allow for.

The Fallout

Thanks to the design of the Turrets protocol nothing unacceptable happened. The contract behaved exactly as it was designed to, just on an account which shouldn’t have been able to be operated on as a source of the contract. You can look through the official drop docs to see what the dig command of the contract actually produces but the tl;dr is that a SmartPlotNFT was minted, issued and de-authorized on both of the PixelAsset distributor accounts.

De-authorized is the key word here. When a > 0 balance asset is de-authorized on an account it’s effectively stuck in the account. This means 2 things, the obvious first is that you can’t send your asset anywhere. No sales, trades, payments or even burns back to the issuing account. The second is that because of this you won’t be able to delete the account by merging it away. This is the crux of the issue with this hack, the mint command (which must be run before the issue command can be run) includes operations which merge (delete) both distributor accounts back into the main issuer account. This won't be able to run as long as there are SmartPlotNFT assets in either of the distributor accounts which will be true as long as those assets are de-authorized, which, will be like, forever, unless I have access to the issuing account of SmartPlotNFT and can manually remediate the issue by authorizing the trustlines and sending those assets elsewhere. Thankfully Nebolsin was gracious enough to provide me with these signing keys and I was able to perform exactly that remediation.

The vulnerability of course was still open until I created and transitioned to the new smart contract with the appropriate if statements to disable the dig arguments from containing ctrlAccount addresses.

You can observe the fallout from this by inspecting the primary issuer ctrlAccount GDJ2TPZFWEWXYIR27YMCUUR3KEDM37PUY7KY2MEFGB344EMTIRA7PXXJ which now has two SmartPlotNFT assets which were issued by the two hacked accounts.

These are the derived child addresses of the above two distributor ctrlAccount accounts which were previously locked by receiving the SmartPlotNFT from these two issuers. If you look through the operation history you can see exactly what happened and how it was remediated.

But wait, what do you mean transitioned to a new smart contract? Good question. In the case of the SmartNFT contracts I’ve left the master ctrlAccount keys as primary signers allowing me to swap out old Turret signers with new ones from new upgraded contracts. This is an intentional design decision intended to protect the SmartNFT ecosystem during this experimental phase. It is not a built in feature when using Turrets. If I had chosen to remove myself as a signer there would have been no remediation, no transitioning, the contract would have died the good, though unfortunate, death. Leaving yourself as a primary signer isn’t very decentralized depending on the goals of your protocol. The ideal scenario would likely be to build in a governance model from within your protocol such that signer swapping and contract upgrading could happen in a fully decentralized way.

The Summary

Thankfully this was an incredibly innocuous hack and thanks to Nebolsin’s cooperation and coordination with me we were able to resolve without any fundamental changes to the contract itself. If he had signer swapped and tossed or refused to gift me the secret keys for the SmartPlotNFT issuers I would have had to remove the distributor deletion operations in the mint command. That would have been fine, but I'm glad it didn't come to a protocol change.

This highlights a really nice fundamental feature of the way Stellar Turrets operate. Most hacks won’t be detrimental and far reaching. You definitely need to design carefully but it’s actually quite easy to sandbox effects into operational layers and account sandboxes isolating attack vectors to dead contracts or ctrlAccounts vs wide spread lost or stolen value. User accounts and funds are rarely directly controlled. Stellar Turrets will most often act as functionality coordinators for actions performed external to the Turrets network. This makes the design very flexible and the attack surface on Turrets quite small. In this hack assets were never at risk and actions were still entirely controlled by the contract, there was no unauthorized or unacceptable access, just unexpected due to a design oversight on my part.

All that to say Stellar Turret design best practices and common gotchas are still emerging so design and participate with care. Know your contract designers and understand the risks while this whole thing grows up, matures and stabilizes. This is an exciting time to participate in DeFi and it’s neat to be able to tokenize that in some small way via the SmartNFT experiment.

🗣 Shoutout
Sergey Nebolsin for white hat hacking me. Definitely could have caused more problems for me but he didn’t. Thanks!

Interested in learning more? Have questions for me? Ready to be the better builder and begin creating your own un-hackable contracts and protocols?

Want (aka need) to snag your own piece of SmartNFT history?

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Tyler van der Hoeven
Tyler van der Hoeven

Written by Tyler van der Hoeven

Engineering better financial futures @StellarOrg through funding, education and innovation. I write my own words. — “Work, and stuff will happen.”

No responses yet

Write a response