Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Denial-of-Service Through UTXOs Flooding #546

Open
c4-bot-1 opened this issue Dec 18, 2023 · 9 comments
Open

Denial-of-Service Through UTXOs Flooding #546

c4-bot-1 opened this issue Dec 18, 2023 · 9 comments
Labels
bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue grade-a QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax satisfactory satisfies C4 submission criteria; eligible for awards sufficient quality report This report is of sufficient quality

Comments

@c4-bot-1
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/bitcoin_client.go#L736-L752

Vulnerability details

Impact

The function FetchUTXOS in bitcoin_client.go is responsible for fetching and ordering UTXOs for use by the Bitcoin TSS address. The way the UTXOs are treated and traversed opens a simple way to perform a denial-of-service attack in the client. The impact is worsened due to the small interval schedule between the potentially intensive loops and the listing of zero confirmation UTXOs.

Proof of Concept

When FetchUTXOS is called, it lists all the known UTXOs for the current Bitcoin TSS address. The RPC call listunspent can be, by itself, a bottleneck for wallets dealing with a big number of transactions and UTXOs; ~160ms, as commented, is not guaranteed for such cases. But the main issue emerges when the whole list of UTXOs is looped and sorted indiscriminately.

	// fetching all TSS utxos takes 160ms
	utxos, err := ob.rpcClient.ListUnspentMinMaxAddresses(0, maxConfirmations, addresses)
	if err != nil {
		return err
	}
	//ob.logger.WatchUTXOS.Debug().Msgf("btc: fetched %d utxos in confirmation range [0, %d]", len(unspents), maxConfirmations)

	// rigid sort to make utxo list deterministic
	sort.SliceStable(utxos, func(i, j int) bool {
		if utxos[i].Amount == utxos[j].Amount {
			if utxos[i].TxID == utxos[j].TxID {
				return utxos[i].Vout < utxos[j].Vout
			}
			return utxos[i].TxID < utxos[j].TxID
		}
		return utxos[i].Amount < utxos[j].Amount
	})

Be it through daily customised use of the address or an attacker actively targeting this vulnerability, the list of UTXOs can grow unmanageably large without a robust and recurrent UTXOs consolidation mechanism. In this case, listing zero confirmations UTXOs increases the risk by decreasing the cost of an attack, but even confirmed UTXOs can be exploited by an attacker trying to drain the network participant's resources and disrupt critical operations. Let us focus on an active attack.

Active DoS

An attacker can generate near-dust, just enough to be relayed (check dust limit concept) UTXOs to this address, spending very little funds to cause huge resources drain over time. This attack is cost-efficient even if the UTXOs are lost to the attacker, but, as it is now, the cost of the attack is highly reduced since the listed UTXOs can have zero confirmations and, thus, be "double-spent" or replaced-by-fee. Besides the almost DoS effect that can be caused by only one looping and sorting of such a huge UTXOs list, this potentially maliciously intensive operation is expected to be performed every 30 seconds.

	ticker := NewDynamicTicker("Bitcoin_WatchGasPrice", ob.GetCoreParams().GasPriceTicker)
				WatchUtxoTicker:             30,

SelectUTXOs mentions consolidated UTXOs and implements an algorithm for allegedly consolidating UTXOs. While it is true there is some consolidation performed, this is not even close to enough to deal with the problem, specially when actively exploited.

	// consolidate biggest possible UTXOs to maximize consolidated value
	// consolidation happens only when there are more than (or equal to) consolidateRank (10) UTXOs
	utxoRank, consolidatedUtxo, consolidatedValue := uint16(0), uint16(0), 0.0
	for i := len(ob.utxos) - 1; i >= 0 && utxosToSpend > 0; i-- { // iterate over UTXOs big-to-small
		if i != idx && (i < left || i >= right) { // exclude nonce-mark and already selected UTXOs
			utxoRank++
			if utxoRank >= consolidateRank { // consolication starts from the 10-ranked UTXO based on value
				utxosToSpend--
				consolidatedUtxo++
				total += ob.utxos[i].Amount
				consolidatedValue += ob.utxos[i].Amount
				results = append(results, ob.utxos[i])
			}
		}
	}

This is a clear DoS vector for clients and cannot be deemed safe without a robust and recurrent strategy to consolidate UTXOs. Requiring zero confirmations before spending the resources on the UTXOs just increases the attack efficiency and its impact.

Tools Used

Manual: code editor.

Recommended Mitigation Steps

I think the best potential solution is to implement a robust and recurrent UTXOs consolidation strategy that is not limited by a low and fixed value (e.g., maxNoOfInputsPerTx), and being defensive when processing these UTXOs in loops and any potentially resource-intensive operation.

The consolidation strategy may need to be separated from the main transactions construction and signature. It is, it could be another scheduled operation with the sole purpose of consolidating UTXOs. Though, some consideration could be given to making FetchUTXOS a bit more efficient in clearing UTXOs without affect the main funds movement.

