Moonbeam Missing Call Check Bugfix Review
On May 27th, whitehat pwning.eth submitted a missing call check critical vulnerability to the Moonbeam network via Immunefi, demonstrating the possibility of a direct theft of the native assets, such as Moonriver (MOVR) and Moonbeam (GLMR), which were deployed using pre-compiled contracts. The Moonbeam team estimated that the vulnerability could have impacted up to $100m in funds, which was prevented due to the whitehat’s swift disclosure.
The security vulnerability was found within Frontier — the Substrate pallet that provides core Ethereum compatibility features within the Polkadot ecosystem, which Moonbeam helped create.
However, thanks to the whitehat’s work, no user funds were lost, and Moonbeam quickly released an upgrade that patched the vulnerability.
The whitehat was awarded $1 million for his find, the max critical bounty from Moonbeam’s bug bounty program on Immunefi. Moonwell added a $50k bonus as well, making pwning.eth’s total winnings $1,050,000.
You can read pwning.eth’s blogpost about his responsible disclosure in Moonbeam here.
What are the Moonbeam and Moonriver networks?
The Moonbeam network is a smart contract platform for cross-chain connected applications that unites functionality from many blockchains including Ethereum, Cosmos, Polkadot, and more. It makes it possible for developers with Solidity or Vyper-based smart contracts to create multi-chain instances of their application that are able to communicate with each other. Moonbeam is able to unify access to users, assets, and data through: compatibility and cross-chain interoperability with many blockchains, an excellent development environment with unmatched tool support, and a modern proof-of-stake architecture built on Substrate.
Moonriver is a companion network to Moonbeam which was deployed as a parachain to the Kusama network on Polkadot.
Both Moonbeam and Moonriver offer the feature of pre-compiled contracts, which allows developers to interact with the native protocol token through an ERC-20 interface. One of the main benefits of this precompile is that it removes the necessity of having a wrapped representation of the protocol token as an ERC-20 smart contract, such as WETH on Ethereum. Furthermore, it prevents having multiple wrapped representations of the same protocol token.
Under the hood, the ERC-20 precompile executes specific substrate actions related to the substrate balances pallet, which is coded in Rust. The balances pallet provides functionality for handling the various types of balances on Moonbeam, setting the free balance, transferring balances, and so on. More pallets can be found here.
In Ethereum, there are three major types of contract calls: regular
When contract A makes a
CALL to contract B by calling
foo(), the function execution relies on contract B’s storage, and the
msg.sender is set to contract A.
This is because contract A called the function
foo(), so that the
msg.sender would be contract A’s address and
msg.value would be the ETH sent along with that function call. Changes made to state during that function call can only affect contract B.
However, when the same call is made using
DELEGATECALL, the function
foo() would be called on contract B but in the context of contract A. This means that the logic of contract B would be used, but any state changes made by the function
foo() would affect the storage of contract A.
msg.sender would point to the EOA who made the call in the first place. And what is important in the case of the Moonbeam bug,
msg.value would point to the first call context, not the second. In other words, Ether is not sent along
delegatecall. (See example 2).
However, there was no logic present under the Moonbeam pre-compiled contract to determine if the incoming call is
DELEGATECALL or a static
CALL in EVM.
The vulnerability concerns calls to non-standard Ethereum precompiles. Those are addresses allowing the EVM, through smart contracts, access to some of Moonbeam’s core features (like XC-20 functionality, staking, and democracy pallets) that do not exist in the base EVM. Using a
DELEGATECALL, a malicious smart contract could access the precompile storage of another party.
DELEGATECALL, a malicious smart contract could access the precompile storage of another party via a callback hence a malicious contract can pass its
msg.sender to the precompile contract to impersonate its caller. There is no way for the precompile contract to figure out the actual caller.
This is not a problem for typical users, as it would require them to send a transaction to the malicious smart contract using phishing vectors. The attacker could have tricked the victims into triggering the malicious contract that hides the backdoor call to
approve() by impersonating the original caller.
However, it is an issue for other smart contracts allowing arbitrary calls to external smart contracts. For example, this is the case of some smart contracts allowing callbacks to user-controllable contracts such as with the flashloan feature, etc, In those situations, a malicious user could make a DEX execute a call to the malicious smart contract that would be able to access the precompiles pretending to be the DEX and possibly transfer its balance to any other address.
Exploit contract demonstrates how a caller could be tricked into providing the
MAX allowance to the attacker’s beneficiary address and later the attacker could call
asset.transferFrom(…) to transfer the tokens from the victim to the beneficiary address.
The runtime 1503 quickly disabled all uses of
DELEGATECALL to prevent the vulnerability from being exploited.
The runtime 1504 re-established
DELEGATECALL for Ethereum standard precompiles and for smart contracts (while keeping it disabled for custom precompiles).
In between those two runtime upgrades, transactions to smart contracts relying on
DELEGATECALL failed and were marked as reverted.
The Moonbeam team permanently fixed the vulnerability by adding a check to revert the
DELEGATECALL to the custom precompiled contract in EVM. The PR of the fix is available here.
We would like to thank pwning.eth for responsibly disclosing such an important bug. Big props also to the Moonbeam team who responded quickly to the report and patched it.
Make sure to check out the bugfix review of the bug that pwning.eth discovered in Aurora, which was similar in nature to the above vulnerability in Moonbeam. For his Aurora find, pwning.eth was paid a $6m bounty.
If you’re a Web2 or Web3 developer who is finally thinking about a bug-hunting career in Web3, we got you. Check out our ultimate blockchain hacking guide, and start taking home some of the $132m in rewards available on Immunefi — the leading bug bounty platform for Web3.
And if you’re feeling good about your skillset and want to see if you will find bugs in the code, check out the bug bounty program from Moonbeam.