Skip to content

Latest commit

 

History

History
114 lines (88 loc) · 4.33 KB

009.md

File metadata and controls

114 lines (88 loc) · 4.33 KB

Basic Powder Meerkat

High

Missing chainId in Hash Computation Allows Replay Attacks Across Chains

Summary

As mentioned in the README, the project will be deployed at:

Ethereum, Arbitrum, Rari Chain, zkSync Mainnet, Base, Polygon, OP Mainnet

The GovernanceStakerOnBehalf.sol contract includes several functions (stakeOnBehalf, stakeMoreOnBehalf, alterDelegateeOnBehalf, alterClaimerOnBehalf, withdrawOnBehalf, and claimRewardOnBehalf) that rely on EIP-712 signatures for authorization. However, the hash computation for these functions does not include the chainId parameter, which is a critical component of the EIP-712 domain separator. This omission makes it possible for an attacker to replay valid signatures on different chains.

Vulnerability Detail

stakeOnBehalf, stakeMoreOnBehalf, alterDelegateeOnBehalf, alterClaimerOnBehalf, withdrawOnBehalf, and claimRewardOnBehalf functions verify the authenticity of transactions using EIP-712 signatures. However, the _hashTypedDataV4() does not include the chainId in the hash computation, making it possible for an attacker to replay the same transaction on different chains.

This can happen when there is also a fork in the chain.

For example stakeOnBehalf() functions works the following way:

  function stakeOnBehalf(
    uint256 _amount,
    address _delegatee,
    address _claimer,
    address _depositor,
    uint256 _deadline,
    bytes memory _signature
  ) external virtual returns (DepositIdentifier _depositId) {
    _revertIfPastDeadline(_deadline);
    _revertIfSignatureIsNotValidNow(
      _depositor,
      _hashTypedDataV4(
        keccak256(
          abi.encode(
            STAKE_TYPEHASH,
            _amount,
            _delegatee,
            _claimer,
            _depositor,
            _useNonce(_depositor),
            _deadline
          )
        )
      ),
      _signature
    );
    _depositId = _stake(_depositor, _amount, _delegatee, _claimer);
  }

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 transactions on the target chain, potentially leading to a loss of staked tokens, misappropriation of rewards, and delegation changes.

Code Snippet

https://github.com/sherlock-audit/2024-11-tally/blob/043815089dfa4cb2ee3e4344839070c9c679ed52/staker/src/extensions/GovernanceStakerOnBehalf.sol#L71-L98

https://github.com/sherlock-audit/2024-11-tally/blob/043815089dfa4cb2ee3e4344839070c9c679ed52/staker/src/extensions/GovernanceStakerOnBehalf.sol#L108-L131

https://github.com/sherlock-audit/2024-11-tally/blob/043815089dfa4cb2ee3e4344839070c9c679ed52/staker/src/extensions/GovernanceStakerOnBehalf.sol#L141-L169

https://github.com/sherlock-audit/2024-11-tally/blob/043815089dfa4cb2ee3e4344839070c9c679ed52/staker/src/extensions/GovernanceStakerOnBehalf.sol#L180-L208

https://github.com/sherlock-audit/2024-11-tally/blob/043815089dfa4cb2ee3e4344839070c9c679ed52/staker/src/extensions/GovernanceStakerOnBehalf.sol#L218-L241

https://github.com/sherlock-audit/2024-11-tally/blob/043815089dfa4cb2ee3e4344839070c9c679ed52/staker/src/extensions/GovernanceStakerOnBehalf.sol#L250-L274

Tool used

Manual Review

Recommendation

Include chainId explicitly in the hash computation for all affected functions, e.g.

bytes32 public constant STAKE_TYPEHASH = keccak256(
    "Stake(uint256 amount,address delegatee,address claimer,address depositor,uint256 chainId,uint256 nonce,uint256 deadline)"
);

function stakeOnBehalf(
    uint256 _amount,
    address _delegatee,
    address _claimer,
    address _depositor,
    uint256 _deadline,
    bytes memory _signature
) external virtual returns (DepositIdentifier _depositId) {
    _revertIfPastDeadline(_deadline);
    _revertIfSignatureIsNotValidNow(
        _depositor,
        _hashTypedDataV4(
            keccak256(
                abi.encode(
                    STAKE_TYPEHASH,
                    _amount,
                    _delegatee,
                    _claimer,
                    _depositor,
                    block.chainid,
                    _useNonce(_depositor),
                    _deadline
                )
            )
        ),
        _signature
    );
    _depositId = _stake(_depositor, _amount, _delegatee, _claimer);
}