Platform: Code4rena
Start Date: 11/01/2023
Pot Size: $60,500 USDC
Total HM: 6
Participants: 69
Period: 6 days
Judge: Trust
Total Solo HM: 2
Id: 204
League: ETH
Rank: 18/69
Findings: 2
Award: $311.49
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: AkshaySrivastav
Also found by: 0xjuicer, Bauer, Tajobin, adriro, csanuragjain, gzeon, immeas, rbserver
275.2478 USDC - $275.25
Attacker can replay KYCRegistry.addKYCAddressViaSignature to reinstate a previously revoked KYC address. Since KYC is an important part of Ondo Finance, the risk is relatively high. This issue is partially mitigated assuming the deadline is reasonably set.
function addKYCAddressViaSignature( uint256 kycRequirementGroup, address user, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(v == 27 || v == 28, "KYCRegistry: invalid v value in signature"); require( !kycState[kycRequirementGroup][user], "KYCRegistry: user already verified" ); require(block.timestamp <= deadline, "KYCRegistry: signature expired"); bytes32 structHash = keccak256( abi.encode(_APPROVAL_TYPEHASH, kycRequirementGroup, user, deadline) ); // https://eips.ethereum.org/EIPS/eip-712 compliant bytes32 expectedMessage = _hashTypedDataV4(structHash); // `ECDSA.recover` reverts if signer is address(0) address signer = ECDSA.recover(expectedMessage, v, r, s); _checkRole(kycGroupRoles[kycRequirementGroup], signer); kycState[kycRequirementGroup][user] = true; emit KYCAddressAddViaSignature( msg.sender, user, signer, kycRequirementGroup, deadline ); }
address[] addresses; function test_signature_replay() public { test_signature_add_happy_case(); addresses.push(user1); vm.startPrank(signer); registry.removeKYCAddresses(1, addresses); vm.stopPrank(); assertEq(registry.getKYCStatus(1, user1), false); test_signature_add_happy_case(); }
Foundry
Add a nonce or store used sig
#0 - c4-judge
2023-01-22T15:56:17Z
trust1995 marked the issue as duplicate of #187
#1 - c4-judge
2023-01-23T11:01:20Z
trust1995 marked the issue as satisfactory
🌟 Selected for report: CodingNameKiki
Also found by: 0x1f8b, 0x52, 0x5rings, 0xAgro, 0xSmartContract, 0xcm, 0xkato, 2997ms, Aymen0909, BClabs, BPZ, BRONZEDISC, Bauer, Bnke0x0, Deekshith99, IllIllI, Josiah, Kaysoft, RaymondFam, Rolezn, SaeedAlipoor01988, Tajobin, Udsen, Viktor_Cortess, adriro, arialblack14, betweenETHlines, btk, chaduke, chrisdior4, cryptphi, csanuragjain, cygaar, defsec, descharre, erictee, gzeon, hansfriese, horsefacts, joestakey, koxuan, lukris02, luxartvinsec, nicobevi, oyc_109, pavankv, peanuts, rbserver, scokaf, shark, tnevler, tsvetanovv, zaskoh
36.2377 USDC - $36.24
/** * @notice Remove addresses from KYC list * * @param kycRequirementGroup KYC group associated with `addresses` * @param addresses List of addresses to revoke KYC'd status */ function removeKYCAddresses( uint256 kycRequirementGroup, address[] calldata addresses ) external onlyRole(kycGroupRoles[kycRequirementGroup]) { uint256 length = addresses.length; for (uint256 i = 0; i < length; i++) { kycState[kycRequirementGroup][addresses[i]] = false; } emit KYCAddressesRemoved(msg.sender, kycRequirementGroup, addresses); }
Since function like claimMint and transfer require KYC, user fund might be stuck
function claimMint( address user, uint256 epochToClaim ) external override updateEpoch nonReentrant whenNotPaused checkKYC(user) {
It is possible to add an already existing address or remove a non-existing address, emitting confusing events.
/** * @notice Add addresses to KYC list for specified `kycRequirementGroup` * * @param kycRequirementGroup KYC group associated with `addresses` * @param addresses List of addresses to grant KYC'd status */ function addKYCAddresses( uint256 kycRequirementGroup, address[] calldata addresses ) external onlyRole(kycGroupRoles[kycRequirementGroup]) { uint256 length = addresses.length; for (uint256 i = 0; i < length; i++) { kycState[kycRequirementGroup][addresses[i]] = true; } emit KYCAddressesAdded(msg.sender, kycRequirementGroup, addresses); } /** * @notice Remove addresses from KYC list * * @param kycRequirementGroup KYC group associated with `addresses` * @param addresses List of addresses to revoke KYC'd status */ function removeKYCAddresses( uint256 kycRequirementGroup, address[] calldata addresses ) external onlyRole(kycGroupRoles[kycRequirementGroup]) { uint256 length = addresses.length; for (uint256 i = 0; i < length; i++) { kycState[kycRequirementGroup][addresses[i]] = false; } emit KYCAddressesRemoved(msg.sender, kycRequirementGroup, addresses); }
This allow manager to execute arbitrary call, including transferring token in the contract.
(bool success, bytes memory ret) = address(exCallData[i].target).call{ value: exCallData[i].value }(exCallData[i].data);
#0 - c4-judge
2023-01-23T11:00:53Z
trust1995 marked the issue as grade-b