要約

  • ForwardedX-Forwarded-For は、認証された本人情報ではなく通信経路についての主張である。受信側が直接観測した接続ピアを起点に、許可された代理範囲だけを遡る必要がある。
  • 原文フィールド、順序、構文解析器、信頼規則、最初の非信頼ノード、利用先の判断を別々に記録する。正しいIP表記は許可の根拠ではない。

「偽造可能」より正確な説明

HTTP ヘッダーは入力なので、クライアントが値を書くこと自体は異常ではない。問題は、アプリケーションがその入力を接続の事実として採用した点にある。

受信側が自分で観測したのは socket peer である。それが最終利用者とは限らず、通常はロードバランサーかもしれない。それでも HTTP テキストより手前にある局所的な事実であり、過去のホップを受理するための出発点になる。

ピアが転送情報を提示することを許可されていなければ、そこで探索を止める。ピア自身を採用するか、想定外の直通経路を拒否する。左側に有効なIP文字列がいくつ並んでも、無権限のピアを証人にはできない。

RFC 7239 が保証しないもの

RFC 7239 の Forwarded は、プロキシによって変更または失われた情報を開示する任意フィールドである。要素には forbyhostproto を含められる。これらは一つの認証情報ではなく、異なる観測を関連付けたものだ。

同 RFC は完全性の限界も明記する。クライアントを含む経路上の全ノードが値を誤って、または悪意をもって変更できるため、フィールドは正しいと仮定できない。検証したプロキシを信頼しても、そのプロキシが自ら観測した部分に根拠を与えるだけで、先に届いた任意の接頭部までは証明しない。

unknown や難読化したノード識別子も仕様上の状態である。数値アドレスへ無理に変換してはならない。欠落、非開示、不正、未知、実アドレスを監査記録で区別する必要がある。

XFF は別の契約

X-Forwarded-For は広く使われるが、RFC 7239 の構文そのものではない。AWS ALB だけを見ても append、preserve、remove があり、append では既存値を残して観測アドレスを右側へ加える。ALB が追加した末尾は説明できても、それより左の値が正しくなるわけではない。

HAProxy は XFF の挿入と標準 Forwarded の生成を別に扱う。下流は、どの製品がどの位置に何を追加したかを知らずに「最初」や「最後」を選べない。フィールド名、付加・置換・削除方針、消費側アルゴリズムを一組の契約として管理すべきである。

右側から現実へ接続する

NGINX では set_real_ip_from が正しい置換値を送ると認める相手を定義する。real_ip_recursive on なら、信頼済み連鎖を遡って最後の非信頼アドレスを選び、$realip_remote_addr は元の接続元を保持する。

Apache mod_remoteip は右から左へ値を評価し、無条件に有効化すれば利用者が他人を容易に詐称できると警告する。Envoy の xff_num_trusted_hops も右側から数え、use_remote_address により計算が変わる。CIDR で信頼境界を表す方式もある。

したがって「本当のIPを取得する」という普遍的処理は存在しない。存在するのは、確認したピア、実トポロジー、設定した信頼範囲、使用した構文、停止した境界である。

重複行とIPv6で合意を試す

RFC 9110 では、リストとして結合可能な同名フィールド行を受信順にカンマで結合できる。エッジ、HTTP ライブラリ、フレームワーク、ログ基盤が同じ要求を異なる形で見る可能性がある。重複行を意図的に送り、順序と解釈が一致するか確認しなければならない。

IPv6 は単純なコロン分割を破る。ポート、引用符、角括弧、unknown、難読化識別子、空白、異常な区切りを負のテストへ入れる。正規化結果と、送信者を信頼した理由は別項目として残す。

hostproto は認証ではない

TLS 終端前の scheme や host を復元すれば、リダイレクトや Secure Cookie の判断に役立つ。しかし、クライアントが proto=https と書いたことは、信頼済みエッジが TLS を観測した証明にならない。host もオリジン認証ではない。

各パラメータに利用者と信頼方針を割り当てる。一つの値が有用だからといってフィールド全体へ権限を拡張してはならない。

PROXY protocol を混ぜない

PROXY protocol は HTTP より前に送信元・宛先を伝える別の転送方式である。専用または厳格に制限した listener で、許可された送信元からだけ受け付ける。無制限に受理すれば、任意の接続者が好きな送信元を記した preface を作れる。

実際のピア、preface の期待・受理状態、提示アドレス、その後の HTTP フィールドを別々に記録する。すべてを単一の client_ip へ上書きすると、証拠の由来が失われる。

失敗経路を仕様にする

外部からオリジンへ直通し、HTTP より前で拒否されるか確認する。非信頼ピアから許可済みアドレスを XFF に書き、採用されないことを確かめる。正規のエッジ経由では偽の接頭部を加え、途中に非信頼プロキシを挿入し、ホップを増減させる。

重複フィールドと IPv6 の境界値を送り、エッジ、オリジン、アプリ、レート制限、ログの結論を比較する。最後に無権限送信元から PROXY preface を送る。設定表ではなく、これらの不一致テストが実行中の仕様になる。