メソッドごとに異なるアクセス制御が必要になることがいったいどれだけあるというのか?
Apache の各種ディレクティブのうち、もっとも間違った使い方をされていると思われ、しかも危険なディレクティブである <Limit> について。
こちらもあわせて参照されたい。10年前なら正しかったんだけどね…。
長ったらしい文章を読む気になれないとか、書いてあることの意味がよくわからないという人は、とりあえずこの節だけ読んで手元の httpd.conf、.htaccess を修正すべし。
google で検索してひっかかったページにそう書いてあったから自分も真似しただけ、という人は、
<Limit ???> ... </Limit>
という部分の <Limit ???>、</Limit> の部分だけを消す(このふたつに囲まれた中身はそのまま残す)。ただし、??? に GET が含まれていなかったり、すぐそばに <LimitExcept ???> が存在していれば、放置しておいてもよい(たぶん)。
自分では意味がよくわかってないんだけど、何か特別な理由があって必ず <Limit> が必要だと言われてる、という場合は、その人にこのページを見てもらってどうすればいいか聞く(その人が答えられない、わからないという場合は、たいてい <Limit> は不要なので、上述のとおり消す)。その人に聞けない場合は、
<Limit ???> ... </Limit>
のすぐ下に以下を付け加えればたぶん大丈夫。
<LimitExcept ???> Order deny,allow Deny from all </LimitExcept>
??? に GET が含まれていない場合は、たぶん放置してよろしい。設定の意図がわからんので断言できないが。
以下、解説。
Apache には特定のメソッドにのみディレクティブを適用させる <Limit> というディレクティブがある。これを使ってアクセス制御をしたが、使いかたを誤って設置者が意図していない(と思われる)動作をする例が広まっているようだ。google では以下のような例がたくさん見つかる。シャレにならんほどたくさん。
AuthType Basic AuthName ... AuthUserFile ... AuthGroupFile /dev/null <Limit GET> require valid-user </Limit>
この場合、require ディレクティブが有効になるのは <Limit> で指定された GET メソッドでリクエストされたときだけである(*1)。require ではなく Allow from や Deny from を <Limit> の中に入れる例も見つかるが、これも同様である。アクセス制御が有効になるのは GET だけ、つまり、POST や PUT などの他のメソッドでリクエストされた場合にはまったく効果がない。
一方、Apache のドキュメントには以下のような例が出ている。
<Limit POST PUT DELETE> Require valid-user </Limit>
google で大量にひっかかる前者のような例と非常によく似ているが、実はその意図は大きく異なっている。
通常 Web ブラウザでアクセスするときには GET メソッドが使われる。POST はフォームに入力した文字列を CGI で受けとるときに使われる(この用途では GET も使われる)。CGI フォームの HTML ソースでは <form method="POST" action=...> だったり<form method="GET" action=...> と書かれたりするが、これはどのメソッドでアクセスすべきか指示したものである。
PUT、DELETE といったメソッドも存在するが、これは通常の HTTP アクセスではほとんど使われない(サーバ上にファイルを転送したり、削除したりするというのが本来の目的)。いずれにしろ、GET 以外では POST は CGI フォームでしか使われないし、PUT、DELETE は通常のアクセスでは絶対に使われないといってもいい「特殊な」メソッドである。つまり特別な操作が要求されるときだけ認証を要求し、それ以外は認証なしで誰にでも閲覧を許可するのが、後者の Apache ドキュメントにあった例の意図である。
しかし、前者の <Limit GET> の例はそうではない。特殊なメソッドは自由に使えるが、通常のアクセスで使われる GET だけを制限の対象にしようという設定だ。メソッドを限定することに何か意味があるのか? ドキュメントを読んでも「大部分の場合にはアクセス制御に関わるディレクティブを<Limit>セクション内に書くべきではありません」と明記されているように、基本的には使う必要はないものだ。ドキュメントにある例はこの「大部分の場合」からはずれる場合にたとえばどう書くか、というサンプルなのである。もし必要があって使うのならば影響範囲をしっかりと見極める必要がある。
そして、うかつに使うと、アクセス制限して特定の人以外には見せないつもりにしたはずなのにページが見られてしまうことがありうる。それを以下から見ていく。
httpd.conf や .htaccess に <Limit GET> ... </Limit> と書いた場合は GET メソッドでリクエストされた場合だけアクセス制限がかかることは前述した。しかし、通常は GET でしかアクセスされないのであれば、GET だけ制限をかければ十分ではないのか?
残念ながら、それは NO だ。POST メソッドが GET と同じように動作してしまうことがあるのだ。
Apache2 は CGI でないふつうのファイルに POST メソッドでアクセスしても、GET と同じようにコンテンツを返してしまう。これではせっかくのアクセス制限が無意味になってしまう。つまり、<Limit GET> は頭隠して尻隠さずで、制限を越えてアクセスできてしまう(*2)。実例をあげよう。
% telnet www.apache.org 80 Trying 209.237.227.195... Connected to www.apache.org. Escape character is '^]'. POST / HTTP/1.0 Host: www.apache.org Content-Length: 0 HTTP/1.1 200 OK Date: Tue, 02 Nov 2004 07:54:39 GMT Server: Apache/2.0.52 (Unix) Last-Modified: Mon, 18 Oct 2004 00:38:27 GMT ETag: "2da7cde-2e53-feb2e6c0" Accept-Ranges: bytes Content-Length: 11859 Cache-Control: max-age=86400 Expires: Wed, 03 Nov 2004 07:54:39 GMT Connection: close Content-Type: text/html; charset=ISO-8859-1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- Copyright 1999-2004 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); (以下略)
POST リクエストに対して応答を返してきているのがわかると思う(200 というのはリクエストが正常に処理されたという意味である)。
もっと端的には、以下のボタンをクリックしてみるといい。なんのエラーもなく、このページがリロードされて表示されるだろう。
しかし、HTML のソースを見ればわかるように、このボタンは <form method="POST"> と POST メソッドでリクエストするよう書かれている。つまり、CGI でも何でもないただの HTML が POST リクエストを受け取って、GET と同じような応答を返しているのである。
<Limit GET> ... </Limit> と書いた場合、その中に書かれたアクセス制御ディレクティブは GET メソッドでしか有効にならない。POST メソッドによるリクエストは制限の対象にならないので、アクセスを許可しないつもりのクライアントからアクセスできてしまうかもしれないのだ。
なお、このように CGI などではない静的ファイルに POST メソッドでリクエストして GET と同じような応答が返ってくる現象は、実は Apache1.3 では起きない。Apache2 だけである。1.3.x や他のいくつかの HTTPD の実装では 200 OK ではなく、405 Method Not Allowed(そのようなメソッドは許可されていないというエラー)が返る。Apache2 のこの動作は適切なのかどうかはわからないが…(InputFilter の概念が導入されたことによる仕様変更か?)。
上で見たように、<Limit GET> は POST メソッドを使って制限をかいくぐられることがあるので不十分である。ならば <Limit GET POST> ならばどうか。これならば POST も制限される。
やはり、NO である。GET でも POST でもないメソッドでアクセス可能な方法があるのだ。しかも、先の例は Apache2 限定だが、ここで述べる方法は 1.3 でも有効である。
以下のようにアクセスしてみる。
% telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. xxx /cgi-bin/printenv HTTP/1.0
上の xxx は伏せ字ではない。ほんとに xxx でリクエストする。こんなデタラメなリクエストでいいのかというと、ちゃんと受け付けられてしまうのだ。こういう応答が返る。
HTTP/1.1 200 OK Date: Thu, 04 Nov 2004 08:41:01 GMT Server: Apache/1.3.33 (Unix) Connection: close Content-Type: text/plain ... REQUEST_METHOD="xxx" ...
405 Method Not Allowed(そのメソッドは許可されていない)というエラーでも 501 Method Not Implemented(そのメソッドは実装されていない)というエラーでもなく、200 OK(正常に処理された) が返っている。上の例では環境変数を出力するだけの CGI にアクセスしているが、その結果の中でもリクエストに使われた xxx がそのまま REQUEST_METHOD として出力されているのがわかる。
xxx などという得体のしれないメソッドを使ったリクエストでも、GET と同じ結果が返ってくる(*3)。もちろん、アクセス制限を <Limit GET> で囲んだ場合には意味がない。<Limit GET POST> としても防げない。xxx というメソッドへの制限を追加してはじめて制限できる。が、xxx を追加しても、今度は ZZZ というメソッドでアクセスされたら回避されてしまう。つまり、<Limit> では防げない。
じゃあ、どうすればいいのか。かんたんなことである。<Limit> なんか使わない。それだけでいいのである。メソッドを限定せずに、すべてのメソッドで同じアクセス制限になるようにすればいい。それだけのことだ。
この現象は CGI のような動的スクリプトでのみ発生する。リクエストされたメソッドによらずスクリプトが GET メソッドのときと同じ応答を返してしまうのが原因なので、httpd.conf や .htaccess での対策ではなく、CGI 内部で REQUEST_METHOD に応じて処理を変えるようにすることでも回避できる。CGI を作成して他者に配布しているような人は、想定されたメソッド以外でのリクエストにはエラーを返すようにスクリプトを書いておくと foolproof になってよいだろう。
なお、<Limit> は列挙されたメソッドのみに対象を限定するが、列挙されたメソッド*以外*に対して働く <LimitExcept> というディレクティブもある。この例では <LimitExcept> の使用も有効である。しかし、WebDAV のように特殊なメソッドを使いわけるものでもないかぎり、そもそもメソッドによって異なる制限をかける必要はないだろう。だからふだんは <LimitExcept> も使う必要はない。ちゃんと意味と必要性を理解している場合のみ使えばいい。
<Limit GET> で囲め、という記述がシャレにならないぐらい広まっているわりには、GET 以外のメソッドで GET 相当の動作を示すことがあるということがほとんど知られていないように思う。ここで挙げたような、通常と異なるメソッドによるアクセスで起きる現象は、基本的には Apache の問題ではなく、たいていの場合はそもそもメソッドにより異なるアクセス制限を設定しようと考えること自体が間違っているのだと個人的には思っている。Apache2 の POST に対する挙動は気持ちわるいけど…。
なお、Apache 以外の HTTPD はよく知らないが、メソッドにより異なるアクセス制御が可能な実装がもしほかにあるとすれば、それにももしかしたらこのような抜け道があるかもしれない。
おまけ。
ところで、ドキュメントにも書いてあるが、メソッド名は大文字小文字が区別される。つまり GET と get は別である。<Limit get> と書いてしまうと通常の GET でのアクセスにすら制限がかからないので注意する必要がある。たしか Apache1.2 では小文字でもよかったような記憶がある。大昔の記述が残ってるサイトだとこの古い小文字の例が見つかることがあるが、けっして真似してはいけない。
なお、Apache ではなく、その前身である NCSA HTTPd ならば、<Limit> を使うのが正しいアクセス制限のやりかただった。つまり、<Limit> を使うのは10年前の古い古い設定方法ということだ。
(*1) ドキュメントにもあるように、GET を指定した場合は HEAD に対しても制限がかかるので正しい表現ではない。
(*2) 不正アクセス禁止法にひっかかるかどうかは知らない。
(*3) POST メソッドでのリクエストを前提に書かれた CGI の場合は POST と同じ動作になることが多いだろう。要は、CGI の方でメソッドの判別をしないかぎり、そのまま既定の動作をしてしまうということである。