sherlock-audit / 2024-06-boost-aa-wallet-judging

3 stars 1 forks source link

TessKimy - Valid signatures can be used to claim other incentives #453

Open sherlock-admin4 opened 1 month ago

sherlock-admin4 commented 1 month ago

TessKimy

High

Valid signatures can be used to claim other incentives

Summary

Valid signatures can be used in order to claim any other incentive from the boost

Root Cause

In Boost protocol, there are two main components: Actions and Incentives. Boost owners creates actions and incentives and users complete these actions and they can claim their reward from incentives. Boost owners use trusted signer addresses in order to avoid violations.

In SignerValidator.sol signature is checked valid or not in validate() function.

    function validate(uint256 boostId, uint256 incentiveId, address claimant, bytes calldata claimData)
        external
        override
        returns (bool)
    {
        if (msg.sender != _validatorCaller) revert BoostError.Unauthorized();

        (BoostClaimData memory claim) = abi.decode(claimData, (BoostClaimData));
        (SignerValidatorInputParams memory validatorData) =
            abi.decode(claim.validatorData, (SignerValidatorInputParams));

&>      bytes32 hash = hashSignerData(boostId, validatorData.incentiveQuantity, claimant, claim.incentiveData);

        if (uint256(validatorData.incentiveQuantity) <= incentiveId) {
            revert BoostError.InvalidIncentive(validatorData.incentiveQuantity, incentiveId);
        }
        if (!signers[validatorData.signer]) revert BoostError.Unauthorized();

        // Mark the incentive as claimed to prevent replays
        // checks internally if the incentive has already been claimed
        _used.setOrThrow(hash, incentiveId);

        // Return the result of the signature check
        // no need for a sig prefix since it's encoded by the EIP712 lib
        return validatorData.signer.isValidSignatureNow(hash, validatorData.signature);
    }

While creation of hash variable validatorData.incentiveQuantity variable is used but it's equal to total number of incentives in the boost. It normally should hash incentiveId in this function in order to create non-replayable hash. incentiveId usage in hashing is crucial in here because users can claim any incentive in the boost after claiming only one of them. incentiveData also doesn't hold the incentiveId data, it stores claimable reward amount of incentive.

Internal pre-conditions

  1. User should complete at least 1 action and then he/she should be eligible for claiming at least 1 incentive

Attack Path

  1. In a boost which has 3 actions and 3 different incentives, user should complete an action in order to claim one of the incentive.
  2. Alice complete one of those action and get a signature from a trusted signer in order to claim her incentive.
  3. This signature is not created especially for that incentiveId because of wrong hashing.
  4. Now Alice can claim every incentive in the Boost using this signature.

Impact

High - Main invariant violation, users can claim any incentive just after claiming one of them.

Mitigation

In order to create a correct hash in order to claim an incentive. Adding incentiveId to hash is crucial for this kind of replay attack vectors.

    function validate(uint256 boostId, uint256 incentiveId, address claimant, bytes calldata claimData)
        external
        override
        returns (bool)
    {
        if (msg.sender != _validatorCaller) revert BoostError.Unauthorized();

        (BoostClaimData memory claim) = abi.decode(claimData, (BoostClaimData));
        (SignerValidatorInputParams memory validatorData) =
            abi.decode(claim.validatorData, (SignerValidatorInputParams));
-       bytes32 hash = hashSignerData(boostId, validatorData.incentiveQuantity, claimant, claim.incentiveData);
+       bytes32 hash = hashSignerData(boostId, incentiveId, claimant, claim.incentiveData);
        if (uint256(validatorData.incentiveQuantity) <= incentiveId) {
            revert BoostError.InvalidIncentive(validatorData.incentiveQuantity, incentiveId);
        }