Summary
The referral system has a critical flaw where users can register with empty referral codes. This allows attackers to fraudulently claim rewards without valid referrals, risking financial losses and damaging trust in the system.
Vulnerability Details
Technical Breakdown
The registerReferral
function lacks validation for empty referral codes:
function registerReferral(
bytes calldata referrerCode,
address referrerAddress,
bytes calldata referralCode,
bool isCustomReferralCode
) external onlyRegisteredEngines {
if (referralCode.length != 0) {
}
emit LogReferralSet(...);
}
This skips validation for empty codes and logs them as valid, making fraud detection difficult.
POC(Proof Of Concept)
pragma solidity ^0.8.25;
import {Test} from "forge-std/Test.sol";
import {Referral} from "@zaros/referral/Referral.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {Vm} from "forge-std/Vm.sol";
contract ReferralZeroAddressEngineTest is Test {
Referral referral;
Referral implementation;
ERC1967Proxy proxy;
address owner = makeAddr("zaros");
function setUp() external {
implementation = new Referral(); bytes memory initData = abi.encodeWithSelector(Referral.initialize.selector, owner);
proxy = new ERC1967Proxy(address(implementation), initData);
referral = Referral(address(proxy));
}
function test_revertOnEmptyReferralCodePreventsFraud() public {
address attacker = makeAddr("attacker");
address engine = makeAddr("engine");
bytes memory emptyCode = "";
vm.prank(owner);
referral.configureEngine(engine, true);
vm.prank(engine);
vm.expectRevert("InvalidReferralCode");
referral.registerReferral(
abi.encode(attacker),
attacker,
emptyCode,
false
);
(bytes memory code, ) = referral.getUserReferralData(abi.encode(attacker));
assertEq(code.length, 0, "Fraudulent empty code accepted");
}
}
Impact
Financial Loss: Attackers drain rewards meant for legitimate users.
Fake Data: Empty codes corrupt referral tracking.
Lost Trust: Users lose confidence in the system.
Tools Used
Foundry: Automated tests to detect empty code acceptance.
Manual Code Review: Identified missing validation checks.
Recommendations
1. Block Empty Codes:
Add a validation check to reject empty codes:
function registerReferral(...) external onlyRegisteredEngines {
require(referralCode.length > 0, "InvalidReferralCode");
emit LogReferralSet(...);
}
2.Add Test Cases:
Verify empty codes are rejected:
function test_emptyReferralCodeReverts() public {
address attacker = makeAddr("attacker");
bytes memory emptyCode = "";
vm.prank(registeredEngine);
vm.expectRevert("InvalidReferralCode");
referral.registerReferral(
abi.encode(attacker),
attacker,
emptyCode,
false
);
(bytes memory code, bool isCustom) = referral.getUserReferralData(abi.encode(attacker));
assertEq(code.length, 0, "Empty code was accepted");
}
Improve Event Logging:
Separate events for valid/invalid attempts:
event ValidReferralSet(...);
event InvalidReferralAttempt(...);
function registerReferral(...) external {
if (referralCode.length == 0) {
emit InvalidReferralAttempt(...);
revert("InvalidReferralCode");
}
emit ValidReferralSet(...);
}