mirror of
https://github.com/autistic-symposium/blockchains-security-toolkit.git
synced 2025-12-11 06:35:34 -05:00
3 KiB
3 KiB
Polygon Lack of Balance
- This vulnerability consisted of a lack of balance/allowance check in the transfer function of Polygon's MRC20 contract and would have allowed an attacker to steal all MATIC.
Vulnerability Analysis
- The MATIC token is like Ether, but for the Polygon network. The MATIC token is used in the Polygon ecosystem for several functions, including voting on PIPs, staking, and gas costs.
- The most interesting thing about MATIC token is its standard. It's the native gas-paying asset of the Polygon network, but it's also a contract deployed on Polygon. The contract is MRC20 contract. This standard is used mainly for the possibility of transferring MATIC gaslessly, which with Ether, is impossible. With Ether, you are making a transaction that a wallet needs to sign.
- Gasless MATIC transfers are facilitated by the
transferWithSig()function. The user who owns the tokens signs a bundle of parameters, including the operator, amount, nonce, and expiration. This signature can be later passed to the MRC20 contract by the operator to perform a transfer on behalf of the token owner. This is gasless for the token owner because the operator pays for the gas. - Smart contracts on Ethereum have access to the built-in ECDSA signature verification algorithm through
erecover. This built-in function lets you verify the integrity of the signature over the hashed data and returns the signer's public key. ecrecoveryis a wrapper function on top of the standarderecover, that lets you pass a packed signature without the need to separate V, R, and S.- The bug in the token could have allowed an attacker to mint an arbitrary number of tokens from the MRC20 contract.
- The main issue is that
_transferFromwill call the_transferfunction directly without checking whether thefromhas enough balance. And we can calltransferWithSig()without a valid signature, thanks to the lack of check to see iferecoveryreturns the zero address. The function takes the balances offromandtoaddress and passes that to the_transfer(), which has the same issue (it doesn't check that the sender has enough balance).
PoC
- Create a byte string of length anything other than 65:
erecoveryreturns the zero address if the packed signature does not have length 65. This means we don't need a valid signature to proceed. amountpassed to the function can be any amount, but we can use the full balance of the MRC20 contract.toaddress will be an attacker address.- After
fromis recovered from the invalid signature,_transferFrom()is called. - As the balances are not checked from
fromandto, contracts makes a_transfer()call. _transfer()only checks if the recipient isn't the MRC20 contract itself and transfers all the amount to the attacker from the MRC20 contract.
Fix
- Remove
transferWithSigfunction.