Lucene search

K
hackeroneYacovmH1:2255968
HistoryNov 17, 2023 - 12:15 p.m.

Hyperledger: CVE-2023-46132

2023-11-1712:15:16
yacovm
hackerone.com
11
hyperledger
fabric
block
data hash
manipulation
vulnerability
security
network fork
attack
peer
transaction
hash function

AI Score

7.1

Confidence

High

EPSS

0.001

Percentile

16.0%

Long summary

In order to create a signature on a big chunk of data such as a block, the data needs to be “compressed” first to the input size of the signature algorithm.

In Fabric’s case, we use a hash function which compressed a Fabric block from arbitrary size to a 32 byte string.

In order to understand the problem we need to be more specific: The block structure has three parts to it: (1) Header, (2) Transactions, and (3) Metadata.

When hashing the block, the header and metadata are stitched together and then hashed, and this hash of the header and the metadata is what signed (it’s a simplification but let’s not get into details)

However, the transactions of the block are not part of the above hash. Instead, the header contains a hash, called the “Data hash” and despite the fact that in the comments it is said: “// The hash of the BlockData, by MerkleTree”, actually it is far from being the case, and that is where our problem lies.

The problem is that the way the transactions are hashed gives an attacker some freedom in manipulating the data.

To create the Data Hash, the transactions in the block are concatenated to one another, creating a big long byte array and then this big long byte array is hashed, and this is essentially the Data Hash.

The transactions in the block are a list of raw byte arrays, and when they are concatenated they look like this:

|$$$$$$$$$$$$|*************|@@@@@@@@@@@@|%%%%%%%%%| (The vertical lines " | " represent how transactions are separated in a block.)

When the transactions are concatenated in order to be hashed, the payload that is hashed is:
$$$$$$$$$$$$*************@@@@@@@@@@@@%%%%%%%%%

An adversary can’t change the bytes of the concatenation, however what it can do, is to modify how transactions are encoded in the block:

For example, consider an adversary wants to manipulate a peer to skip the second transaction (******).

It can then create a block with the transactions as follows:

|$$$$$$$$$$$$*************|@@@@@@@@@@@@|%%%%%%%%%|

Notice that a block with the above transactions has the same concatenation of bytes as the original block, but the block has one less transaction - the first transaction is a concatenation of the first and second transactions in the original block.

When the peer receives this block, it looks at the first transaction and when it parses it, it completely ignores the ***** bytes, (we will see why soon), and so, an adversary can create a block with the same hash but different transactions and this would create a fork in the network.

I made a small PoC where I created a block with 2 transactions (by invoking two chaincodes at the same time) with a Raft orderer:

    [e][OrdererOrg.orderer] 2023-10-14 23:07:34.076 CEST 0079 INFO [orderer.consensus.etcdraft] propose -> Created block [10] with 2 transactions, there are 0 blocks in flight channel=testchannel node=1

But right after creating the block, I just modified only its transaction content (without modifying the block hash) and then the peers only detect a single transaction inside that block:

    [e][Org2.peer0] 2023-10-14 23:07:34.079 CEST 0099 INFO [kvledger] commit -> [testchannel] Committed block [10] with 1 transaction(s) in 0ms (state_validation=0ms block_and_pvtdata_commit=0ms state_commit=0ms) commitHash=[c5ecca818da9319af2f276dd01cd1337938f20c3535dd23f95a33933a114fe84]

The important takeaway from this experiment is that the peer does not detect any tempering was done to the block. If an attacker performs this attack, the network can be forked silently and no one will notice the network was forked until it’s too late.

Impact

In V1 and V2, we only have a crash fault tolerant orderer and as such, the security model Fabric operates in is that the orderer is honest,
but peers may be malicious. As such, a peer that replicates a block from a malicious peer can have a state fork.

In V3 which we did not a release a GA yet (only a preview), we have a byzantine fault tolerant orderering service, so the security model that Fabric operates in such a case includes malicious orderers. If the orderer is malicious, it can cause state forks for peers, and can infect non-malicious orderers with cross-linked blocks.

AI Score

7.1

Confidence

High

EPSS

0.001

Percentile

16.0%