#38605 [BC-Low] Lack of fee_rate/last_fees validation in handle_bitcoin_pre_sign_request ebables rog
Submitted on Jan 7th 2025 at 18:34:32 UTC by @niroh for Attackathon | Stacks
Report ID: #38605
Report Type: Blockchain/DLT
Report severity: Low
Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_0.9/signer
Impacts:
Direct loss of funds
Description
Brief/Intro
As part of the sweep tx creation/validation process, the coordinator creates a transaction package and sends it for signers to pre-approve. If the package is approved by enough signers, the package transactions are then signed and broadcasted. Package pre approval is handled by signers in handle_bitcoin_pre_sign_request. Signers validate every significant detail in the package through their own db/client-connections to avoid trusting the coordinator who, as a single entity, is not trusted and may try to compromize the system.
One exception however is the fee_rate and last_fees which are provided at part of the PreSignRequest and are not validated by the signers. This enables a rogue signer to maliciously cause financal loss to depositors by spoofing the current fee_rate required by the bitcoin blockchain, or by spoofing last_fees even though no existing package is in the mempool (both having the same effect of imposing an unneccesarily exccesive fee on depositors).
Vulnerability Details
Attack scenario:
A rogue signer decides to compromize the sbtc system's reputation/reliability by causing financial loss to depositors.
Whenever its this signer's turn to be a coordinator, they search through all current valid, pending deposits for deposit requests with very high max_fee settings.
If the signer finds such requests, they "spoof" the fee_rate retrieved from the bitcoin client to a much higher value. The signer chooses a value that is just high enough to extract the maximum fee posiible from the deposit/s without failing the minimum max_fee validation test.
The signer constructs a transaction package with the spoofed fee_rate. Given the bloated fee_rate, deposit requests with a conservative max_fee setting will be filtered out, leaving only those with high enough max_fee to pass the max_fee validation even with the spoofed fee_rate.
The Signer then sends a preSignRequest for the package. Since the only invalid data in the package is the fee_rate (which in itself is not validated by signers) the package is approved and signed.
A sweep transaction is sent with an abnormally high fee_rate that can drain the entire deposit amount.
Impact Details
Financial loss caused to depositors: their deposit is processed but with a fee_rate that is much higher than the real chain fee_rate causing them to pay a fee that is orders of magnitude higher than they should have.
Recommendation
fee_rate validation
While the exact fee_rate might change between the time the Coordinator samples it and the time the signers validate the PreSignRequest, you could check for some reasonable deviation between the two (e.g. fee_rate given in the preSignRequest is within 20% of the one obtained by the signer).
last_fee validation
Since the same attack can be achieved by spoofing a last_fee, signers should also validate last_fee, obtaining it in the same way the coordinator does (should have the same value as provided by the coordinator).
References
https://github.com/stacks-network/sbtc/blob/83b316c5d26a3434a1b53d558bc7f899ce6c03f2/signer/src/transaction_coordinator.rs#L1317 https://github.com/stacks-network/sbtc/blob/83b316c5d26a3434a1b53d558bc7f899ce6c03f2/signer/src/bitcoin/validation.rs#L224
Proof of Concept
Proof of Concept
This POC simulates a scenario where a single deposit request is pending with max_fee = 1602000 and amount = 1682100. The coordinator sets a fake fee_rate of 6000 (the maximum that will still enable the deposit request to pass the max_fee validation). The depositor ends up paying a fee of 1,410,000 (~$1373), about x100 what they would pay if the real fee_rate (typically 60 on the devenv) was used.
How to run
Make the following temporary change in sbtc/signer/src/bitcoin/transaction_coordianator.rs line 1227:
Create a backup for demo_cli.rs and replace its content with the code below. Main changes: a. Instead of setting the deposit tx amount to the sum of the call parameters 'amount' and 'max_fee', we assume the given 'amount' parameter represents the total amount and set the deposit amount to 'amount' only. b. A donation is added at the start of exec_deposit because we call demo_cli directly and not through the shell script that handles donation.
Rebuild the docker:
docker compose -f docker/docker-compose.yml --profile default --profile bitcoin-mempool --profile sbtc-signer buildRun the devnet docker with make devenv-up
Once the system is up, run ./signers.sh info to get the SIGNERS_KEY
run
cargo run -p signer --bin demo-cli deposit --amount 1682100 --max-fee 1602000 --lock-time 50 --stacks-addr ST2SBXRBJJTH7GV5J93HJ62W2NRRQ46XYBK92Y039 --signer-key SIGNERS_KEYCheck the local bitcoin explorer (localhost:8083) and see that the sweep transaction is executed with a fee of 1,410,100 (~$1373).
Check the Stacks local explorer and see that a complete transaction was executed, hoever the amount minted to the recipient is only 272,100.
Last updated
Was this helpful?