Summary
The NativeMetaTransaction::executeMetaTransaction
function is vulnerable to cross-chain replay attacks due to the fact that signatures are verified without incorporating the chainId.
I would like to note that I know that the scope of this audit competition only mentions polygon
as a compatible blockchain for this protocol. But for the sake of security, I report this issue since the procotol could be deployed in another chain.
The issue can also occur when there is also a fork in the chain.
Vulnerability Details
The NativeMetaTransaction::executeMetaTransaction
function verifies the authenticity of transactions using EIP-712 signatures. However, the current implementation of the NativeMetaTransaction::hashMetaTransaction` function does not include the chainId in the hash computation, making it possible for an attacker to replay the same transaction on different chains.
File: /contracts/meta-transaction/NativeMetaTransaction.sol#L33-L68
function executeMetaTransaction(
address userAddress,
bytes memory functionSignature,
bytes32 sigR,
bytes32 sigS,
uint8 sigV
) public payable returns (bytes memory) {
MetaTransaction memory metaTx = MetaTransaction({
nonce: nonces[userAddress],
from: userAddress,
functionSignature: functionSignature
});
require(
verify(userAddress, metaTx, sigR, sigS, sigV),
"Signer and signature do not match"
);
nonces[userAddress] = nonces[userAddress] + 1;
emit MetaTransactionExecuted(
userAddress,
msg.sender,
functionSignature,
hashMetaTransaction(metaTx)
);
(bool success, bytes memory returnData) = address(this).call{value: msg.value}(
abi.encodePacked(functionSignature, userAddress)
);
require(success, "Function call not successful");
return returnData;
}
File: contracts/meta-transaction/NativeMetaTransaction.sol#L90-L106
function verify(
address signer,
MetaTransaction memory metaTx,
bytes32 sigR,
bytes32 sigS,
uint8 sigV
) internal view returns (bool) {
require(signer != address(0), "NativeMetaTransaction: INVALID_SIGNER");
return
signer ==
ecrecover(
toTypedMessageHash(hashMetaTransaction(metaTx)),
sigV,
sigR,
sigS
);
}
File: contracts/meta-transaction/NativeMetaTransaction.sol#L70-L84
function hashMetaTransaction(MetaTransaction memory metaTx)
public
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
META_TRANSACTION_TYPEHASH,
metaTx.nonce,
metaTx.from,
keccak256(metaTx.functionSignature)
)
);
}
Impact
An attacker can exploit this vulnerability by monitoring transactions on one chain and replaying them on another chain(fork) where the same contract is deployed. This can lead to unauthorized transaction execution.
Tools Used
Manual review.
Recommendations
Consider to include the chainId
in the verification process of the EIP-712 signatures. This ensures that each signature is unique to the specific chain it was intended for, preventing replay attacks across different chains.
An updated of the NativeMetaTransaction::hashMetaTransaction
function could be like this:
File: contracts/meta-transaction/NativeMetaTransaction.sol#L70-L84
function hashMetaTransaction(MetaTransaction memory metaTx)
public
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
META_TRANSACTION_TYPEHASH,
metaTx.nonce,
metaTx.from,
-- keccak256(metaTx.functionSignature)
++ keccak256(metaTx.functionSignature),
++ block.chainid
)
);
}