mirror of
https://github.com/autistic-symposium/blockchains-security-toolkit.git
synced 2025-06-05 21:48:54 -04: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. ecrecovery
is 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
_transferFrom
will call the_transfer
function directly without checking whether thefrom
has enough balance. And we can calltransferWithSig()
without a valid signature, thanks to the lack of check to see iferecovery
returns the zero address. The function takes the balances offrom
andto
address 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:
erecovery
returns the zero address if the packed signature does not have length 65. This means we don't need a valid signature to proceed. amount
passed to the function can be any amount, but we can use the full balance of the MRC20 contract.to
address will be an attacker address.- After
from
is recovered from the invalid signature,_transferFrom()
is called. - As the balances are not checked from
from
andto
, 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
transferWithSig
function.