Closed code423n4 closed 1 year ago
0xSorryNotSorry marked the issue as duplicate of #315
berndartmueller marked the issue as unsatisfactory: Out of scope
Hi @berndartmueller , thank you for taking the time to read this.
I believe this issue is a valid Medium-severity issue due to the following reasons:
For example, let's say the threshold is 10 signers. The first 9 signers vote "honestly" with parameter value A but the last signer (malicious) votes with some parameter value B to take over the multisig (as depicted in my POC above). This is the same as giving the power to one centralized role and completely defeats the purpose of the multi-sig.
This is even more dangerous in our case since the signer has access to the rotateSigners() function which updates the signers.
I believe the flaw is in the logic of how the MultiSig is implemented. It should be checking the hashed id of the values the signers vote for to ensure each signer is voting for the same values without any malicious input to take over the multi-sig.
(I believe this resonates with the comments provided by @pcarranzav on #333 as well)
Thank you once again for re-evaluating this.
berndartmueller marked the issue as not a duplicate
Hey @mcgrathcoutinho,
firstly, I'm marking this submission as not a duplicate of #315.
Secondly, your assumption
The MultiSigBase contract is supposed to work like a multi-sig where the power to execute a proposal or rotateSigners is dependent on the signers who vote "on a specific set of values" to meet the threshold. But the current implementation does not do that and only takes into consideration the power of the signer voting one less than the threshold.
is unfortunately not correct.
The topic
which signers vote on is based on the calldata (msg.data
), see https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L47
Your described scenario is not possible as any signer who calls the rotateSigners
with different arguments would vote for a different topic
. Thus, the submission is invalid.
Thank you for clarifying this, a very silly error from my side.
Lines of code
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L142 https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L149 https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/governance/AxelarServiceGovernance.sol#L48 https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/governance/Multisig.sol#L30
Vulnerability details
Impact
Since signers are trusted entities, a malicious signer has access to call the rotateSigners() function. This means the attacker can set a random smart contract address or his own EOA account to be a signer while erasing all the previous signers. This prevents anyone from calling the rotateSigners() function in the MultiSigBase.sol contract. Additionally, the executeMultisigProposal() function in AxelarServiceGovernance.sol contract and the execute() function in the MultiSig.sol contract will be rendered useless as no one will be able to call them. The impact of a missing check is huge here as one malicious signer can renounce on behalf of other signers as well.
Proof of Concept
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L142
A malicious signer calls this function and passes the onlySigners modifier check.
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L44
In order for the malicious signer to pass the onlySigners modifier and execute the rotateSigners() function, they need to meet two conditions:
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L119
The malicious signer monitors the vote count through the getSignerVotesCount() function to perfectly call the rotateSigners() function when the vote count is one less than the threshold. This allows the signer to enter the rotateSigners() function since threshold is met.
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/auth/MultisigBase.sol#L149
The rotateSigners() function in turn calls this _rotateSigners() internal function. In this function:
This way a malicious signer can freeze functions relying on the onlySigners() modifier.
Tools Used
Manual Review
Recommended Mitigation Steps
One thing we can notice in this issue is that everytime a signer votes (i.e. basically calling the rotateSigners() function to increase vote count in onlySigners() modifier), the modifier does not check whether all the signers are voting for the same parameter values (address[] memory newAccounts and uint256 newThreshold). This is what allows the malicious signer to vote differently than the rest of the signers, thus allowing the signer to set some random contract address or his own EOA as the sole signer.
The solution to this problem would be to:
function setProposalHash(address[] memory newAccounts) external onlySigners { if(proposalHash != 0x) revert proposalOngoing(); proposalHash = keccak256(abi.encodePacked(newAccounts)); //emit event here }
Assessed type
Governance