#40770 [BC-Low] Unvalidated withdrawal events allow data manipulation and denial of service in Emily
Submitted on Mar 3rd 2025 at 15:03:52 UTC by @Cartel for Attackathon | Stacks II
Report ID: #40770
Report Type: Blockchain/DLT
Report severity: Low
Target: https://github.com/stacks-network/sbtc/tree/immunefi_attackaton_1.0
Impacts:
API crash preventing correct processing of deposits
Temporarily Freezing Network Transactions
Description
Brief/Intro
The Emily service blindly trusts and processes withdrawal events received from signers without verifying their authenticity or correctness. This allows a malicious signer to manipulate legitimate withdrawal data or inject fake withdrawal records, ultimately corrupting Emily’s database and enabling a DoS attack.
Vulnerability Details
Whenever a user initiates a withdrawal on Stacks, a withdrawal-create event is emitted by sbtc-registry.clar.new_block.rs listens for these emitted events, writes them to the signers' database, and also informs Emily:
// Create any new withdrawal instances. We do this before performing any updates
// because a withdrawal needs to exist in the Emily API database in order for it
// to be updated.
emily_client
.create_withdrawals(created_withdrawals)
.await
.into_iter()
.for_each(|create_withdrawal_result| {
if let Err(error) = create_withdrawal_result {
tracing::error!(%error, "failed to create withdrawal in Emily");
}
});
// Execute updates in parallel.
let futures = vec![
emily_client
.update_deposits(completed_deposits)
.map(UpdateResult::Deposit)
.boxed(),
emily_client
.update_withdrawals(updated_withdrawals)
.map(UpdateResult::Withdrawal)
.boxed(),
];The problem here is that Emily does not validate the correctness of the events received from the signers upon create_withdrawals and update_withdrawals.
A malicious signer can manipulate legitimate withdrawal requests, altering parameters such as the amount, status, and more.
Since a malicious signer can monitor the Stacks mempool, they can detect legitimate withdrawal requests as they appear in the mempool and immediately call
create_withdrawalson Emily before other signers, using the same withdrawal-related data (such as the requestId and other identifiers), but for example with a manipulated amount set to 0. It also does not matter if the signer is the coordinator or not, they can do this anytime.Additionally, the malicious signer can also update a withdrawal request in Emily database arbitrarily by calling
update_withdrawalsfunction.
Furthermore, a malicious signer can inform Emily about non-existent withdrawal requests, which Emily will blindly process and record. This allows an attacker to flood Emily with a large number of junk requests, ultimately causing it to run out of memory and fill its database with junk data. Since the malicious signer can send a large number of requests within a single call, they can DoS Emily with just a few calls.
Impact Details
A malicious signer can:
Manipulate data for all legitimate withdrawal requests in Emily’s database.
Cause a DoS by exhausting Emily's memory and filling its database with junk data.
References
None
Proof of Concept
Proof of Concept
In this PoC, we modify the amount and status of a legitimate withdrawal request (emitted and caught by new_block.rs), and we also inject 999 junk records (each being a copy of that legitimate request) into Emily’s database.
To test the scenario please apply the following changes.
Changes to new_block.rs:
Add the following test case to sbtc/signer/tests/integration/stacks_events_observer.rs:
Run the test:
Results:
Was this helpful?