hats-finance / Proof-Of-Humanity-V2-0xef0709445d394a22704850c772a28a863bb780b0

Proof of Humanity Protocol v2
2 stars 1 forks source link

Unauthorized Humanity ID Update via Valid Gateway #169

Open hats-bug-reporter[bot] opened 2 months ago

hats-bug-reporter[bot] commented 2 months ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0x40c76a145bd69bf7c9683679bfcf2307c63d7c4c607ef19707908b7b1310e563 Severity: high

Description: Description\ The updateHumanity function in the CrossChainProofOfHumanity.sol contract allows any valid gateway to update the humanity status of any humanity ID. This could potentially lead to unauthorized updates, where a valid gateway could claim or update the humanity status of any humanity ID without proper authorization from the rightful owner.

/**
 * @notice Sends an update of the humanity status to the foreign chain. No need to specify the receiving chain as it'd be known by the gateway
 * @notice Communicates with receiveUpdate function of this contract's instance on the receiving chain
 *
 * @param _bridgeGateway address of the bridge gateway to use
 * @param _humanityId Id of the humanity to update
 */
function updateHumanity(address _bridgeGateway, bytes20 _humanityId) external allowedGateway(_bridgeGateway) {
    (, , , uint40 expirationTime, address owner, ) = proofOfHumanity.getHumanityInfo(_humanityId);
    bool humanityClaimed = proofOfHumanity.isClaimed(_humanityId);

    CrossChainHumanity storage humanity = humanityData[_humanityId];

    require(humanity.isHomeChain || humanityClaimed, "Must update from home chain");

    // isHomeChain is set to true when humanity is claimed
    // It also keeps true value (unless overwritten) if it was set before and humanity is now expired,
    //      thus making it possible to update state of expired / unregistered humanity
    humanity.isHomeChain = true;

    IBridgeGateway(_bridgeGateway).sendMessage(
        abi.encodeWithSelector(
            ICrossChainProofOfHumanity.receiveUpdate.selector,
            owner,
            _humanityId,
            expirationTime,
            humanityClaimed
        )
    );

    emit UpdateInitiated(_humanityId, owner, expirationTime, humanityClaimed, _bridgeGateway);
}

The updateHumanity function is designed to send an update of the humanity status to a foreign chain. However, the function allows any valid gateway (as determined by the allowedGateway modifier) to call it. This means that any authorized gateway can update the humanity status of any humanity ID, regardless of whether they have the rightful authority to do so.

Attack Scenario\ Step 1: Setup

Step 2: GatewayA Updates Humanity ID

Step 3: Unauthorized Update by GatewayB

Step 4: Impact

this also possible in another function in the crosschainHumnity() function,i will post it in detail in comments

Attachments

  1. Proof of Concept (PoC) File

    function updateHumanity(address _bridgeGateway, bytes20 _humanityId) external allowedGateway(_bridgeGateway) {
        (, , , uint40 expirationTime, address owner, ) = proofOfHumanity.getHumanityInfo(_humanityId);
        bool humanityClaimed = proofOfHumanity.isClaimed(_humanityId);
    
        CrossChainHumanity storage humanity = humanityData[_humanityId];
    
        require(humanity.isHomeChain || humanityClaimed, "Must update from home chain");
    
        // isHomeChain is set to true when humanity is claimed
        // It also keeps true value (unless overwritten) if it was set before and humanity is now expired,
        //      thus making it possible to update state of expired / unregistered humanity
        humanity.isHomeChain = true;
    
        IBridgeGateway(_bridgeGateway).sendMessage(
            abi.encodeWithSelector(
                ICrossChainProofOfHumanity.receiveUpdate.selector,
                owner,
                _humanityId,
                expirationTime,
                humanityClaimed
            )
        );
    
        emit UpdateInitiated(_humanityId, owner, expirationTime, humanityClaimed, _bridgeGateway);
    }
    function receiveUpdate(
        address _owner,
        bytes20 _humanityId,
        uint40 _expirationTime,
        bool _isActive
    ) external override allowedGateway(msg.sender) {//@check-anyone able to call this?
        CrossChainHumanity storage humanity = humanityData[_humanityId];
    
        // Clear humanityId for past owner
        delete accountHumanity[humanity.owner];
    
        if (_isActive) {
            accountHumanity[_owner] = _humanityId;
            humanity.owner = _owner;
        } else delete humanity.owner;
    
        humanity.expirationTime = _expirationTime;
    
        // If it received update from another chain `isHomeChain` flag is marked as false
        //         in order to avoid bridging state update of an removed / expired humanity
        humanity.isHomeChain = false;//@check
    
        emit UpdateReceived(_humanityId, _owner, _expirationTime, _isActive);
    }
  2. Revised Code File (Optional)

    • To mitigate this issue, additional checks should be implemented to ensure that only the rightful owner or an explicitly authorized entity can update the humanity status.
clesaege commented 2 months ago

This means that any authorized gateway can update the humanity status of any humanity ID, regardless of whether they have the rightful authority to do so.

If they are authorized, they have the rightful authority to do so.

batmanBinary commented 2 months ago

@clesaege , i think there is a misunderstanding.please consider the below example.

Legitimate User Action:

image

Exploitation by Attacker Attacker Observes the Pending Transaction:

Bob, an attacker, observes Alice's pending transaction on the blockchain. Bob sees the parameters Alice used: _owner, _humanityId, _expirationTime, and _transferHash. Attacker Front-Runs the Transaction:

Bob quickly prepares his own transaction to the Foreign Bridge contract, calling requireToPassMessage() with the following parameters:

Executing the Transfer on the Home Side:

The Home Bridge contract decodes the message and calls receiveTransfer() on the CrossChainProofOfHumanity contract with Bob's parameters. The receiveTransfer() function sets Bob as the owner of the humanity ID (0x12345), even though Bob is not the legitimate owner.

Result Bob successfully front-ran Alice's transaction and received ownership of Alice's humanity ID on the other chain. Alice's legitimate transfer is now invalid because the receiveTransfer function has already processed the transfer hash (0xabcdef).

@clesaege ,please let me know if you need any further details.

clesaege commented 2 months ago

Bob quickly prepares his own transaction to the Foreign Bridge contract, calling requireToPassMessage() with the following parameters:

This will not satisfy require(amb.messageSender() == foreignGateway, "!foreignGateway"); so won't work.