BurnTx is a security vulnerability in Lisk and derived blockchains, discovered during the work on the snapshot-validator. It was responsibly disclosed on October 22nd and patched the day after that. This is the original description of the vulnerability.

In Lisk and derivative systems, addresses are strings in the format [0-9]{1,21}L . Those addresses are generated by an eight byte hash of the public key, represented as an unsigned 64 bit integer, i.e. the range [0, 18446744073709551615].

As you can see, the address format allows representing addresses that exceed the uint64 range. Addresses in the format [0-9]{1,21}L are referred to as valid addresses and addresses that are in the uint64 range are referred to as plausible addresses.

Lisk Core represents addresses as strings which leads to two cases where valid addresses are not plausible addresses in the Lisk blockchain:

The address value is not verified to be in the uint64 range, i.e. 18446744073709551616L to 999999999999999999999L are valid addresses. Leading 0s are not normalized, i.e. 0111122223333L is a different address than 111122223333L and 00111122223333L.

In both situations, the created valid addresses do not belong to a public key, so when those addresses receive money, there is no way to ever retrieve it. At the moment, more than 18,000 LSK have been burned by sending them to implausible addresses. While being able to destroy value by sending it to nowhere is not convenient, I don’t think it is a big issue. Keypairs get lost every day and nothing prevents users from being able to send money to the wrong address, to a lost keypair or to any plausible address that does not belong to an existing keypair.

While addresses are processed as string during storage and transaction handling, they are converted back to bytes when a transaction is serialized for signing. This allows changing the recipient addresses while maintaining the same signature. This is fundamentally described as transaction malleability. Two different situations where this can be applied are described below:

Values too big for the uint64 range are cut: a big number helper expands those addresses to 16 bytes and then slices the first 8 bytes from the result, losing most information along the way. 111122223333L, 0111122223333L and 00111122223333L are encoded into the same eight bytes 00 00 00 19 df 66 90 e5.

The transaction creator has no way to prove they wanted to send to one specific of the valid addresses that are all encoded into the same bytes. This bug can be abused to manipulate someone’s send transactions in order to burn tokens.

Who is affected?

Case 1 can be abused when it is possible to append 8 bytes to the right without exceeding the biggest valid address value 999999999999999999999 = 36 35 c9 ad c5 de 9f ff ff. That means the address range 00 00 00 00 00 00 00 00 to 00 00 00 00 00 00 00 36 (0L to 54L) is affected.

Case 2 can be abused at any time someone sends a transaction to an address with less than 22 characters (0L to 99999999999999999999L). Since the largest plausible address is only 21 characters long, every plausible address is equally exploitable. A skilled attacker can intercept a signed transaction and prepend a leading “0” to the recipient address. Subsequently, the transactions signature is still valid and the tokens are transmitted to the falsified recipient.

Designing an attack

The objective of a potential attack is to burn tokens by changing a transaction’s recipient in a way that leaves the signature valid. Motivations for doing this may be diverse, but for the purposes of this disclosure these will not be discussed.

In order to manipulate transactions, an attacker must be able to access the transaction pool. The best way to do this is to operate a compromised node in the network which end users trust to handle their transactions. In this case the malicious node receives the transaction from the user, changes the recipient and broadcasts it to the network. No further action is needed from the node after that point.

In the event that users are not directly connecting to the malicious node, the attacker would need to deploy a large amount of nodes in the Lisk network. Whenever a malicious node receives a transaction, it manipulates that transaction and rebroadcast to peers. There are some tricks that can be applied to ensure manipulated transactions get broadcasted to as many nodes as possible. For example, broadcasting to more peers than a normal node would or resetting the relay count. This results in a high chance of success that many of these manipulated transactions make it to the forging delegate and subsequently are written into a block. The number of nodes that an attacker will need depends on the reliability threshold required. If the attacker wanted to burn a specific user’s funds, it is estimated that 300 to 500 nodes would be required to ensure a high probability of success. If the attacker’s goal is just to facilitate value destruction and sow chaos by burning funds from random transaction in the network, a dozen nodes would be sufficient. However, these figures are rough estimates based on historical network observations.

An active delegate or someone bribing an active delegate has the power to reliably manipulate up to 25 choosen transactions per round that are in the transaction pool at the time of forging a new block.

Post-block attacks

The three attack vectors above can be classified as pre-block attacks, i.e. manipulations that happen on transactions before a block is created. In addition to that it is also possible to manipulate a recipient in a transaction within a block without invalidating the block signature. This is possible because the same collision happens when the payload hash is calculated from a block’s transactions.

One way to manipulate blocks is to collect blocks in the network, manipulate transactions and rebroadcast the block. A second way to manipulate blocks is spreading falsified blockchain snapshots. The manipulated transactions in the snapshot become a single source of truth for a growing portion of the network which utilize these snapshots. Additionally, nodes spread this manipulated state to syncing peers, which further spreads the diseased state to other nodes.

Having multiple blocks with the same payload hash and ID but different recipients can have multiple effects:

If the affected addresses vote in such a way that their voting weight is sufficient to change the rank of active delegates we get a fork type 3. This happens because multiple nodes calculating the expected forging order get different results and blocks created by an unexpected delegate are rejected as invalid. This has the potential to divide the network into two or more groups in which each group maintains its own list of active delegates and continues to build its chain. For non-voting recipients or recipients with voting weight too low to change ranks, an attacker can split the network as follows. An attacker possesses the private key of their own empty address A . Now x tokens are sent to A but the attacker manipulates the block including the send transaction such that the money is sent to A' . One set of nodes now holds the balance state A: x, A': 0 and the other set of nodes holds the balance state A: 0, A': x . After the block is confirmed, the attacker sends a second transaction sending tokens from A to some other address. For the first group this is a valid transaction because A has tokens to send but for the second group the transaction is invalid because A has insufficient funds. As soon as the transaction is included in a block by a delegate in the first group, the second group will reject that block causing a network split. A variant of 2 is not being in control of the targeted recipient address A but knowing that the owner of A will move funds in the near future. Dividing the network into the groups A has funds and A has fewer funds leads to a time bomb that triggers a network split as soon as the owner of A tries to send a sufficiently large amount which the second group rejects. This could be done on an exchange’s deposit address for example. As soon as the exchange loses money because the group A has fewer funds becomes the majority fork, the exchange may consider delisting the affected coin.

Situation in RISE

Transactions can be manipulated the same way as in Lisk. But on testnet at least, recipient addresses are normalized by the node, using the following function:

public fromBytes(tx: IBytesTransaction): IBaseTransaction<any> & { relays: number }

This function creates a transaction object from a binary representation.

So, while the vulnerability may have been fixed unknowingly by the introduction of Protocol Buffers into the testnet code, the RISE mainnet should still be vulnerable to the exploit. Currently, there is not a demo of the exploit on the RISE mainnet as this is deemed a risk to the network’s integrity.

The developers at RISE realized that the largest possible address is not 22 but 21 characters long and they changed the address validation to the stricter format [0-9]{1,20}R . As a result, only recipient addresses up to 9999999999999999999R are vulnerable, which is 54 % of the address space.

Further notes

This is collection of related thoughts that have not been looked into:

There is a senderId field in send transactions which is optional in Lisk and required in RISE that might cause similar problems. The transaction type 3 (submit delegate votes) for some reason has a recipientId field, which may be vulnerable as well.

Credits

Isabella Dell deserves a huge amount of credit for being a trustworthy partner for reviewing this vulnerability, discussing additional attack vectors and planning a responsible disclosure.

Example data

Examples of manipulated transactions in Lisk and RISE are available in the original article and are skipped here to improve readability.