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

Proof of Humanity Protocol v2
2 stars 1 forks source link

any owner can receive anyone's humanity ID #157

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): 0x7313cb04938b02de4544bf87fade63830fe3b1fecc7313ffc0663d6c1066e8ee Severity: high

Description: Description\ The receiveTransfer function in CrossChainProofOfHumanity.sol has a potential vulnerability that allows any owner to receive anyone's humanity ID. This vulnerability arises because the function does not adequately verify that the _owner parameter is the intended recipient of the humanity transfer. An attacker or malicious owner could exploit this by front-running a legitimate transfer and calling the receiveTransfer function with their own address as the _owner, thereby claiming the humanity ID for themselves.

Attack Scenario\

Legitimate Transfer Initiation:

Monitoring the Blockchain:

Front-Running the Transfer:

Execution of receiveTransfer Function:


function receiveTransfer(
    address _owner,
    bytes20 _humanityId,
    uint40 _expirationTime,
    bytes32 _transferHash
) external override allowedGateway(msg.sender) {
    require(!receivedTransferHashes[_transferHash]);

    bool success = proofOfHumanity.ccGrantHumanity(_humanityId, _owner, _expirationTime);

    CrossChainHumanity storage humanity = humanityData[_humanityId];
    delete accountHumanity[humanity.owner];

    if (success) {
        accountHumanity[_owner] = _humanityId;
        humanity.owner = _owner;
        humanity.expirationTime = _expirationTime;
        humanity.isHomeChain = true;
        humanity.lastTransferTime = uint40(block.timestamp);
    }

    receivedTransferHashes[_transferHash] = true;
    emit TransferReceived(_humanityId, _owner, _expirationTime, _transferHash);
}

Granting Humanity to the Attacker:

function ccGrantHumanity(
    bytes20 _humanityId,
    address _account,
    uint40 _expirationTime
) external onlyCrossChain returns (bool success) {
    Humanity storage humanity = humanityData[_humanityId];

    if (humanity.owner != address(0x0) && block.timestamp < humanity.expirationTime) return false;

    require(humanityData[accountHumanity[_account]].requestCount[_account] == 0);

    humanity.owner = _account;
    humanity.expirationTime = _expirationTime;
    accountHumanity[_account] = _humanityId;

    emit HumanityGrantedDirectly(_humanityId, _account, _expirationTime);

    return true;
}

Humanity Not Claimed:

Updating State:

The humanity ID 0x123 is now associated with Bob's address 0xBob. The state is updated to reflect Bob as the owner of the humanity ID.

Attachments

  1. Proof of Concept (PoC) File
function receiveTransfer(//@audit-any owner can receive anyone's humanity
        address _owner,
        bytes20 _humanityId,
        uint40 _expirationTime,
        bytes32 _transferHash
    ) external override allowedGateway(msg.sender) {
        // Once transfer hash is flagged as received it is not possible to receive the transfer again
        require(!receivedTransferHashes[_transferHash]);

        // If humanity is claimed on the main contract it will return false and not override the state
        // Otherwise requires _owner to not be in process of claiming a humanity
        bool success = proofOfHumanity.ccGrantHumanity(_humanityId, _owner, _expirationTime);

        CrossChainHumanity storage humanity = humanityData[_humanityId];

        // Clear human humanityId for past owner
        delete accountHumanity[humanity.owner];

        // Overriding this data in case it is outdated
        if (success) {
            accountHumanity[_owner] = _humanityId;

            humanity.owner = _owner;
            humanity.expirationTime = _expirationTime;
            humanity.isHomeChain = true;
            humanity.lastTransferTime = uint40(block.timestamp);
        }

        receivedTransferHashes[_transferHash] = true;

        emit TransferReceived(_humanityId, _owner, _expirationTime, _transferHash);
    }
  1. Revised Code File (Optional)
clesaege commented 2 months ago

An attacker or malicious owner could exploit this by front-running a legitimate transfer and calling the receiveTransfer function with their own address as the _owner, thereby claiming the humanity ID for themselves.

We check that the sender is an allowed gateway so an attacker call would revert.

AresAudits commented 2 months ago

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

below is how the bridge works from the docs

image

now let's understand the attack scenario

Legitimate User Action:

Exploitation by Attacker

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

Executing the Transfer on the Home Side:

Result

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.