CVE-2023-46132 – github.com/hyperledger/fabric
Package
Manager: go
Name: github.com/hyperledger/fabric
Vulnerable Version: >=1.0.0-alpha <2.2.14 || >=2.3.0 <2.5.5
Severity
Level: High
CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L
CVSS v4.0: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N
EPSS: 0.00179 pctl0.39807
Details
Fabric vulnerable to crosslinking transaction attack # Short summary Combining two molecules to one another, called "cross-linking" results in a molecule with a chemical formula that is composed of all atoms of the original two molecules. In Fabric, one can take a block of transactions and cross-link the transactions in a way that alters the way the peers parse the transactions. If a first peer receives a block `B` and a second peer receives a block identical to `B` but with the transactions being cross-linked, the second peer will parse transactions in a different way and thus its world state will deviate from the first peer. Orderers or peers cannot detect that a block has its transactions cross-linked, because there is a vulnerability in the way Fabric hashes the transactions of blocks. It simply and naively concatenates them, which is insecure and lets an adversary craft a "cross-linked block" (block with cross-linked transactions) which alters the way peers process transactions. For example, it is possible to select a transaction and manipulate a peer to completely avoid processing it, without changing the computed hash of the block. Additional validations have been added in v2.2.14 and v2.5.5 to detect potential cross-linking issues before processing blocks. ## 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. # 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. # Patches Here is the patch I propose (the explanation is further below): ``` diff --git a/internal/peer/gossip/mcs.go b/internal/peer/gossip/mcs.go index b46df8b6a..9c3b5c8fd 100644 --- a/internal/peer/gossip/mcs.go +++ b/internal/peer/gossip/mcs.go @@ -150,6 +150,10 @@ func (s *MSPMessageCryptoService) VerifyBlock(chainID common.ChannelID, seqNum u return fmt.Errorf("Block with id [%d] on channel [%s] does not have metadata. Block not valid.", block.Header.Number, chainID) } + if err := protoutil.VerifyTransactionsAreWellFormed(block); err != nil { + return err + } + // - Verify that Header.DataHash is equal to the hash of block.Data // This is to ensure that the header is consistent with the data carried by this block if !bytes.Equal(protoutil.BlockDataHash(block.Data), block.Header.DataHash) { diff --git a/orderer/common/cluster/util.go b/orderer/common/cluster/util.go index e229bebfc..05b1bfaa9 100644 --- a/orderer/common/cluster/util.go
Metadata
Created: 2023-11-14T20:28:34Z
Modified: 2023-11-14T21:37:09Z
Source: https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2023/11/GHSA-v9w2-543f-h69m/GHSA-v9w2-543f-h69m.json
CWE IDs: ["CWE-362"]
Alternative ID: GHSA-v9w2-543f-h69m
Finding: F124
Auto approve: 1