Skip to content

Latest commit

 

History

History
112 lines (82 loc) · 4.06 KB

File metadata and controls

112 lines (82 loc) · 4.06 KB

Bitter Hemp Tiger

High

Claim signature can be used by anyone, makes frontrunning and stealing funds possible

Summary

In VVVVCTokenDistributor::claim, it allows users to claim their rewards accumulated during the vesting period, an user would need a valid signature to proceed. However, in claim and signature validation function, it only checks wether the signature is from expected signer, but does not check msg.sender, this open an opportunity for anyone to use existing valid signature for their own benefit.

Root Cause

Here, in the claim function, basic sanity checks are done for the param argument and signature:

    function claim(ClaimParams memory _params) public {
        if (claimIsPaused) {
            revert ClaimIsPaused();
        }

        if (_params.projectTokenProxyWallets.length != _params.tokenAmountsToClaim.length) {
            revert ArrayLengthMismatch();
        }

        if (_params.nonce <= nonces[_params.kycAddress]) {
            revert InvalidNonce();
        }

        if (!_isSignatureValid(_params)) {
            revert InvalidSignature();
        }

        // update nonce
        nonces[_params.kycAddress] = _params.nonce;

        // define token to transfer
        IERC20 projectToken = IERC20(_params.projectTokenAddress);

        // transfer tokens from each wallet to the caller
        for (uint256 i = 0; i < _params.projectTokenProxyWallets.length; i++) {
            projectToken.safeTransferFrom(
                _params.projectTokenProxyWallets[i],
                msg.sender,
                _params.tokenAmountsToClaim[i]
            );
        }

        emit VCClaim(
            _params.kycAddress,
            _params.projectTokenAddress,
            _params.projectTokenProxyWallets,
            _params.tokenAmountsToClaim,
            _params.nonce
        );
    }

And also in _isSignatureValid:

    function _isSignatureValid(ClaimParams memory _params) private view returns (bool) {
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                DOMAIN_SEPARATOR,
                keccak256(
                    abi.encode(
                        CLAIM_TYPEHASH,
                        _params.kycAddress,
                        _params.projectTokenAddress,
                        _params.projectTokenProxyWallets,
                        _params.tokenAmountsToClaim,
                        _params.nonce,
                        _params.deadline
                    )
                )
            )
        );

        address recoveredAddress = ECDSA.recover(digest, _params.signature);

        bool isSigner = recoveredAddress == signer;
        bool isExpired = block.timestamp > _params.deadline;
        return isSigner && !isExpired;
    }

The above function checks if signature is from expected signature, and wether signature has expired. During the entire flow of claiming process, msg.sender is not validated anywhere, but at the end, rewards are transferred to msg.sender. This would allow anyone, including addresses are not from KYC list and expected claim to use such signature to get reward for their own benefit.

In the vesting contract, invest also does not check msg.sender, but as msg.sender would need to pay for vested amount for kycAddress, such attack would gain no value.

Internal pre-conditions

Alice has some rewards ready to be claimed, and off-chain the protocol signs Alice a signature for her claim.

External pre-conditions

No response

Attack Path

Attacker observes her transaction or gets her signature in some other ways, frontruns her transaction using her signature, and get Alice's reward.

Impact

Rewards can be stolen, causing loss of funds due to frontrunning.

PoC

No response

Mitigation

Restrict reward recipient, considering adding recipient field in signature as well, and validate if msg.sender == params.recipient.