原文标题:《 The Dangers of Surprising Code 》
原文作者: samczsun
The porridge of the night, Babbit
Note: In this article by samczsun, a white-hat hacker known as "auditing god" and a research partner at Paradigm, who recently rescued BitDAO MISO's $350 million assets from the Dutch auction pool, He warned about the potential security risks of the NFT token standard and predicted that as the ERC-721 and ERC-1155 token standards become more popular, attacks against the NFT are likely to become more frequent.

If you work in software engineering, chances are you've heard of at least one software engineering principle. While I'm not advocating strict adherence to every one of these principles, there are a few that do deserve attention.
What I'm going to talk about today is the principle of least surprise, which has a fancy name, but it's a very simple idea. What it says is that when presented with code that claims to do something, most users make assumptions about how it will do it. So it's your job as a developer to write code that fits these assumptions so that your users aren't surprised.
This is a good principle because developers like to make assumptions about things. If you export a calculateScore(GameState)& NBSP; Many people assume that this function will only read from the game state. If you also change the state of the game, you'll leave a lot of people in a confused state trying to figure out why their state of the game was randomly broken. Even if you put it in documentation, there's still no guarantee that people will see it, so it's best to make sure your code isn't surprising in the first place.

"Six hours of debugging will save you five minutes of document reading."
Back in early 2018, when the ERC-721 standard was drafted, there was a suggestion to implement transfer security to ensure that tokens would not get stuck in recipient contracts that were not used to process the tokens. To this end, the proposal authors modify the behavior of the transfer function to check whether the recipient is able to support token transfers. They also introduced the unsafeTransfer function, which will bypass this check if the sender wishes.
However, due to backward compatibility concerns, this function was renamed in a subsequent commit. This makes the transfer function for erC-20 and ERC-721 tokens behave the same. However, you now need to move the receiver inspection elsewhere. Therefore, the standard authors introduce safe class functions: safeTransfer And & have spent SafeTransferFrom.
This is a solution to the legitimacy problem, as there are many examples of ERC-20 tokens being accidentally transferred to contracts that never expected to receive the token (a particularly common mistake is to move the token into the token contract, locking it permanently). Not surprisingly, in drafting erC-1155, the proposal's authors took inspiration from ERC-721 and included recipient checks not only for transfers but also for mints.
Over the next few years, these standards mostly lay dormant, while the ERC-20 token standard maintained its popularity, and the recent spike in GAS costs, along with increased community interest in NFT, naturally led developers to increasingly use the ERC-721 and ERC-1155 token standards. With all this new interest, we should be thankful that these standards are designed with security in mind, right?
Ok, but what does security mean for transfer and casting? Different parties have different interpretations of safety. To a developer, a security function can mean that it does not contain any bugs or introduce additional security issues. And for users, that could mean including extra guardrails to protect them from accidentally shooting themselves in the foot.
It turns out that, in this case, there will be more of the latter and less of the former. This is especially unfortunate because, given the choice between transfer and safeTransfer functions, why don't you choose the safe one? The name says it all!
Well, one reason might be our old friend reentrancy, or what I've been trying to rename it: Insecure external calls. Recall that if the recipient is under the control of an attacker, then any external call may not be secure because the attacker may cause your contract to transition to an undefined state. By design, these "secure" functions perform external calls to the token receiver, usually controlled by the sender during casting or transfer. In other words, this is really a textbook example of an insecure external call.
But, you might ask yourself, what's the worst that can happen if recipients are allowed to contract to reject transfers they can't handle? Well, let me answer that question through two case studies.
Hashmasks is a limited supply of NFT avatar items that allows users to buy up to 20 Mask NFTS per transaction (although they have been sold out for months). Here's the function to buy a mask:

You might think that this function looks pretty reasonable. However, as you might have expected, there is a lot of work to be done in _safeMint There is something sinister lurking in the call. Let's see.

For security purposes, this function performs a callback to the token recipients to check if they are willing to accept the transfer. However, we are the receiver of the token, which means we just got a callback callback, at which point we can do anything we want, including calling the mintNFT function again. If we do this, we will call the function again after we have cast only one mask, which means we can request that 19 more masks be cast. This resulted in 39 Mask NFT's being cast, although the maximum number allowed by the rules was only 20.
Recently I was contacted by Nick Johnson from ENS who wanted me to take a look at the work they are doing on the ENS domain wrapper. This domain wrapper allows users to tokenize their ENS domain names with the new ERC-1155 token, which provides support for fine-grained permissions and a more consistent API.
In summary, in order to encapsulate any ENS domain name (more specifically, all ENS domains except 2ld.eth), you must first approve the domain wrapper to access your ENS domain name. Then you call wrap (uint96 bytes, the address, the address), it is casting a token ERC - 1155 for you, is also responsible for managing the underlying ENS domain name.
Here is the wrap function, which is fairly simple. First, we call _wrap, which performs some logic and returns the hash domain name. We then make sure that the sender of the transaction is indeed the owner of the ENS domain name before taking over the domain. Please note that if the sender does not own the underlying ENS domain name, then the entire transaction should be restored and revoked at _wrap& NBSP; Any changes made in.

Here's the _wrap function itself, nothing special here.

Unfortunately, that's exactly what it is. _mint Function, which can be a terrible surprise to the unsuspecting developer. The ERC-1155 specification states that at the time of casting a token, the recipient should be consulted on their willingness to accept the token. After delving into the library code, which was slightly modified based on OpenZeppelin, we found that this was indeed the case.

But what's in it for us? Ok, so once again we see an insecure external call that we can use to perform a reentrant attack. Specifically, note that during the callback callback, we owned the ERC-1155 token for the token ENS domain name, but the domain wrapper had not yet verified that we owned the underlying ENS domain name itself. This allows us to operate on the ENS domain without actually owning it. For example, we could ask the domain wrapper to unpack our domain name, burn the token we just minted and get the underlying ENS domain name.

Now that we own the underlying ENS domain, we can do anything we want with it, like register new subdomains or set up parsers. When we're done, we just exit the callback callback. The domain wrapper will interact with the current owner of the underlying ENS domain name (us) and complete the transaction. As such, we have taken temporary ownership and made arbitrary changes to any ENS domain names that the domain wrapper is approved to use.
Surprising code can break things in catastrophic ways. In both cases in this article, developers inadvertently increased their attack surface by reasonably assuming that the Safe function classes could be used safely. Such attacks are likely to become more frequent as the ERC-721 and ERC-1155 token standards become more popular and widespread. Developers need to consider the risks of using safe class functions and determine how external calls interact with the code they write.
The original link
Welcome to join the official BlockBeats community:
Telegram Subscription Group: https://t.me/theblockbeats
Telegram Discussion Group: https://t.me/BlockBeats_App
Official Twitter Account: https://twitter.com/BlockBeatsAsia