Develop with pleasure!

福岡でCloudとかBlockchainとか。

LND 0.17.5以下に影響する不要なフェイルバックの脆弱性

先日、Matt MorehouseによってLND 0.17.5以下に影響する脆弱性の内容が開示された↓

morehouse.github.io

※ なお、この脆弱性を悪用する場合、別途ターゲットとなるノードを適切なタイミングで再起動させる必要がある。そのようなことが可能な別の攻撃ベクトルがないと成立しない。

不要なフェイルバック

まず前提として、LNで支払いをルーティングする際、以下のようなプロセスが実行される:

sequenceDiagram
    participant N1 as 上流ノード
    participant N2 as 下流ノード

    %% ホップ1→2 (HTLC追加)
    N1->>N2: ① update_add_htlc (payment_hash, amount, cltv_expiry)
    N1->>N2: ② commitment_signed
    N2->>N1: ③ revoke_and_ack
    N2->>N1: ④ commitment_signed
    N1->>N2: ⑤ revoke_and_ack

    note over N1, N2: HTLCが確立され、最終的に受取人まで到達

    %% 逆方向の決済伝播
    N2->>N1: ⑥ update_fulfill_htlc (payment_preimage)
    N2->>N1: ⑦ commitment_signed
    N1->>N2: ⑧ revoke_and_ack
    N1->>N2: ⑨ commitment_signed
    N2->>N1: ⑩ revoke_and_ack

update_add_htlcメッセージで、上流(支払人)から下流(受取人)に向けてHTLCのオファーがトリガーされ、経路上の各ノードは、commitment_signed/revoke_and_ackをやりとりしてコミットメントトランザクションを更新する。オファーが受取人まで届くと、update_fulfill_htlcメッセージで受取人から上流へプリイメージを使ってHTLCの解決が行われる。その際もオファーの時と同様commitment_signed/revoke_and_ackをやりとりしてコミットメントトランザクションを更新する。

今回問題となったのは、

  1. 下流ノードがプリイメージを使ったHTLCの請求を⑦で開始し、
  2. 上流ノードが⑧、⑨を送り
  3. 下流ノードは⑩のrevokeを拒否して、⑨で受け取った更新後のコミットメントトランザクションをブロードキャストし、それが承認される。

という挙動をした場合のLNDの動作。

上流ノードからすると下流ノードの有効なトランザクションが2つ存在する場合(②と⑨)。

元々、LNDはオンチェーンのコミットメントトランザクションと、相手側の他の有効なコミットメントを比較して、オンチェーン側に欠落しているHTLCがあればそれを上流にフェイルバックするようになっている。ただ、上記の場合は、⑥でプリイメージが分かっているので、上流ノードはさらにその上流ノードに対してプリイメージを使ってHTLCを解決すればいい。

しかしLNDの実装に問題があり、LNDで再起動が発生すると、上流のHTLCがまだ解決されていない場合、今度はHTLCのフェイルバックが上流に送信されるようになっていた模様。

つまり、下流はオンチェーンでHTLCが下流側の残高にチャージされ、上流のHTLCはフェイルバックによってHTLCがキャンセルされるという現象が発生する。つまりHTLCの金額が盗難される。

このような本来不要なフェイルバックが行われるというのが今回開示された脆弱性の内容。ただ、攻撃者のノードが被害者をサンドイッチしている状況かつ、被害者のノードを再起動させる必要がある。特に後者ができるような攻撃手法が必要になるので、攻撃自体は難しいだろう。

修正

LND v0.18.0で、プリイメージが既知のHTLCのフェイルバックを防止する修正がされてる↓

https://github.com/lightningnetwork/lnd/commit/6f0c2b5bab68c156262c1e8e2286f9a6b36bbbd7#diff-a0b8064876b1b1d6085fa7ffdbfd38c81cb06c1ca3f34a08dbaacba203cda3ebR2142-R2155

BOLTの問題?

CLNやEclair、LDKでは、過去にそれぞれ独自に問題を発見して、修正が行われた模様。そういう過去の事例がありながら、BOLTにこのようなケースをカバーするための仕様の定義がされていないのが問題では?という問題提起が最後にされてる。

2025.03.21追記

その後、プリイメージを知っている場合は、上流に失敗を流さないよう保証するBOLTの修正が行われた↓

github.com