Frontrunning in Reorgs

1 year ago 37
BOOK THIS SPACE FOR AD
ARTICLE AD

Jelle Gerbrandy

On April 19 we at Hats.Finance received a bug report through our Bounty Vault from Nik (@nik_eth_dev on twitter and telegram). Nik has been an appreciated contributor in the audit competitions we run, and we thank him for the report.

In this article, we explore the details of the vulnerability, its practical implications, the proposed fix, and the subsequent decision on the severity and payout

The vulnerability regards our TokenLockFactory contract.
We use this contract to create TokenLock instances — these are contracts that are used for vested payouts and hold tokens that become available over time to a beneficiary. We use the token lock contract for bounty payouts (projects can choose to pay part or all of the bounty in a vested form) and we also use it to stream salaries to some of our collaborators.

The report points out a very interesting attack on our implementation of this pattern. The TokenlockFactory.createTokenLock() function uses OpenZeppelin’s Clones.clone() function to create new TokenLock proxy instances. The starting observation is that the clone()
function uses the create opcode to deploy a new proxy contract. This new proxy contract will then be available at an address that is calculated from the deployer’s address (i.e. the address of the factory contract) and a nonce (that represents how many previous contracts the factory has created).

So here is a possible attack:

Ann calls createTokenLock to create a TokenLock at address 0x1234.. with Bill as a beneficiaryAnn funds the contract by sending tokens to 0x1234..A block reorg happens and the chain gets reset to a block before the contract was created in step 1.An attacker front-runs Ann’s transaction from step 1 to create a TokenLock at address 0x1234.. and sets themselves as the beneficiaryAfter Ann’s transaction from step 1 is mined, the transaction from step 2. is mined as well and her tokens are sent to 0x1234..Profit! for the attacker who now controls the funds

This attack scenario describes a case in which an attacker takes all the funds send to a TokenLock contract. However, it is also an attack that is extremely difficult to execute in practice. First of all, it assumes that the funding of the token lock happens in a transaction that is separate from the creation of the token lock happens, and that the funder does not check if the destination contract is configured correctly — i.e. it is only when a user uses our contracts in a very specific way that there are actual funds at risk. Another observation is that the attack is only possible if a reorg happens at exactly the right moment — and block reorganisations are extremely rare in Proof-of-Stake.

That said, the vulnerability, although difficult to exploit, exists, and the fix is straightforward.

The limits of using createare known, and the EVM offers an alternative way of calculating contract addresses that is more suitable for our use case and makes the attack not feasible.

Instead of using create to create a new contract, we can use Clones.cloneDeterministic from the OpenZeppelin contract to create the proxy contract using the create2 opcode. These functions take a salt parameter that determines the address where the contract will be deployed. We can include some crucial configuration parameters — specifically, the beneficiary— into thesalt parameter. In practice, this means that the address of the tokenlock contract depends on the address of the beneficiary, and so cannot be hijacked by an attacker that front-runs the transaction.

The size of the bounty paid out by our vault depends on two factors: the severity of the vulnerability, and the amount that is actually at risk in production.

The primary use of the TokenLockFactory is to create contracts for payouts of bounties from our Vaults. When a bounty is paid out, the TokenLockcontract is created and funded in a single transaction — and so this procedure is not vulnerable to the attack described. We also use our contracts for paying some of our collaborators, but in these cases our procedures are such that some time (at least some days) pass between contract creation and their funding, during which the beneficiary is asked to confirm their address. So no funds are currently at risk

After some discussion, we decided to classify the bug as “low severity” (“contract does not work as expected”). According to our rules, payouts are capped by the amount “at risk in production at the time of disclosure”, which is 0. However, given that there is a small but real risk of falling victim to the exploit in the future, we decided to fix the issue, and to award the reporter with a 5,000 USDC and an NFT as a recognition for their efforts and to show our appreciation bringing our attention to such an interesting attack.

We believe that collaboration and continuous improvement are key to strengthening our platform’s security. We encourage more individuals to participate in our bug bounty and share their expertise. By doing so, you contribute to enhancing the overall security and stand a chance to earn rewards for your valuable insights.

Read Entire Article