背景
PR #54 のディープレビュー指摘(blocker 1 / 3)。read relay 経路には、ネットワーク参加が自由(Byzantine 前提)であることと噛み合わない箇所が 3 つある。いずれも「DHT 近傍であること」を「正規 member であること」と同一視していることに起因する。
問題
1. 未証明の DHT 候補へ caller の credential を転送する
resolve_members(state_node_service.rs)はローカルに ContentNetwork レコードが無い場合、find_closest_peers の結果をそのまま member として扱う。その候補へ relay_read_content / relay_read_history が caller の token・request signature・timestamp を転送する。
XOR 距離は配置規則であって、そのピアが現在も正規 member であることの暗号学的証明ではない。攻撃者は自分の PeerID を対象コンテンツの DHT キー近傍に置くだけで候補に入れる。
現状の影響範囲(PR #56 の署名統一後): リクエスト署名は monas-request-v1:<op>:<resource>:<timestamp>:<body digest> に束縛されるようになったため、盗んだ署名を別 content や別 operation に転用することはできない。残るのは「同じ content の同じ操作を 5 分以内に再実行できる」ことと、委譲 JWT 自体が TTL 内で候補ノードの手に渡ることである。read relay では候補ノードはどのみち暗号文を扱う立場なので、追加で得られる情報は限定的だが、credential を未証明の相手に渡す構造そのものは解消されていない。
2. 最初に成功した応答をそのまま受理する
relay_read_data は最初に Ok を返した候補の (data, version) を返す。wire response 内の content_id を要求したものと照合していない。
PR #56 の CID 再計算により payload の改ざんは弾けるが、cross-content の取り違え(別 content の正規 Node を返す)は CID 検証だけでは弾けない — クライアントが要求した version CID と一致しなければ落ちるので実害は限定的だが、応答を要求へ束縛する検証は明示的に入れるべき。
3. 未証明ピアの 401/403 を権威ある最終判定として扱う
record_relay_read_error が auth verdict(AuthenticationFailed / AuthorizationFailed)を受け取ると ControlFlow::Break で failover を即座に打ち切る。「member が実際に評価した」という前提だが、その判定主体が正規 member である保証がない。
→ DHT 近傍を操作できる 1 台が、正当なユーザーの read を恒久的に拒否できる(可用性攻撃)。
あるべき修正
根本は「認証された member discovery」を持つこと:
- member であることを検証可能にする(ContentNetwork レコードへの署名、または member 自身が提示できる membership proof)
- 未証明の候補へは実 credential を渡さない。あるいは client が最終 member へ直接 proof を提示する構造にする
- 応答を要求した content / series / version へ束縛して検証する
- 単一候補の否定応答で failover を打ち切らない。または否定応答を「正規の policy holder による判定」と検証できるようにする
関連
テスト観点
- 悪意ある DHT 候補が credential を再利用しようとするケース
- cross-content response を返すケース
- 偽 401/403 を返す候補が 1 台混じっても、正規 member から読めること
背景
PR #54 のディープレビュー指摘(blocker 1 / 3)。read relay 経路には、ネットワーク参加が自由(Byzantine 前提)であることと噛み合わない箇所が 3 つある。いずれも「DHT 近傍であること」を「正規 member であること」と同一視していることに起因する。
問題
1. 未証明の DHT 候補へ caller の credential を転送する
resolve_members(state_node_service.rs)はローカルにContentNetworkレコードが無い場合、find_closest_peersの結果をそのまま member として扱う。その候補へrelay_read_content/relay_read_historyが caller の token・request signature・timestamp を転送する。XOR 距離は配置規則であって、そのピアが現在も正規 member であることの暗号学的証明ではない。攻撃者は自分の PeerID を対象コンテンツの DHT キー近傍に置くだけで候補に入れる。
現状の影響範囲(PR #56 の署名統一後): リクエスト署名は
monas-request-v1:<op>:<resource>:<timestamp>:<body digest>に束縛されるようになったため、盗んだ署名を別 content や別 operation に転用することはできない。残るのは「同じ content の同じ操作を 5 分以内に再実行できる」ことと、委譲 JWT 自体が TTL 内で候補ノードの手に渡ることである。read relay では候補ノードはどのみち暗号文を扱う立場なので、追加で得られる情報は限定的だが、credential を未証明の相手に渡す構造そのものは解消されていない。2. 最初に成功した応答をそのまま受理する
relay_read_dataは最初にOkを返した候補の(data, version)を返す。wire response 内のcontent_idを要求したものと照合していない。PR #56 の CID 再計算により payload の改ざんは弾けるが、cross-content の取り違え(別 content の正規 Node を返す)は CID 検証だけでは弾けない — クライアントが要求した version CID と一致しなければ落ちるので実害は限定的だが、応答を要求へ束縛する検証は明示的に入れるべき。
3. 未証明ピアの 401/403 を権威ある最終判定として扱う
record_relay_read_errorが auth verdict(AuthenticationFailed/AuthorizationFailed)を受け取るとControlFlow::Breakで failover を即座に打ち切る。「member が実際に評価した」という前提だが、その判定主体が正規 member である保証がない。→ DHT 近傍を操作できる 1 台が、正当なユーザーの read を恒久的に拒否できる(可用性攻撃)。
あるべき修正
根本は「認証された member discovery」を持つこと:
関連
テスト観点