s_tajima:TechBlog

渋谷で働くインフラエンジニアのTechブログです。

mod_extract_forwarded, mod_rpafの脆弱性らしきものを見つけた話

はじめに

本記事で言及しているmod_rpafというモジュールは、forkによる派生などによって幾つかの実装があるようです。
きちんと区別できるように、今回は以下の実装をそれぞれ分けて表記することにします。
(ここに挙げた以外にもいくつかの実装をみつけることはできました。)

GitHub - gnif/mod_rpaf: reverse proxy add forward module for Apache => mod_rpaf(gnif版)
GitHub - ttkzw/mod_rpaf-0.6 => mod_rpaf(ttkzw版)

※2016/02/19追記
@ttkzwさんご本人から直接連絡をいただきました。
修正していただけたようです。
ご対応ありがとうございます!

Bugfix: wrong remote_addr is set. · ttkzw/mod_rpaf-0.6@345365c · GitHub

概要

mod_extract_forwarded, mod_rpaf(ttkzw版)を導入している環境において、
悪意のあるリクエストによってリモートホスト(接続元のホスト)の 先頭に 任意の文字列が追加 されてしまうという不具合の話です。

尚、httpd2.4に組み込まれている、mod_remoteipについては影響を受けないことを確認しました。

※ mod_extract_forwarded, mod_rpafについては mod_remoteipのリモートアドレスの扱いに関する不具合の修正について - s_tajima:TechBlog で解説しているので割愛します。

脆弱性の解説

mod_extract_forwarded, mod_rpaf(ttkzw版)を導入していて、通常のリクエスト処理フローにX-Forwarded-Forヘッダが使われるような環境を想定します。

わかりやすくするために、今回は以下のようなケースとします。

Client(192.0.2.1) -- global network -->  ReverseProxy(10.0.0.1) -- local network --> WebServer(10.1.0.1)
※Webサーバーには信頼するProxyとして10.0.0.0/8を指定

通常のリクエスト

WebServerには以下のようなX-Forwarded-Forヘッダが届きます。

X-Forwarded-For: 192.0.2.1, 10.0.0.1

その結果、リモートホストには以下の文字列が記録されます。

192.0.2.1

脆弱性をついた攻撃のリクエスト

このような環境にたいして、Clientから、"(ダブルクォート) を含む X-Forwarded-Forヘッダを送信します。
curlコマンドで表すと以下のようなリクエストになります。

curl -H 'X-Forwarded-For: SOMEEVILSTRINGS"' http://example.com

するとWebServerには、以下のようなX-Forwarded-Forヘッダが届きます。

X-Forwarded-For: SOMEEVILSTRINGS", 192.0.2.1, 10.0.0.1

すると不本意なことに、リモートホストとして以下の文字列が記録されます。

SOMEEVILSTRINGS", 192.0.2.1

考えられそうな影響

アクセス制限の回避

正規表現を使ってIPアドレスベースによるアクセス制限をかけているが、
その正規表現が適切でない場合にその制限を回避される懸念があります。

※PHPでの例です。  
if (preg_match('/^10\./', $_SERVER['REMOTE_ADDR'])){  
    # ローカルネットワークからのみリクエストを受け付ける想定の処理。  
}

このケースでは SOMEEVILSTRINGS10.1.1.1 等とすることで制限を回避できてしまいます。

XSS, SQLインジェクション

リモートホストに入ってくる文字列がIPアドレス以外になることを想定せず、適切なエスケープをしていないと、
XSSSQLインジェクションを実行される可能性があります。

なぜこのようなことが起こるのか

mod_extract_forwarded, mod_rpaf(ttkzw版)では、X-Forwarded-Forヘッダのパースにapacheのコアに含まれるap_get_token関数を使用しています。

ap_get_token関数は、カンマ区切り等で複数の値を取りうるヘッダの値を分割文字列毎に分割するための関数ですが、
ヘッダ文字列内に "(ダブルクォート) が含まれるとそこから文字列の終端までを1つの塊として処理してしまいます。

更に、ap_get_tokeによって返された値を、IPアドレスとして正しい文字列であるかを判定せずに、
そのままリモートホストとして扱ってしまうために、今回のようなことが起こります。

参考:

対応

mod_rpaf(gnif版)の 2014/03/29 のこのコミット Refactor IP subnet comparison using apr funcs · gnif/mod_rpaf@8419240 · GitHub 以降であれば、
X-Forwarded-Forヘッダのパースが自前の実装になっていて、
この問題は発生しないようなので、そちらに乗り換えるというのは1つの手段です。
また、rpaf_looks_like_ip関数によってIPアドレスとして正しいフォーマットであるかのチェックもしてくれています。
https://github.com/gnif/mod_rpaf/blob/stable/mod_rpaf.c#L293-L301

mod_extract_forwarded, mod_rpaf(ttkzw版)は共に長い間メンテナンスがされていないようなので、
何らかの理由で乗り換えが難しいケースでは、適切なパッチを自前であてる必要がありそうです。

以上、mod_extract_forwarded, mod_rpafの脆弱性らしきものを見つけた話でした。