X-Forwarded-For(XFF)の先頭に書かれたIPは、リクエストの送信者が自由に書き換えられます。実際のクライアントIPは、自分で管理しているプロキシが付け足した分を右から取り除き、残った一番右の値として決めます。
XFFは右端から順に信用できる
XFFは、プロキシを経由するたびに、そのプロキシが「自分に接続してきた相手のIP」を末尾に付け足していくヘッダーです。クライアント → CDN → ロードバランサー → Webサーバーという構成なら、Webサーバーには次のように届きます。
X-Forwarded-For: 203.0.113.10, 192.0.2.44
(203.0.113.10 = CDNが記録したクライアント、192.0.2.44 = ロードバランサーが記録したCDNのノード)
クライアントは最初のリクエストに好きなXFFヘッダーを付けて送れ、プロキシはそれを消さずに後ろへ付け足すだけです。信用できるのは、自分で管理しているプロキシが書いた右側の値だけです。
判断の手順は次のとおりです。
- 自分の構成で、XFFに値を付け足すプロキシがいくつあるかを数える(上の例ならCDNとロードバランサーの2つ)
- 右端から、その各プロキシが付け足した値を取り除く
- 残った値のうち一番右が、自分のプロキシに接続してきた実際のクライアントIP
先頭が偽装されていたログの実例
EC-CUBEサイトのログを見ていて、カート投入のPOSTを数分で数百回繰り返すbotを見つけました(すべて404で失敗しており、購入は成立していません)。普段のXFFは上の例のように2つの値が並びますが、このbotのリクエストだけは3つ並んでいました。
"198.51.100.7, 203.0.113.10, 192.0.2.44"
"198.51.100.93, 203.0.113.10, 192.0.2.61"
"198.51.100.150, 203.0.113.10, 192.0.2.44"
先頭の値はリクエストごとにバラバラでした。一方、2番目の値はほぼすべて同じ国内のモバイル回線でした。bot自身がXFFヘッダーを付けて先頭に偽のIPを入れ、CDNがその後ろに本当の接続元を付け足していた、ということです。先頭の値で集計すると、数百の別々の相手から少しずつ来たように見え、本当の接続元は上位に出てきません。
ログを見るときは、XFFの値の数が自分の構成より多い行を疑ってください。多い分は送信者が付けた値です。
EC-CUBEではTRUSTED_PROXIESに信用するプロキシを並べる
Symfonyの Request::getClientIp() は、信用するプロキシとして登録したIPを右から取り除き、残った一番右の値を返します。EC-CUBE 4.3の index.php は .env の TRUSTED_PROXIES をカンマで区切って Request::setTrustedProxies() に渡します。初期状態ではコメントアウトされています。
TRUSTED_PROXIES=10.0.0.0/8,192.0.2.0/24
ロードバランサーだけを登録した場合と、CDNも登録した場合で、Symfony 6.4の getClientIp() が何を返すかを実測しました(接続元のロードバランサーは 10.0.0.5)。
| 信用するプロキシ | XFFが2つ(通常) | XFFが3つ(先頭が偽装) |
|---|---|---|
| 未設定 | 10.0.0.5 | 10.0.0.5 |
| ロードバランサーのみ | 192.0.2.44 | 192.0.2.44 |
| ロードバランサー+CDN | 203.0.113.10 | 203.0.113.10 |
どの設定でも、偽装された先頭の 198.51.100.7 は返りません。ただし、正しいクライアントIPが返るのは経由するプロキシをすべて登録したときだけです。ロードバランサーだけを登録するとCDNのノードIPが返るので、アプリのログにはCDNのIPが並びます。未設定なら、すべてのアクセスがロードバランサーのIPから来たように記録されます。
信用リストに入れる範囲の注意
CDNを信用リストに入れるときは、そのCDNが公開している送信元IPの範囲を登録します。範囲は変わることがあるので、定期的に見直してください。
なお、接続元が信用リストに入っていない場合、SymfonyはXFFを読まずに接続元のIPをそのまま返します(上の表の「未設定」の行)。プロキシを経由せずWebサーバーへ直接送られたリクエストは、XFFをどう書いても無視されます。
EC-CUBEに関するお問い合わせ
[重要]現在公式にセキュリティサポートが切れていないPHPは8.1以上、MySQLは8.0以上で、対応しているEC-CUBEバージョンは4.2以上です。古いEC-CUBEを使っている方は適切なタイミングでバージョンアップをご検討ください。