#38392 [BC-High] Signer can steal STX tokens in multi-sign wallet by setting a high stacks tx fee

Submitted on Jan 2nd 2025 at 10:52:58 UTC by @f4lc0n for Attackathon | Stacks

  • Report ID: #38392

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_0.9/signer

  • Impacts:

    • Direct loss of funds

Description

Brief/Intro

When a signer acts as a coordinator, it will initiate some sBTC stacks contract calls. And he can set the tx fee for these contract calls. These tx fees will be rewarded to the miners of the stacks chain.

The problem now is that signers do not check the tx fee set by the coordinator. Therefore, a malicious signer can set a very large tx fee to reward the multi-sign wallet's STX to the stacks miner. And he can cooperate with the stacks miner to steal this amount of funds.

Vulnerability Details

The signer/src/transaction_signer.rs::handle_stacks_transaction_sign_request code is as follow.

    async fn handle_stacks_transaction_sign_request(
        &mut self,
        request: &StacksTransactionSignRequest,
        bitcoin_chain_tip: &model::BitcoinBlockHash,
        origin_public_key: &PublicKey,
    ) -> Result<(), Error> {
        let instant = std::time::Instant::now();
        let validation_status = self
            .assert_valid_stacks_tx_sign_request(request, bitcoin_chain_tip, origin_public_key)
            .await;

        metrics::histogram!(
            Metrics::ValidationDurationSeconds,
            "blockchain" => STACKS_BLOCKCHAIN,
            "kind" => request.tx_kind(),
        )
        .record(instant.elapsed());
        metrics::counter!(
            Metrics::SignRequestsTotal,
            "blockchain" => STACKS_BLOCKCHAIN,
            "kind" => request.tx_kind(),
            "status" => if validation_status.is_ok() { "success" } else { "failed" },
        )
        .increment(1);
        validation_status?;

        // We need to set the nonce in order to get the exact transaction
        // that we need to sign.
        let wallet = SignerWallet::load(&self.context, bitcoin_chain_tip).await?;
        wallet.set_nonce(request.nonce);

        let multi_sig = MultisigTx::new_tx(&request.contract_tx, &wallet, request.tx_fee);
        let txid = multi_sig.tx().txid();

        debug_assert_eq!(txid, request.txid);

        let signature = crate::signature::sign_stacks_tx(multi_sig.tx(), &self.signer_private_key);

        let msg = message::StacksTransactionSignature { txid, signature };

        self.send_message(msg, bitcoin_chain_tip).await?;

        Ok(())
    }

In the above code, it does not do any check on request.tx_fee. Therefore, a malicious signer can set any tx_fee, and all other signers will agree to this tx_fee.

Impact Details

It will cause signers multi-signature wallets to lose STX tokens.

If the malicious signer and miner cooperate, the malicious signer can benefit from it.

References

None

Proof of Concept

Proof of Concept

  1. Base on: https://github.com/stacks-network/sbtc/releases/tag/0.0.9-rc4

  2. Patch signer/src/config/mod.rs, add attacker flag config

  3. Patch signer/src/main.rs, load attacker flag

  4. Patch docker/docker-compose.yml, add attacker flag

  5. Patch signer/src/transaction_coordinator.rs, add attack action

  6. Run docker

  7. This PoC sets sbtc-signer-3 as an attacker, which will automatically attack if it is the coordinator

  8. Keep running the demo until the trigger the coordinator is sbtc-signer-3. You can observe the log of sbtc-signer-3. When "@audit; attacker set large tx fee" appears, it is triggered.

  9. Track the transaction initiated by sbtc-signer-3 on explorer, and you will find that it consumes a lot of STX

Last updated

Was this helpful?