The factory function createAccount()
creates a new account contract for the user using CREATE2. We show that an attacker can set allowances before calling selfdestruct
, impacting subsequent legitimate users who redeploy the account at the same address. This allows an attacker to drain funds from accounts deployed at these addresses.
The createDeterministicERC1967
function takes three parameters (msg.value
, ACCOUNT_IMPLEMENTATION
, and actualSalt
) to deploy a new instance of a contract. All three parameters are deterministic and can be read on-chain. Although the actualSalt
is derived from the hash of the calldataLength
, it is still possible to compute this.
During network congestion, transactions can sit in the mempool before being included in a block. An attacker can access these pending transactions, decode the input
field using certain libraries, and extract the necessary details.
The msg.value
is also present in the transaction details. With this information, an attacker can front-run the legitimate transaction by deploying a contract to the same address, setting allowances for themselves before calling selfdestruct
.
PoC: Setting Allowances
In createAccount
, CREATE2 salt is user-supplied, and msg.value
is also user-supplied:
The steps an attacker will need to perform are:
Determine ACCOUNT_IMPLEMENTATION
from the contract storage.
Read calldata from the mempool.
Front-run and deploy a malicious contract to the target address with the ability to set allowances.
Call selfdestruct
in the same transaction to remove the malicious contract bytecode but leave the allowances set.
An attacker can pre-compute the deterministic address using the following method:
PoC: Impact on Subsequent Users
Assuming an attacker has set allowances and called selfdestruct
, the steps for impacting subsequent users are:
A legitimate user deploys an account to the same address.
The user initializes the account, unaware that allowances have already been set.
The attacker exploits these allowances to drain funds from the user's account.
Coded unit-PoC
While we cannot provide an actual hash collision due to infrastructural constraints, we provide a coded PoC to prove the following properties of the EVM enabling this attack:
A contract can be deployed on top of an address that already had a contract before.
By deploying a contract and calling selfdestruct
in the same transaction, allowances can be set for an address that has no bytecode.
Steps to recreate:
Paste the following file onto Remix:
Deploy the contract Test
.
Run the function Test.test()
with a salt of your choice and record the returned address. The results will be:
Test.getAllowance()
for that address will return exactly APPROVE_AMOUNT
.
Test.getCodeSize()
for that address will return exactly zero.
Using the same salt, run Test.test()
again. The transaction will go through, and the result will be:
Test.test()
returns the same address as the first run.
Test.getAllowance()
for that address will return twice APPROVE_AMOUNT
.
Test.getCodeSize()
for that address will still return zero.
Complete draining of an account's funds if allowances are set and selfdestruct
is called.
The advancement of computing hardware shows that the cost of an attack is just a few million dollars, and the current Bitcoin network hashrate allows brute-forcing in about half an hour. The cost of the attack may be offset by longer brute-force times.
For DeFi protocols, it is normal for account balances to reach significant values. Such an attack is massively profitable and poses a severe risk to the protocol's users.
To mitigate this vulnerability, avoid allowing user-supplied salt, ensuring salts are securely generated on-chain, or implement additional verification steps before deploying contracts to deterministic addresses.
Valid high, given it can be executed on any chain that has a public mempool. - These two issues has similar root cause as issue #171 and duplicates, but is the only issue that highlights a valid exploit scenario that can cause a loss of funds - Issue #82 can forcefully cause funds to be stuck within the factory contract, given `createDeterministicERC1967` will not revert when a new Nexus Proxy instance is already deployed. - Issue #91 can forcefully transfer funds out of Nexus Account by front-running a creation, setting a allowance and then self destructing. Duplicating due to the following reasons: - Both issues are contigent on front-running an account creation, which is only possible since the salt use to deploy a new Nexus Proxy instance are user deployed. So the same fix of including `msg.sender` and/or a unique identifier would prevent these attacks. i.e. same root cause --> different attack paths - Both issues have similarities with issue #112 , but I would say is a unique issue given even if #112 is fixed, this issues will not be fixed
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.