As for being defensive when processing UTXOs, wasting resources on unsafe zero confirmations UTXOs must be completely prevented. And, when possible, avoid performing potentially resouce-intensive operations below a safely configured UTXO value threshold. But as this is not always possible and the UTXOs list need to be processed one way or another, a robust and recurrent UTXOs consolidation strategy seems to be the main course of action.

Assessed type

DoS

@c4-bot-1 c4-bot-1 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Dec 18, 2023
c4-bot-7 added a commit that referenced this issue Dec 18, 2023
@c4-pre-sort
Copy link

DadeKuma marked the issue as duplicate of #402

@c4-pre-sort c4-pre-sort added duplicate-402 sufficient quality report This report is of sufficient quality labels Dec 20, 2023
@c4-pre-sort
Copy link

DadeKuma marked the issue as sufficient quality report

@c4-judge
Copy link

c4-judge commented Jan 7, 2024

0xean changed the severity to 3 (High Risk)

@c4-judge c4-judge added 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Jan 7, 2024
@c4-judge
Copy link

c4-judge commented Jan 7, 2024

0xean marked the issue as satisfactory

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Jan 7, 2024
@ciphermarco
Copy link

Hi, @0xean. I'd like to point one detail for why this issue is not a duplicate of #402. Both have different root causes and fixes.

The root cause for #402 is that ListUnspentMinMaxAddresses is called with zero confirmations, thus accepting unconfirmed UTXOs and exposing the system to all the risks that come with that.

As presented above, the root cause for this issue #546 is the fact that the software is vulnerable to a DoS attack because it doesn't implement a defensive and functional strategy to consolidate and deal with all UTXOs, specially near-dust ones. This attack path is still open for confirmed UTXOs at a negligible cost for the attacker, and only accepting confirmed UTXOs does not fix this. The "Recommended Mitigation Steps" explains what could be done differently.

UTXO consolidation is a critical strategy for wallets, specially in this context. Bitcoin Core, and similar alternatives, implements a robust strategy to consolidate UTXOs. But, due to very specific needs, ZetaChain foregoes those protections by selecting the UTXOs that will be used to build the transaction without Bitcoin Core's coin selection algorithm. After being flooded with near-dust UTXOs without any strategy to deal with that, the DoS presented in the issue is persistent and consolidating these UTXOs in order to re-establish normal operations with the same TSS address may be prohibitively expensive; calling for more drastic measures. Here, given that the UTXOs selection is consensus-critical amongst peers in ZetaChain, the issue is even more sensitive.

A similar protection must be implemented, otherwise the risk for unintended or actively malicious DoS is inevitable. Dust limit is not part of Bitcoin's consensus, but, as shown in the issue above, we may consider that it's realistic to expect that transactions with UTXOs above 546 satoshis will be successfully relayed across the network:

An attacker can generate near-dust, just enough to be relayed (check dust limit concept) UTXOs to this address, spending very little funds to cause huge resources drain over time. This attack is cost-efficient even if the UTXOs are lost to the attacker [...]

Focusing on an attack and not normal occurrences in the daily management of the wallet, 546 and 1,000 satoshis are respectively around 0.25 USD and 0.46 USD. And the transaction fees for the attack can be easily neglected by mixing the malicious near-dust UTXOs as outputs in larger transactions that have other economical uses for the attacker, like just sending some near-dust amount as change to the TSS address in such transactions. The cost is negligible, and, since the wallet lacks a robust UTXO consolidation strategy, this can be performed along a short or long interval of time.

Thank you so much for your consideration and time.

@0xean
Copy link

0xean commented Jan 15, 2024

Thanks @ciphermarco - agreed this is a separate issue. That being said, I do not believe this shows sufficient proof to show that a DOS will occur here and more evidence would be required to show the number of UTXO's required to actually have the network fail and the costs or timeline as to when that number would be reached.

I DO think this is a very valid QA issue however, but cannot award it as M as there is not sufficient evidence to show the feasibility of this attack or when the state would be reach through "normal" use for enough resources to be consumed dealing with these UTXO's to actually DOS the chain.

@c4-judge
Copy link

0xean marked the issue as not a duplicate

@c4-judge c4-judge added downgraded by judge Judge downgraded the risk level of this issue QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax and removed 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge labels Jan 15, 2024
@c4-judge
Copy link

0xean changed the severity to QA (Quality Assurance)

@c4-judge
Copy link

0xean marked the issue as grade-b

@C4-Staff C4-Staff added grade-a and removed grade-b labels Jan 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue grade-a QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax satisfactory satisfies C4 submission criteria; eligible for awards sufficient quality report This report is of sufficient quality
Projects
None yet
Development

No branches or pull requests

6 participants