#40655 [BC-Medium] Malicious signers can give different votes to other Signers to prevent sBTC withdrawal
Submitted on Feb 28th 2025 at 14:44:26 UTC by @f4lc0n for Attackathon | Stacks II
Report ID: #40655
Report Type: Blockchain/DLT
Report severity: Medium
Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_1.0
Impacts:
Permanent freezing of funds (fix requires hardfork)
Description
Brief/Intro
After each signer finds a new withdrawal request, it will check it and vote on it (accept is true or false). Each signer will send its vote to other signers and save the votes received from other signers. Then, when each signer generates a withdrawal BTC transaction, the signer_bitmap (that is, votes received from other signers) will be packaged into the BTC transaction.
Then, the malicious signer can send different votes to different signers.
For example, there are 12 signers in total and the multi-signature threshold is 8.
The malicious signer sends a message with accept as
trueto 5 signers and a message with accept asfalseto the other 6 signers.Then, when the coordinator requests the BTC transaction signature from all signers, the signers will generate two different BTC transactions, representing the malicious signer's accept as
trueandfalserespectively.In the end, this BTC transaction will not be executed because at most only 6 signers' BTC transaction sighashes are the same.
Vulnerability Details
The signer/src/bitcoin/utxo.rs::WithdrawalRequest struct code is as follows. The signer_bitmap represents the votes of other signers received by the current signer.
The signer/src/bitcoin/utxo.rs::UnsignedTransaction::withdrawal_merkle_root function code is as follows. It will put the hash of all WithdrawalRequests into the BTC transaction. That is to say, if the votes received by the signer is different, the BTC transaction it generates will also be different.
Impact Details
All withdrawal BTC transactions cannot be executed, resulting in sBTC being frozen.
Since the input of withdrawal BTC transactions may be deposits, deposits may also not be executed.
References
None
Proof of Concept
Proof of Concept
Base on: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_1.0
Auditor wallet address:
ST2BEV097EV2R9ZMFRMRT904QB5RFYMA0683TC111Auditor wallet mnemonics:
spawn knee orchard patrol merge forget dust position daring short bridge elevator attitude leopard opera appear auction limit magic hover tunnel museum quantum manual
Patch
docker/stacks/stacks-regtest-miner.toml. Give the auditor address some STX for testing.Add this code to
signer/src/bin/pocinit.rs. On the basis of./signers.sh democommand, it also deposited some sBTC to the auditor address for testingAdd
pocinitbin tosigner/Cargo.tomlPatch
signer/src/request_decider.rs. In this way, we simulate thatsbtc-signer-1receivestrueaccept fromsbtc-signer-3andsbtc-signer-2receivesfalseaccept fromsbtc-signer-3. We don't patch the message sender because that is too complicated.Run
devenvand runpocinitUse Sandbox to call
sbtc-withdrawal.initiate-withdrawal-requestto trigger a withdrawal.Check the logs and you will see that every time one of
sbtc-signer-1orsbtc-signer-2throws the following error.It should be noted that the withdrawal was finally executed successfully because we only patched
sbtc-signer-1andsbtc-signer-2. In reality, the attackersbtc-signer-3only needs to refuse to sign to make the 2/3 multi-signature difficult to execute.
Was this helpful?