The protocol owner can withdraw all funds from the bridge
Description
Users can transfer funds across chains through Bridges installed on Ethereum, EVM-compatible chains, and Mina. When a user transfers funds to a bridge on one chain using the lock
function, a backend system provides a message signed by a validator containing information about the transferred fund's amount, recipient, and fee. Using this message, the bridge on the other chain allows the user to receive the funds through the unlock
function.
Fee deduction occurs during the unlock
process on each bridge. For example, when transferring funds from Mina to Ethereum, the fee is sent to the owner when the user calls the unlock
function on the Ethereum bridge, and the user receives the remaining amount. However, in the case of transferring funds from Ethereum to Mina, there is no fee deduction logic in place. This design decision was made by Sotatek to avoid the protocol owner incurring costs when transferring fees collected on the Mina bridge to Ethereum. Instead of deducting fees within the Mina bridge contract, the backend pre-deducts the fee amount, ensuring the user can only unlock the net amount. The owner can then withdraw the fees recorded by the backend using the withdrawETH
function.
function withdrawETH(uint256 amount) public onlyOwner {
require(address(this).balance >= amount, "Bridge: insufficient balance");
payable(msg.sender).transfer(amount);
}
However, since the fee amounts accumulated from Ethereum-to-Mina bridging are stored only in the backend and not on-chain, the withdrawETH
function does not have a mechanism to limit the amount of ether an admin can withdraw.
Impact
The owner can theoretically withdraw the entire balance, including funds that users are supposed to claim from the bridge. This requires unnecessary extra trust on the protocol from users. Furthermore, if a security incident such as a key compromise occurs, the protocol could be drained.
Recommendations
Define the fee rate for Ethereum-to-Mina bridging within the contract, and modify the lock
function to allow users to pass the fee amount as an argument when calling the function. The lock
function should then verify whether the user has set a sufficient fee. If the fee is adequate, the specified amount of fee can be directly transferred to the admin. This ensures that the admin receives the exact fee amount without needing to manually withdraw the fees limitlessly.
Remediation
Sotatek acknowledged the finding and provided the following comment:
Since this is a business requirement for us, we will keep it in this mainnet version for now. Will remove and replace it with the next version.