Sparkn

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

The `_distribute` function of **ProxyFactory** contract can arbitrarily call any function of the proxy

Summary

The contest organizer(with deployProxyAndDistribute and deployProxyAndDistributeBySignature functions) and the ProxyFactory owner(with deployProxyAndDistributeByOwner and distributeByOwner functions) can call the proxy with arbitrary data: (bool success,) = proxy.call(data);
The contract ProxyFactory through the functions deployProxyAndDistribute, deployProxyAndDistributeBySignature, deployProxyAndDistributeByOwner and distributeByOwner should only have the possibility of calling the distribute function of the implementation(Distributor) contract

Vulnerability Details

The proxy implementation(Distributor contract) only has an external function but new implementations could have more external/public functions, they could even be upgradeable contracts("If we want to upgrade the implementation contract we can deploy a new one and change the implementation address of proxy contract.")

For example:

pragma solidity 0.8.18;
import {Test} from "forge-std/Test.sol";
import {ProxyFactory} from "../../src/ProxyFactory.sol";
import {Distributor} from "../../src/Distributor.sol";
contract PoC is Test {
bytes32 idAlice = keccak256("Alice");
address alice = address(uint160(uint256(idAlice)));
ProxyFactory proxyFactory;
Distributor distributor;
function setUp() public {
// Deploy proxyFactory and distributor
address[] memory tokensToWhitelist = new address[](1);
tokensToWhitelist[0] = address(666);
proxyFactory = new ProxyFactory(tokensToWhitelist);
distributor = new Distributor(address(proxyFactory), address(this));
// Set alice's contest
proxyFactory.setContest(alice, idAlice, block.timestamp, address(distributor));
}
function test() public {
// Alice call `getConstants` instead if the `distribute` function
bytes memory data = abi.encodeWithSelector(Distributor.getConstants.selector);
vm.prank(alice);
proxyFactory.deployProxyAndDistribute(idAlice, address(distributor), data);
}
}

Impact

The contract ProxyFactory through the functions deployProxyAndDistribute, deployProxyAndDistributeBySignature, deployProxyAndDistributeByOwner and distributeByOwner bring the possibility to arbitrarily call any function of the proxy

Recommendations

Declare an interface for the implementation:

interface Distributor {
function distribute(bytes memory data) external;
}

Implement in Distributor contract:

function distribute(bytes memory data)
external
{
if (msg.sender != FACTORY_ADDRESS) {
revert Distributor__OnlyFactoryAddressIsAllowed();
}
(address token, address[] memory winners, uint256[] memory percentages, bytes memory data) =
abi.decode(data, (address, address[], uint256[], bytes));
_distribute(token, winners, percentages, data);
}

Use this signature when call the proxy:

@@ -247,7 +247,7 @@ contract ProxyFactory is Ownable, EIP712 {
/// @param proxy The proxy address
/// @param data The prize distribution data
function _distribute(address proxy, bytes calldata data) internal {
- (bool success,) = proxy.call(data);
+ (bool success,) = proxy.call(abi.encodeWithSelector(Distributor.distribute.selector, data));
if (!success) revert ProxyFactory__DelegateCallFailed();
emit Distributed(proxy, data);
}

Support

FAQs

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