Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Flow Security Incident 27th December: Technical Post-Mortem- 1853

FlowPosted 2 Months Ago
  • Flow suffered a major hack of about $3.9M USD. This was not an application but an issue with the blockchain itself. No existing user balances were accessed; the attacker was able to duplicate assets out of thin air. This is the story of vulnerability and how Flow handled it.
  • Cadence is a resource-oriented programming language similar to Move. In many blockchains, such as EVM, tokens are in a ledger (a map of balances), and that's it. In Flow, they are programmable objects that exist in a user's account storage, mimicking a physical dollar. These Resources, mimicking a physical asset, cannot be copied or accidentally discarded; they can only be moved or explicitly destroyed. The resources are great, but rigorous static and dynamic checks are performed to ensure resource integrity. If this could be bypassed, it would be a major problem.
  • In Cadence, Attachments are designed to allow developers to extend a struct or resource type with new functionality. they are values that are just attached to existing resources. When making a transaction, the imported struct and the value types were passed in dynamically. The validation for attachment types was not strict enough, which is the main reason for the issue. Mainly, the fields passed in were not checked against their static declared types, AND the runtime types field information could be specified incorrectly.
  • So, what's the consequence of this? By specifying an attachment with invalid fields, Cadence would believe that a type is a struct when it was actually a Resource during execution. During execution, this allowed the resource to be duplicated repeatedly. By duplicating coins, you could create tokens out of thin air. The verification system, like this, is scary because the consequences of a mistake are so deadly.
  • The authors considered this occurrence and did not perform these checks on some internal types because they should only be creatable by the runtime itself. A small number of these internal types, such as PublicKey used in the exploit, can be created with user code. So, the attacker declared a value as a PublicKey and then encoded a resource-containing structure as a value in its place. This allowed for smuggling a resource inside a struct context.
  • The previous two bugs were nice for copying values. But the resource is a public key type. So, a type-confusion bug was needed to restore the object as a useful resource. On the contract initialization function add(), the static types being used on the call were not validated to match the contract itself. This allowed turning a static public key into a resource. An absolutely crazy chain of three bugs!
  • The vulnerabilities above were fixed with specific checks. They upped the rewards on the bug bounty program and added a regression test for this exploit. To prevent this from happening in the future, several other things were done:
    • Hardened Argument Validation: Prevent rogue fields from being passed in.
    • Extended defensive checks for built-in types: Member access checks are done on all types now. This would have prevented the exploit in the first place.
    • Strict Deployment Semantics: Match between static and dynamic types on the order of contract instantiation.
  • The attacker obtained a small number of each of the 13 tokens. Then, duplicated these small amounts in a sequence 42 times to get much more than the total supply of tokens. The attacker tries to cash out on a centralized exchange but is flagged/stopped due to the massive size. A small number of assets are bridged through Celer, deBridge, and Stargate.
  • So, now what? Are assets gone? Four hours after the exploit took place, the bug was fixed, and validators halted transaction ingestion. Two days later, the counterfeit assets are recovered, destroyed, and the malicious accounts are restricted. Although a lot of tokens were duplicated, the realized damage was about $3.9M via bridged assets prior to the chain halt.
  • How were the tokens recovered? Via a special contract that had Governance authority to delete counterfeit tokens. Funds that were stolen via DEX's were recovered from the attacker-controlled addresses and designated as liquidity pool rebalancing funds.
  • An absolutely crazy exploit and an interesting resource. It's wild to me that Flow could delete resources or change ownership via Governance. Still, it seems like most of the impact was limited by this response so I can respect it.