26110 - [SC - Insight] All the funds from the DepositProxy contracts c...
Submitted on Nov 25th 2023 at 02:03:18 UTC by @savi0ur for Boost | DeGate
Report ID: #26110
Report type: Smart Contract
Report severity: Insight
Target: https://etherscan.io/address/0x54D7aE423Edb07282645e740C046B9373970a168#code
Impacts:
Direct theft of funds exceeding 1,000,000 USD from the Default Deposit Contract
Description
Bug Description
We can see below are the two separate transactions that are initiated after DepositProxy contract deployment.
As can be seen on-chain, there are two separate tx : 1) for upgrading implementation address of the proxy and 2) for initializing that implementation contract from proxy with an exchange address.
Due to the protocol is using proxy/implementation pattern for upgradeability support, there is always a need to have an initialize() in implementation contract and it needs to be called in the context of proxy to initialize implementation contract.
This is the definition of initialize():
To initialize DepositProxy's implementation contract, there is a initialize(address _exchange) which takes an address of exchange. However, as we can see, this initialize() can be frontrun and attacker can initialize exchange with an attacker controlled address.
Once attacker frontrun and set an exchange to an attacker controlled address, he/she can bypass the check - onlyExchange defined on withdraw() from proxy's implementation and can steal all funds stored inside DepositProxy.
For the future upgrades, proxy contract will remain same but implementation contract will be changing. Hence, proxy will need to upgrade and initialize again with new implementation address.
Since Proxy's storage will not be changing, so the token's balances. As we can see, if such future upgrades happened with two separate tx - upgradeTo tx and initialize tx. Attacker can frontrun initialize tx OR backrun upgradeTo tx to initialize proxy's implementation with an attacker controlled address and then can easily steal all the funds that are already there in proxy contract.
Impact
All the funds from the DepositProxy contracts can be stolen by frontrunning initialize() transaction of the proxy's implementation and transferring all the funds from proxy contract to attacker controlled address.
Risk Breakdown
Difficulty to Exploit: Very Easy
Recommendation
I recommend to initialize proxy's implementation at the time of upgrade using only upgradeToAndCall() instead of having two tx - 1) upgradeTo() and then 2) initialize().
We should upgrade and initialize proxy contract by passing implementation address and data as abi.encodeWithSignature("initialize(address)", exchange_address) to upgradeToAndCall().
References
DepositProxy : https://etherscan.io/address/0x54D7aE423Edb07282645e740C046B9373970a168 DepositProxy Implementation : https://etherscan.io/address/0x8ccc06c4c3b2b06616eee1b62f558f5b9c08f973 UpgradeTo tx - https://etherscan.io/tx/0xb1e5bc43a9a516618be17e0075ca12b7420b5daa42e377af2906a2c8d9619bdc Initialize tx - https://etherscan.io/tx/0xd263c3c19fedb4a8765b8facd6bce1d1609b940d415d460351b590edba371970
Proof Of Concept
NOTE:
For simplicity of POC, i am doing a prank on
proxy ownerfor upgradeTo transaction.
Steps to Run using Foundry:
Install Foundry (https://book.getfoundry.sh/getting-started/installation)
Open terminal and run
forge init pocandcd pocDownload implementation contract source using command
cast etherscan-source -d src --etherscan-api-key $ETHERSCAN_API_KEY 0x8CCc06C4C3B2b06616EeE1B62F558f5b9C08f973Modify
initialize()fromDefaultDepositContract.solfor showing an upgrade as new implementation as shown below (removed checkexchange == address(0)as exchange was set in previousinitialize(), so it cant be zero anymore).
Paste following foundry code in TestPoC.t.sol
Run using
forge test -vv
Console Output:
Last updated
Was this helpful?