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!

Horton Hears A Who!- 1077

Nadav Hollander Posted 3 Years Ago
  • The Horton Principle is a principle that should always be followed in cryptography: "mean what you sign and sign what you mean". Any time this can be violated, there is a major security problem.
  • The Wyvern protocol is a decentralized digital asset exchange running on Ethereum. This is the standard listings and offers style of market, such as trading NFTs.
  • Wyvern listings contain different parameters used to indicate listing or offer information and authenticate other involved smart contract calls. This is aggregated into a single commitment that the user signs and the contract checks. This is done in order to save funds, since it is signed off chain, and can be signed with expiration times as well. Pretty neat functionality!
  • Aggregating data is sketchy business if you come from the world of website hacking. The bytes being concatenated had no delimiter. Why is this bad? Consider A as 0x01 and B being 0x0101. If we concatenate A + B together, we end up with 0x010101. However, the signed data would be the same if A was 0x0101 and B was 0x01! This violates Horton's Principle mentioned above. In the context of the smart contract, this means we can change the parameter that the bytes were signed for!
  • Practically, this can be exploited in a very bad way. In the Wyvern system, NFTs are swapped for other digital items. To allow this, the Wyvern listings have a calldata field to allow a callback with specific parameters. In some cases, the calldata must be mutated at the time of fulfillment. This can be done by a replacementPattern - a bitmask to alter the calldata. This is necessary for OpenSea, the address of the offer taker must be added to the calldata.
  • In calldata, the first 4 bytes are the function selector. In the context of OpenSea, we need to call transferFrom(address,address,uint256). Using the modification primitive from above, an attacker could shift bits between the callData and replacementPattern to modify the function selector! The closest selector is getApproved(uint) at only 10 bits of difference.
  • There is a 1 in 1024 chance that a random 4 byte bitmask (with the shift) has the correct bitmask to change the selector to the proper location. Additionally, the address could be started at any point in the pattern. Hence, this reduced the exploit down to 1 out of 64 offers being exploitable. Using the getApproved(0) primitive, an attacker could have taken WETH from these users, despite them never approving or still owning the NFT.
  • Overall, a super interesting bug that was never exploited. Handling signing and data parsing is not nearly as simple as one would think.