26124 - [SC - Insight] Some owners of the MultiSigWallet can bring the...
Submitted on Nov 25th 2023 at 17:38:27 UTC by @savi0ur for Boost | DeGate
Report ID: #26124
Report type: Smart Contract
Report severity: Insight
Target: https://etherscan.io/address/0x2028834B2c0A36A918c10937EeA71BE4f932da52#code
Impacts:
Unintended functionality of multisig owners
Description
Bug Description
The vulnerability occurs due to removeOwner() and replaceOwner(). replaceOwner() allows replacing newOwner to be an address(0).
This is the removeOwner(address owner), which allows them to remove themselves. It also adjusting required = owners.length after removal of owner if required > owners.length.
Note: When required > owners.length, then required == owners.length. So future submitted transactions need 100% approvals from remaining owners inorder to get executed.
function removeOwner(address owner)
public
onlyWallet
ownerExists(owner)
{
isOwner[owner] = false;
for (uint i=0; i<owners.length - 1; i++)
if (owners[i] == owner) {
owners[i] = owners[owners.length - 1];
break;
}
owners.length -= 1;
if (required > owners.length)
changeRequirement(owners.length);
OwnerRemoval(owner);
}
modifier onlyWallet() {
if (msg.sender != address(this))
throw;
_;
}
modifier ownerExists(address owner) {
if (!isOwner[owner])
throw;
_;
}
function changeRequirement(uint _required)
public
onlyWallet
validRequirement(owners.length, _required)
{
required = _required;
RequirementChange(_required);
}This is the replaceOwner(address owner, address newOwner). Which allows owner to get replaced with newOwner.
Note: replaceOwner(address owner, address newOwner) is requiring isOwner[owner] == true and isowner[newOwner] == false, using ownerExists and ownerDoesNotExist modifier. But its not checking for newOwner to be not null. So its allowing owner to replace itself with an address(0).
Attack could happens when atleast owners.length - required + 1 number of owners are malicious. In 4/6 multisig, 6 - 4 + 1 = 3 owners and 5/6, 6 - 5 + 1 = 2 owners only are required to be malicious, which is always less than required when required > owners.length / 2.
This is an attack scenario with 4/6 multisig:
owners.length - requirednumber of owners can remove themselves usingremoveOwner(), to makerequired == owners.lengthi.e., 4 == 4.One more owner can replace itself with null address, which still hold 4 == 4 but one of the owner is now null address and no one owns this null address.
So, even though all the remaining 3 owners
confirmTransactionfor all the future transactions(say they now want to changerequired = 3usingchangeRequirement(uint _required)tx), condition toexecuteTransactioni.e.,required == #confirmation(4 == 4 confirmations) can never be satisfied.Since nothing can be upgraded, proxy have to be re-deployed with state migration from locked proxy to the new deployed proxy.
Impact
Some owners of the MultiSigWallet can bring the system in a state where future upgrade/any operation is impossible
Recommendation
We recommend to not allow any owner to get replaced with null address using replaceOwner(address owner, address newOwner) by adding a check notNull(newOwner).
References
Gnosis MultiSig - https://etherscan.io/address/0x2028834B2c0A36A918c10937EeA71BE4f932da52?utm_source=immunefi#code
Proof Of Concept
Steps to Run using Foundry:
Install Foundry (https://book.getfoundry.sh/getting-started/installation)
Open terminal and run
forge init pocandcd pocPaste following foundry code in POC.t.sol
Run using
forge test -vv
Console Output:
Last updated
Was this helpful?