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 {
address[] memory tokensToWhitelist = new address[](1);
tokensToWhitelist[0] = address(666)
proxyFactory = new ProxyFactory(tokensToWhitelist)
distributor = new Distributor(address(proxyFactory), address(this));
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 {
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);
}