Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: medium
Valid

[H-01] Organizer can `distribute` Balance of `Proxy.sol` to any Arbitrary Address

Summary

The organizer can distribute the balance of Proxy.sol to any arbitrary address he wishes.

Vulnerability Details

Since anyone can sponsor a contest, consider the following scenario:

  1. Organizer comes up with a contest idea and submits it

  2. Contest is created and organizer funds it with a starting sum of e.g. 100USDT

  3. As time passes and the contest is not yet solved, more sponsors take part and fund the contest

  4. At the time of the contest's solution the total amount raised could be any "x" USDT - for example the original 100USDT by the organizer and an additional 400USDT from other sponsors

The organizer is the one that calls the function deployProxyAndDistribute inside ProxyFactory.sol and he passes whatever inputs he wishes. The proxy delegates the call to the _distribute function inside Distributor.sol with the parameters we used.

Let's closely examine the _distribute function inside Distributor.sol before we continue:

function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)
internal
{
// token address input check
if (token == address(0)) revert Distributor__NoZeroAddress();
if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}
// winners and percentages input check
if (winners.length == 0 || winners.length != percentages.length) revert Distributor__MismatchedArrays();
uint256 percentagesLength = percentages.length;
uint256 totalPercentage;
for (uint256 i; i < percentagesLength;) {
totalPercentage += percentages[i];
unchecked {
++i;
}
}
// check if totalPercentage is correct
if (totalPercentage != (10000 - COMMISSION_FEE)) {
revert Distributor__MismatchedPercentages();
}
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this));
// if there is no token to distribute, then revert
if (totalAmount == 0) revert Distributor__NoTokenToDistribute();
uint256 winnersLength = winners.length; // cache length
for (uint256 i; i < winnersLength;) {
uint256 amount = totalAmount * percentages[i] / BASIS_POINTS;
erc20.safeTransfer(winners[i], amount); // @audit no checks to verify off-chain with on-chain info so we can just distribute tokens to whatever address we like (including ours)
unchecked {
++i;
}
}
// send commission fee as well as all the remaining tokens to STADIUM_ADDRESS to avoid dust remaining
_commissionTransfer(erc20);
emit Distributed(token, winners, percentages, data);
}

There are no checks in place if the address he inputs for address[] memory winners is the address of the winner/s, thus he can input any EOA he himself has access to and rug the winnings.

He gets back his 100USDT + 400USDT profit of the sponsors' tokens.

One of the main points I want to showcase is that if there is a KYC process for the organizers this is less likely, but during the interview with Patrick the owners clarified that they're adopting the protocol for a lot of people that are non-educated in crypto.

Given this, it makes it more likely that a non-crypto native's EOA is compromised and able to execute such an attack.

Impact

The impact is loss of funds for the sponsors and leads to reputational damage to the protocol as a whole.

Tools Used

VSCode, Manual Review

Recommendations

Incorporate checks inside the _distribute function to compare the addresses of the winner/s address/es with the ones that are being input by the organizer.

Support

FAQs

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