DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Reuse of Same Merkle Root for Different Unripe Tokens Leads to Potential Fraudulent Claims

Summary

The addUnripeToken function in the UnripeFacet allows the addition of new Unripe Tokens. However, it does not check if the Merkle root has already been used for a different Unripe Token. This can lead to potential fraudulent claims, as malicious users can reuse Merkle proofs across different tokens that share the same root, compromising the integrity and security of the protocol.

Proof of concept

1: Creating and Adding Unripe Tokens:

i: The malicious user adds multiple Unripe Tokens using the same Merkle root:

  • unripeToken1 = 0xTokenAddress1

  • unripeToken2 = 0xTokenAddress2

  • root = 0xSharedRoot

2: Generating a Merkle Proof:

  • The malicious user generates a Merkle proof for an entry that is valid under the shared Merkle root. This proof is initially intended for 0xTokenAddress1.

3: Reusing the Merkle Proof:

  • The user then submits the same Merkle proof for 0xTokenAddress2, which also shares the same root, making fraudulent claims across different tokens possible.

Test

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../contracts/UnripeFacet.sol";
import "../contracts/libraries/LibAppStorage.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestDuplicateMerkleRoot is Test {
UnripeFacet unripeFacet;
ERC20 unripeToken1;
ERC20 unripeToken2;
AppStorage s;
function setUp() public {
unripeFacet = new UnripeFacet();
unripeToken1 = new ERC20("Unripe Token 1", "URT1");
unripeToken2 = new ERC20("Unripe Token 2", "URT2");
// Initialize other necessary contracts and states
}
function testDuplicateMerkleRootAddition() public {
address unripeTokenAddress1 = address(unripeToken1);
address unripeTokenAddress2 = address(unripeToken2);
address underlyingToken = address(new ERC20("Ripe Token", "RPT"));
bytes32 sharedRoot = keccak256(abi.encodePacked("shared merkle root"));
// Add Unripe Token 1
unripeFacet.addUnripeToken(unripeTokenAddress1, underlyingToken, sharedRoot);
// Add Unripe Token 2 with the same Merkle root
unripeFacet.addUnripeToken(unripeTokenAddress2, underlyingToken, sharedRoot);
// Check if both tokens have the same Merkle root
UnripeTokenSettings memory settings1 = s.sys.silo.unripeSettings[unripeTokenAddress1];
UnripeTokenSettings memory settings2 = s.sys.silo.unripeSettings[unripeTokenAddress2];
assertEq(settings1.merkleRoot, sharedRoot, "Merkle root for Token 1 should match shared root");
assertEq(settings2.merkleRoot, sharedRoot, "Merkle root for Token 2 should match shared root");
}
function testMerkleRootReuseExploit() public {
address unripeTokenAddress1 = address(unripeToken1);
address unripeTokenAddress2 = address(unripeToken2);
address underlyingToken = address(new ERC20("Ripe Token", "RPT"));
bytes32 sharedRoot = keccak256(abi.encodePacked("shared merkle root"));
// Add Unripe Token 1 and 2 with the same Merkle root
unripeFacet.addUnripeToken(unripeTokenAddress1, underlyingToken, sharedRoot);
unripeFacet.addUnripeToken(unripeTokenAddress2, underlyingToken, sharedRoot);
// Generate a valid proof for Token 1
bytes32[] memory proof = generateValidProof(sharedRoot, unripeTokenAddress1);
// Reuse the same proof for Token 2
bool success = false;
try unripeFacet.claim(unripeTokenAddress2, proof) {
success = true;
} catch {}
assertTrue(success, "Fraudulent claim using reused Merkle proof should succeed");
}
function generateValidProof(bytes32 root, address token) internal returns (bytes32[] memory) {
// Mock function to generate a valid Merkle proof
bytes32[] memory proof;
proof[0] = root;
proof[1] = keccak256(abi.encodePacked(token));
return proof;
}
}

Impact

  • Fraudulent claims can result in significant financial losses and undermine the security and fairness of the protocol.

  • Compromised claims verification can lead to widespread exploitation and loss of user trust.

Tools Used

Manual Review

Recommendations

  • include a check to ensure that the Merkle root is not already in use by another Unripe Token.

function addUnripeToken(
address unripeToken,
address underlyingToken,
bytes32 root
) external payable fundsSafu noNetFlow noSupplyChange nonReentrant {
LibDiamond.enforceIsOwnerOrContract();
AppStorage storage s = LibAppStorage.diamondStorage();
// Check if the Unripe Token already exists
require(s.sys.silo.unripeSettings[unripeToken].underlyingToken == address(0), "Unripe token already exists");
// Check if the Merkle root is already in use
for (address token in s.sys.silo.unripeSettings) {
require(s.sys.silo.unripeSettings[token].merkleRoot != root, "Merkle root already in use");
}
// Add the Unripe Token settings
s.sys.silo.unripeSettings[unripeToken] = UnripeTokenSettings({
underlyingToken: underlyingToken,
merkleRoot: root,
balanceOfUnderlying: 0 // Initially set to 0 or some default value
});
emit AddUnripeToken(unripeToken, underlyingToken, root);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational/Gas

Invalid as per docs https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.