LoginSignup
5

More than 1 year has passed since last update.

posted at

updated at

ApacheのRewriteでパラメータのURLへリダイレクトしたい

概要

Apache HTTP Server(以下Apache)だけで、あるURLパスにアクセスされた際に、付与されたパラメータで指定されたURLへリダイレクトする必要があり、試行錯誤したときの話です。

環境

  • CentOS7
  • Apache Httpd Server 2.4.6

要件

  • URLパス/redirectへアクセスされたときに、パラメータredirectで指定されたURLへリダイレクトする

http://example.com/redirect?redirect=http://other.example.com
 ↓ リダイレクト
http://other.example.com

試行錯誤

バックエンドへプロキシせずにApacheの処理でリダイレクトを行う必要があったため、Rewrite設定で対応することにしました。
※ 設定はRewriteの部分のみに、パラメータの値取得もredirectのみの想定と簡略化しています
※ 正規表現は厳密性よりも可読性を重視しています

試行

単純にパラメータを抽出して、条件に合っている場合にリダイレクトすればOK!と考えて以下のような設定にしました。

ダメだった設定1
# /redirectへのアクセスでredirectパラメータが存在する場合、RED_PARAM環境変数に値を設定。
# /redirectへのアクセスで、RED_PARAMに値が設定されている場合にRED_PARAMの値へリダイレクトする。
RewriteCond %{QUERY_STRING} ^redirect=(.*)$
RewriteRule ^/redirect$ - [E=RED_PARAM:%1]
RewriteCond %{ENV:RED_PARAM} ^.+$
RewriteRule ^/redirect$ %{ENV:RED_PARAM} [L,QSD]

無事にhttp://other.example.comへリダイレクトしました。よし!終了。

錯誤

パラメータってURLエンコードされるよね。以下のような場合は大丈夫?
http://example.com/redirect?redirect=http%3A%2F%2Fother%2Eexample%2Ecom

試してみる。むむっ
http://example.com/http%253A%252F%252Fother%252Eexample%252Ecom
にリダイレクトされてしまいました。
そのままRewriteしたところ、パスとして扱われる結果に。。。

二重にエンコードされているので、RewriteフラグNEをつけてみることにします。

ダメだった設定2
# /redirectへのアクセスでredirectパラメータが存在する場合、RED_PARAM環境変数に値を設定。
# /redirectへのアクセスで、RED_PARAMに値が設定されている場合にRED_PARAMの値へリダイレクトする。
RewriteCond %{QUERY_STRING} ^redirect=(.*)$
RewriteRule ^/redirect$ - [E=RED_PARAM:%1]
RewriteCond %{ENV:RED_PARAM  ^.+$
RewriteRule ^/redirect$ %{ENV:RED_PARAM} [L,QSD,NE]

http://example.com/http%3A%2F%2Fother%2Eexample%2Ecom
二重エンコードは解消したが、やはりだめでした。

mod_rewriteの動作

mod_rewriteの説明では、RewriteRuleの置換文字列には、システムファイルパス・URLパス・絶対URL・ダッシュ(-)を指定することができると記載されています。
mod_rewriteのソースを確認したところ、絶対URLと見なされるためには、置換文字列中に://が含まれていることが条件で、上記の要件の場合、パラメータがURLエンコードされているときはデコードする必要がありました。

そこで引数の文字列をURLデコードして標準出力するシェルスクリプト(decode.sh)を作成、何とか呼び出せないかと調査を開始しました。

RewriteMapディレクティブを発見

ReweriteMapで外部プログラムを呼び出すことができるとの情報を見つけたので、さっそくデコードするシェルスクリプトを呼び出してみます。
プログラムは、標準入力を待ち受けて、標準出力へ改行で終わる文字列を出力するものでなければいけないため、引数から標準入力を待ち受けるようシェルスクリプトを修正しました。
RewriteMapでマップ名decodeに、プログラム呼び出しprgを書いてコロン区切りでプログラムファイルconf/decode.shを指定する以下の設定にしました。

うまくいったが外部プログラムが必要な設定
# decodeというマップ名に外部プログラムconf/decode.shを設定
RewriteMap decode prg:conf/decode.sh

# /redirectへのアクセスでredirectパラメータが存在する場合、RED_PARAM環境変数に値をデコードして設定。
# /redirectへのアクセスで、RED_PARAMに値が設定されている場合にRED_PARAMの値へリダイレクトする。
RewriteCond %{QUERY_STRING} ^redirect=(.*)$
RewriteRule ^/redirect$ - [E=RED_PARAM:${decode:%1}]
RewriteCond %{ENV:RED_PARAM} ^.+$
RewriteRule ^/redirect$ %{ENV:RED_PARAM} [L,QSD]

ちゃんとhttp://other.example.comへリダイレクトすることができました!

しかし、よくよくRewriteMapについて調べると外部プログラム以外にもマッピング種別があり、escape(エンコード)・unescape(デコード)・uppercase・lowercaseを内部ファンクションとして呼び出せることが判明!
試しにRewriteMapの設定を変更して、内部ファンクションのunescapeを指定してみます。

一番スマートと思われる設定
# decodeというマップ名に内部ファンクションunescapeを設定
RewriteMap decode int:unescape

# /redirectへのアクセスでredirectパラメータが存在する場合、RED_PARAM環境変数に値をデコードして設定。
# /redirectへのアクセスで、RED_PARAMに値が設定されている場合にRED_PARAMの値へリダイレクトする。
RewriteCond %{QUERY_STRING} ^redirect=(.*)$
RewriteRule ^/redirect$ - [E=RED_PARAM:${decode:%1}]
RewriteCond %{ENV:RED_PARAM} ^.+$
RewriteRule ^/redirect$ %{ENV:RED_PARAM} [L,QSD]

外部プログラムをRewriteMapで呼び出したときと同様、期待通りに動作しました!

今回お世話になったRewriteMapについて調べたことを紹介します。

RewriteMapディレクティブの書き方

RewriteMap MapName MapType:MapSource

  • MapName マップを呼び出すときの名称
    呼び出すときは、${MapName:LookupKey\|DefaultValue}で指定することが可能です。
    LookupKeyはマップのキー。
    DefaultValueは省略可能。

  • MapTypeMapSource

MapType MapSource 説明
txt プレーンテキスト形式のマッピングファイルのパス KeyとValueをスペース区切りで1行に記述する形式。
rnd プレーンテキスト形式のマッピングファイルのパス KeyとValueをスペース区切りで1行に記述する形式。Valueは複数の値をパイプで区切った形式。ランダムでいずれかの値が選択される。
dbm(=DBType) Key-Value形式のDBMファイルのパス DBTypeはsdbm, gdbm, ndbm, dbで作成したファイルが使用可能だが、Apache HTTP Serverのユーティリティhttxt2dbmでテキストファイルから作成したdbmファイルが推奨されている。
int toupper,tolower,escape,unescape MapSourceのいずれかの内部ファンクションを指定する。 toupper:大文字にする tolower:小文字にする escape:URLエンコードする unescape:URLデコードする
prg 実行するプログラムファイルのパス プログラムはSTDINから一つの引数(Key)をとり、STDOUTで一つの改行で終了する文字列(Value)を出力する必要がある。
dbd,fastdbd クエリ 一つの引数(Key)を与え、一つの値(Value)を返すクエリを記述する。mod_dbdでDBアクセスが行えるよう設定してあることが前提。fastdbdは、クエリキャッシュを作成するため高速。

RewriteMapディレクティブの使いどころ

RewriteMapを使用することで、長くなりがちなRewrite設定を簡略化することが可能です。

rewrite.conf
RewriteRule /foo http://foo.example.com  [L]
RewriteRule /bar http://bar.example.com [L]
RewriteRule /hoge /example/hoge [L]
...

以下のように置換定義を別のファイルでシンプルに定義することが可能です。
RewriteRuleが多数定義されている場合、置換定義を別のファイルで定義してApacheの設定ファイルを簡略化できるか検討したほうがよさそうです。

rewrite.conf
RewriteMap mapname txt:conf/rewritemap.txt
RewriteRule /(.+) ${mapname:$1} [L]
conf/rewritemap.txt
foo  http://foo.example.com
bar  http://bar.example.com
hoge /example/hoge

また、置換定義が非常に多い場合は、txtの代わりにdbmファイルを使用することを検討しましょう。
これは、LookupKeyにインデックスが作成されるためです。
Apacheに付属されているhttxt2dbmコマンドを使用してdbmファイルを作成することができます。
httxt2dbm -i rewritemap.txt -o rewritemap.dbm

コマンドを実行すると、rewtitemap.dbm.dirrewritemap.dbm.pagファイルが作成されます。
dbmファイルを利用するときのRewriteMapの設定は以下のようになります。

rewrite.conf
RewriteMap mapname dbm:conf/rewritemap.dbm
RewriteRule /(.+) ${mapname:$1} [L]

他にもMapTypeがありますが、今回はここまでとします。

注意

  • パラメータURLへのリダイレクトでは、予期せぬリダイレクト先にリダイレクトされることが考えられるため、パラメータのバリデーションは行いましょう。バリデーションを行わない場合、脆弱性となる恐れがあります。記事中の設定では省略されていますが、正規表現を用いたRewriteCondなどでリダイレクト先URLのバリデーションを行い、NGの場合はエラー画面にリダイレクトするなど考慮しましょう。
    https://qiita.com/tamura__246/items/f41d2f69f1c8494493e0
  • RewriteMapの外部プログラムマッピングでは、プログラムが終了するまで待機します。応答時間の遅延の原因となる可能性がありますので注意しましょう。
  • URL中に改行コードが含まれている場合は、削除する・エラーとするなど対策を行いましょう。
    https://www.ipa.go.jp/security/vuln/websecurity-HTML-1_7.html
  • その他、ディレクトリトラバーサル対策など脆弱性対策を確認・実施しましょう。

最後に

初めからApache HTTP Serverの調査をしっかり行うべきでした。。。
Apache HTTP Serverのモジュールはたくさんあるため、もっと良い方法があるかもしれません。
もっと良い方法があるよ、という方は教えていただけるとうれしいです。

参考URL

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5