本スクリプトは作者が2001年から2002年にかけて開発していたものですが、脆弱性の指摘を受けて2011年に約10年ぶりにバージョンアップしました。脆弱性の詳細は、更新履歴に記載しています。
このドキュメントの内容は開発当時のものであり、多くの情報やリンクが古いものとなっていますのでご注意ください。
小規模Webサイト向けの、自サイト内全文検索用CGIスクリプトです。Webサイト内のHTML文書やそのほかのテキストファイルから、特定の単語や文字列を含むものを探し出して一覧表示します。毎回すべてのファイルを読み込んで検索するいわゆるgrep型であり、インデックス構築の必要はありません。そのかわり(grep型の常として)動作は比較的低速で、サーバーへの負荷もそこそこかかります。検索対象文書の総数が100個から200個程度のWebサイトでの利用を想定しています。
Perl 5.005以降をCGIで利用可能なWebサーバー
このデータは作者が独自に調査したものです。その内容は保証しません。
同じホストでも、サーバーによって設定が違うこともあり得ます。
ホスト名 | 動作 | nkfのパス | Web公開用ディレクトリの例 | 備考 |
---|---|---|---|---|
さくらインターネット | ○ | /usr/local/bin/nkf | /home/[ユーザー名]/www/ | 作者のメイン環境です。 |
BIGLOBE | ○ | /usr/local/bin/nkf | 変則的[*] | [*]「サーバー設定調査用CGI」を利用してください。 |
リムネット | ○ | /usr/local/bin/nkf | 変則的(サポートページで確認可能) | |
NetLaputa | ○ | /usr/local/bin/nkf | /space/UserHome/home0/[ユーザ名]/HomePage/ | |
トクトク | ○ | /usr/bin/nkf | /home/member/[ユーザー名]/ | nkfは存在しましたが、外部プログラムの呼び出しは基本的にサポート対象外のようです。自己責任でお願いします。 |
ただぺーじ(CSC) | ○ | /usr/local/bin/nkf | /home/[ユーザー名]/public_html/ | nkfは存在しましたが、外部プログラムの呼び出しは基本的にサポート対象外のようです。自己責任でお願いします。 |
ミルトクラブ | ○ | /usr/local/bin/nkf | 変則的[*] | [*]サポートページの「カスタムCGI設置について」-「サーバーにおける絶対パス」で調べられるパスを設定してもうまくいきませんでした。「サーバー設定調査用CGI」を利用してください。 |
freeweb | ○ | なし | /home/freeweb/[ジャンル名]/[ユーザー名]/ | |
INTERLINK | ○ | なし | /home/[ユーザー名]/public_html/ | |
@nifty | △ | なし | /homepage/ | 改造が必要です(改造方法)。 |
BEKKOAME INTERNET | × | − | − | HTML文書を置くサーバーとCGIを置くサーバーが別(@nifty、BIGLOBEなどのようにCGIを呼び出すためのURLが別なだけでなく、FTPのアップロード先自体が違う=CGIから文書ファイルが参照できない)のため、動作しません。 |
Panasonic Hi-HO | × | − | − | Perlのバージョンが4のため、動作しません。 |
ファイル名 | 内容 | 修正 |
---|---|---|
mwsearch.cgi | スクリプト本体 | サーバーによっては、1行めのPerlのパスのみ書き換える必要があります。たいていはそのままでOKです。 |
config.pl | 設定ファイル | 必ず設定を変更する必要があります。 |
template/header.html | ヘッダテンプレート | このままでも動作しますが、ぜひお好みでカスタマイズしてください。 |
template/list.html | リストテンプレート | このままでも動作しますが、ぜひお好みでカスタマイズしてください。 |
template/footer.html | フッタテンプレート | このままでも動作しますが、ぜひお好みでカスタマイズしてください。 |
template/error.html | エラーテンプレート | このままでも動作しますが、ぜひお好みでカスタマイズしてください。 |
jcode.pl | 日本語処理ライブラリ(©歌代和正氏) | 修正の必要はありません。 |
sample.html | 検索ページのサンプル | このままでも動作しますが、このファイルはあくまでフォームの設定方法のサンプルです。実際にはご自分のページに同じような内容を組み込んでご利用ください。 |
test.cgi | サーバー設定調査用のCGIスクリプト | スクリプト本体と同様、サーバーによっては、1行めのPerlのパスのみ書き換える必要があります。 |
readme.html | ドキュメント(このファイル) | サーバーへ転送する必要はありません。 |
omake.html | おまけ:開発後記 | サーバーへ転送する必要はありません。というか、読む必要もありません(笑)。 |
付属のtest.cgiを動作させることにより、本スクリプトの設定に必要な情報のうち、以下のものを得ることができます。
どれも、プロバイダなどから情報が提供されている場合もありますが、そうでない場合はお使いください。
以下の説明に従って、各ファイルの設定を変更してください。なお、両方ともファイルの文字コードはEUC、改行コードはLFです。修正にはこれらを正しく扱えるエディタをご利用ください。
先頭にあるPerlのパス指定を、プロバイダなどの指示に従って書き換えてください。大抵は「#!/usr/local/bin/perl」のままで問題ありませんが、「#!/usr/bin/perl」などの場合もあります。
このファイル内のほかの部分は、不用意に変更しないように気を付けてください。改造したい場合は別ですが:-)
設定ファイルです。必ず変更する必要があります。各設定項目の意味はファイル内に記述していますが、注意点は以下のとおりです。
検索フォームを付けたいページ(トップページなど)に、以下のようなコードを追加します。もちろん、新たに検索ページを作ってもよいでしょう。サンプル(sample.html)も参考にしてください。なお、フォーム送信のメソッドは、POST、GETどちらでも構いません。
<FORM action="cgi-bin/mysearch/mysearch.cgi" method="GET">
<LABEL for="keyword">検索文字列:</LABEL>
<INPUT type="text" name="keywords"><BR>
<INPUT type="radio" name="option" value="and" checked>AND
<INPUT type="radio" name="option" value="or">OR
<INPUT type="radio" name="option" value="whole">完全一致
<INPUT type="radio" name="option" value="regular">正規表現<BR>
<INPUT type="submit" value="検索"><BR>
<INPUT type="hidden" name="testword" value="テスト文字列">
</FORM>
フォームで送信する内容の、それぞれの意味は以下のとおりです。「keywords」のみが必須で、あとの項目はすべてオプショナル(省略可能)です。すべて小文字で指定してください。
検索に利用するキーワードを指定します。タイプは(あらかじめ設定されたものから選ばせるなどの特殊用途を除けば)textです。
ここに何も入力されずにフォームからデータが送信されると、本スクリプトはエラーを表示します(エラーメッセージは設定ファイルの$szErrMsg_NullWordで設定可能)。AND、OR検索の際は「何も入力されていない」場合と「全角、半角スペースしか入力されていない」場合、完全一致、正規表現検索の場合は「何も入力されていない」場合のみエラーとなります。可能性は低いですが、「連続する○個のスペース」などを検索したい場合がないとも限らないので、こういった仕様にしています。
検索方法を指定します。valueはand(AND)、or(OR)、whole(完全一致)、regular(正規表現)のいずれかです。必ず小文字で指定してください。
省略された場合は、AND検索が指定されたものとして処理を行ないます。
「正規表現」では、Perlの正規表現をそのまま使います。不正な(エラーを引き起こすような)文字列が検索語に使われた場合はエラーを表示して検索を行なわないようにしていますが(エラーメッセージは設定ファイルの$szErrorMsg_Regularで設定可能)、取り扱いには十分にご注意ください。また設置前に、本当に正規表現検索が必要かどうかよくご検討ください。
文字コード判別に利用する文字列を指定します。本スクリプトでは内部的にEUCで日本語文字の処理を行なっているため、フォームから入力されたキーワードをEUCに変換していますが、この値が指定されていると、valueに設定された文字列を利用して、フォームから入力された(=変換元の)文字コードを判別します。
これにより、キーワード自体に文字コードの判別が難しい文字(特定の漢字一文字など)が利用されても、文字コードの判別をミスすることなく、正しく変換が行なえるわけです。
ただし、万が一、「フォームの各項目ごとに別々の文字コードで値を送る」ようなWebブラウザがあったとしたら、この設定は逆に誤作動の原因となります。作者自身はそのようなWebブラウザの存在を知りませんが、一応ご注意ください。
省略された場合は、keywordsから文字コードを判別します。なお、ユーザーにとって意味のある文字列ではありませんから、タイプはhiddenにしてください。
簡易的なカテゴリ指定機能を利用する場合に指定します。
本スクリプトの簡易的なカテゴリとは、検索対象のパス($szTargetPath)に、このcategoryオプションで指定された値を追加するというものです。そのため、Webサイトの構造として、「カテゴリごとにディレクトリが作成されている」というものを想定しています。
なお、値として「all」は予約されています。categoryオプションの値がこれの場合は、指定されなかった場合と同じく、検索対象のパスには影響を与えません。
具体的な記述方法としては、以下のようにSELECT要素などでユーザーに選択させるような形がよいでしょう。
<LABEL for="category">カテゴリの指定</LABEL>
<SELECT name="category">
<OPTION value="all">すべて</OPTION>
<OPTION value="diary">日記</OPTION>
<OPTION value="library">ライブラリ</OPTION>
<OPTION value="profile">プロフィール</OPTION>
</SELECT>
なお、この例では便宜上SELECT要素による選択リストとしましたが、ラジオボタンなどで選択する形でももちろん構いません。
通常は指定する必要はありません。設定ファイルの名前を、明示的に指定したい場合場合に使います。「指定された値+".pl"」が設定ファイル名となります。
SELECT要素などにより設定ファイルを切り換えることによって、最大検索件数を変えたり、表示方法(テンプレート)を変えたり、検索後のメッセージを変えたりなど、設定ファイルで変更できるものは何でも変更できます。
この値には、数字およびアルファベット、アンダースコア("_")のみが使用可能です。それ以外の文字は、実行時に削除されます。
省略された場合は、標準の設定ファイル(config.pl)が使用されます。
本スクリプトは検索結果を表示するWebページを、テンプレートを読み込んで生成しています。そのため普通にHTML文書を編集するのと同じように、容易にカスタマイズが可能です。
……というか、作者にデザインセンスがなく、標準のテンプレートのできがあまりよくないので、ぜひカスタマイズしてください。お願いです(笑)。
検索結果表示ページを構成するヘッダ、リスト、フッタの三つと、エラー表示用のもの、計四つのテンプレートがあります。ファイル名は設定ファイルで指定します。
それぞれのテンプレートにはいくつかのマクロ文字列が設定されています。これを埋めこんでおくことにより、実行時にさまざまなパラメータに置換されます。
HTML自体のヘッダや、再検索用のフォームなどを表示するためのテンプレートです。また、検索にヒットしたページの一覧の表示にリストや表を用いる場合の開始タグ(<UL>、<OL>、<DL>、<TABLE>など)はこのファイルの末尾に記述します。
ヘッダテンプレートでは以下のマクロ文字列が使えます。
入力したキーワード(AND、OR検索の場合は余分なスペースを削ったもの)に置換されます。再検索用のフォームや、「○○を検索しました」といったメッセージの表示に使えます。
再検索用フォームでの使用を想定しています(ほかに使い道はありません)。上から順にそれぞれ検索方法のAND、OR、完全、正規表現に対応しており、検索に使用された方法に対応するもののみが文字列" checked"に、それ以外は空白文字に置換されます。
これにより、
<INPUT type="radio" name="option" value="and"[MWSEARCH_AND_CHECKED]>AND
<INPUT type="radio" name="option" value="or"[MWSEARCH_OR_CHECKED]>OR
<INPUT type="radio" name="option" value="whole"[MWSEARCH_WHOLE_CHECKED]>完全一致
<INPUT type="radio" name="option" value="regular"[MWSEARCH_REGULAR_CHECKED]>正規表現
といったコードを書くことで、「検索時に選択した検索方法が、再検索用フォームでは標準で指定されている」というユーザーフレンドリー(笑)な検索結果ページを生成できます。
「MyWebSearch Ver.1.20」というふうに、「スクリプト名+バージョン番号」に置換され、ソフト名からは作者のWebサイトのトップページへのリンクが張られます。
たとえば「Powerd by MyWebSearch〜」といった感じで使うことを想定しています。後述のフッタテンプレートで使うほうが一般的かもしれません。
検索にヒットしたページのタイトルやURL、最初にヒットした段落(正確にはソースコード内の論理行)を表示するためのテンプレートで、当然ヒットしたページの数だけ繰り返し置換、出力が行なわれます。
リストテンプレートでは以下のマクロ文字列が使えます。
ヒットしたページのタイトルに置換されます。タイトルはHTMLのTITLE要素から抽出されます。タイトルが抽出できなかった(TITLE要素がなかった、あるいは形式が不正だった、あるいは本スクリプトのバグ)場合は、設定ファイルの$szNoTitleで指定した文字列が使用されます。
HTML文書内で、検索にヒットした単語(など)の総数に置換されます。
ヒットしたページのURLに置換されます。
検索にヒットした最初の単語を含む一つの段落(ソースコード内の論理行、ただし<BR>、<P>タグのみ実際の改行と同一視)に置換されます。段落内に含まれる検索単語は設定ファイル内の$szHitWord_Preと$szHitWord_Postで括られます。
検索結果の一覧の一番目から順に、連番に置換されます。
マクロ名こそ「RANK」ですが、単純にヒットした単語数でソートしているだけです。同一ヒット数のページの順序も不定です。
検索結果のメッセージや、文書のフッタ情報(ADRESS要素など)などを表示するためのテンプレートです。また、検索にヒットしたページの一覧の表示にリストや表を用いる場合の終了タグ(</UL>、</OL>、</DL>、</TABLE>など)はこのファイルの先頭に記述します。
フッタテンプレートでは、ヘッダテンプレートに使えるマクロ文字列すべてに加えて、以下のマクロ文字列が使えます。
検索結果を表示するメッセージに置換されます。これは、検索が正常に終了した場合は「$szMsg_Hit_Pre + [ヒットした文書の数] + $szMsg_Hit_Post」、検索件数が最大ヒット数の制限を超えた場合は「$szMsg_Over」、一件もヒットしなかった場合は「$szMsg_Nohit」が、設定ファイルより読み込まれて使用されます。
本スクリプトのCGIとしての実処理時間(起動直後から、検索、リストの出力を終了してフッタを出力する直前まで)を秒数で表わしたものに置換されます。
本スクリプトのプロセスが消費したCPU時間(起動直後から、検索、リストの出力を終了してフッタを出力する直前まで)を小数点2桁までの秒数で表わしたものに置換されます。CGIとしてWebサーバー上で実行した場合は、実際にユーザーが感じる処理時間(検索を開始してから、出力が終了するまで)とはかけ離れている場合が多い(というか普通)ため、デバッグ用くらいにしか意味はないかもしれません。
ちなみに、フッタテンプレートでは、まず[MWSEARCH_RESULTMESSAGE]マクロが展開された後に、[MWSEARCH_TIME]と[MWSEARCH_CPUTIME]が展開されます。そのためたとえば、設定ファイル内の$szMsg_Hit_Pre/Postなどに[MWSEARCH_TIME]などを設定して二重にマクロ展開を行ない、検索が正常に終了した場合のみ「16個の文書がヒットしました。検索にかかった時間は3秒です。」と表示する、といったことが可能です。
検索単語数が多すぎた場合(標準では10個まで)などに表示するエラー画面のテンプレートです。再検索用のフォームなどを設置してもよいでしょう。
エラーテンプレートでは、ヘッダテンプレートのマクロ文字列から[MWSEARCH_KEYWORDS]を除いたものと、以下のマクロ文字列が使えます。
エラーの内容に応じた、エラーメッセージに置換されます。メッセージは、設定ファイル内の$szErrMsg_で始まるいくつかの値で指定します。
ひととおり設定が終ったら、FTPクライアントなどを利用して、Webサーバーへファイルを転送します。場所はプロバイダなどによりCGIを置くことが許可されている場所なら、どこでも構いません。ファイル数が結構あるので、(それが許されるなら)専用のディレクトリを作成することをお勧めします。なお、mwsearch.cgiと設定ファイル(通常config.pl)は、必ず同じディレクトリに転送してください。
ファイルのパーミッションもサーバー次第ですが、メインの実行ファイルであるmwsearch.cgiは755(rwxr-xr-x)、そのほかは(多くのサーバーで標準の)644(rw-r--r--)にしておくのが、(最善かどうかは別として)一般的です。
WebサーバーApacheのsuEXEC機能などにより、CGIがその所有者の権限で動作する場合は、CGIの実行ファイルおよびそこから読み書きするファイルは、所有者のみにアクセス権限があれば十分です。この場合は700(rwx------)および600(rw-------)にしておくのが、セキュリティの観点からは最善であり、可能であればぜひそうすることをお勧めします。
なお、ログファイルは存在しなければ自動で作成しますが、CGIがユーザーの権限で実行される場合は、通常ユーザーはディレクトリへの書き込み権限がないので作成に失敗します。この場合は、あらかじめ空のファイルを作成してログの作成場所に転送し、606(rw----rw-)など、ユーザーでも書きこめるような設定にしておいてください。
ただしこれは同時に、誰でもログファイルを閲覧(場合によっては書き込みも)できるということを意味します。その点は十分にご留意ください。ログには個人情報のようなものは記録していませんが(詳細は後述)、他人に見られたくなければ、想像しにくいファイル名を付ける、拡張子を.cgiにしてHTTP経由のアクセスでわざと実行エラーを起こすようにする、Web公開用のディレクトリより上位のディレクトリにログを置く、などの方法がありますが、どれも完璧ではありません(たとえば、同じサーバーのほかのユーザーのCGIからのアクセスなどに対しては無防備です)。セキュリティ上最良の手段は、「ログを作成しない」ことです:-P
逆に、CGIがその所有者の権限で動作する場合は、前述のとおり600(rw-------)にしておけば、他人にどうこうされる恐れはまずありません。と思います。
これで、検索の準備は整いました。あとは、検索ページのフォームから、CGIにアクセスするだけです。
ログは、日時、検索方法、ヒット件数、処理時間(CPU時間)、カテゴリ、検索語句がタブ区切りで並んだテキストファイルです。例としては以下のようになります。
2001/02/22 19:31:21 regular 5 1.48 all .*WING 2001/02/22 19:32:54 or 20 1.55 all さくら サクラ 2001/02/23 12:11:04 and 13 2.63 diary さくら カード 2001/02/24 04:02:54 and 13 1.63 profile WZ マクロ 2001/02/24 16:03:11 whole 16 1.48 all Pocket PC
新しいバージョンに入れ換える際、設定項目が増えている場合があります。その場合、添付の設定ファイル(config.pl)内で、どのバージョンでどの項目が加わったかが分かるようになっているので、必要なもの(自分の使っているバージョン以降に追加されたもの)を既存の設定ファイルにコピーしてください。
リストテンプレートの[MWSEARCH_URL]が置換される「URL」とは、ヒットしたページのサーバー内での絶対パスの、$szTargetPathを$szHomeUrlに置換したものです。そのため、$szHomeUrlを空文字にすれば、「Webサイトのトップページからの相対URL」を表示させることも可能です。この場合はヘッダテンプレート内のHTMLヘッダ(ややこし)に
<BASE href="http://www.finalbeta.jp/">
というふうに、リンクの起点となるURLを記述しておくか、
<A href="http://www.finalbeta.jp/[MWSEARCH_URL]">[MWSEARCH_URL]</A>
というふうに、リストテンプレートのA要素の方でURLを補ってやれば正常にリンクされます(もちろん、このURLは例です。そのままだと作者のページにリンクが張られてしまいます)。
「動作環境」でも述べましたが、ログファイルのロックにはflock関数を利用しています。そのため、この関数が利用できないWindows 9x系+ActivePerlなどでは、排他制御がかからず、同時書きこみによるログファイル破壊の可能性が常に付きまといます(flockが使用可能な場合のみ使うようにしているので、スクリプト自体がエラーを起こすことはありません)。
Web上などを探せば、flockを使わない、汎用的なロック処理のルーチンなどを公開されている方もいらっしゃいますので、それらを使って改造するというのも手です。「絶対に必要な情報ではないので、気にしない」というのも一つの手ではあります。
……これ、Tipsというより手抜きの言い訳ですな。
そんなへなちょこな環境は、@niftyくらいしか知りませんが:-P
改造には結構根性がいるので気合い入れてください。
Perlの配布ファイルの「/lib」ディレクトリより、Carp.pm、CGI.pm、constant.pm、Exporter.pm、overload.pm、strict.pm、vars.pmを抽出し、サーバーに転送します。また、find.plも同様に転送します。さらに、サーバー上に「Class」というディレクトリを作成し、「/lib/Class」ディレクトリのStruct.pmを転送します。
File::Findではなくfind.plを使う関係上、テンプレートファイルなどの相対パスによる指定はできません。$szLogFile、$szHeaderFile、$szListFile、$szFooterFile、$szErrorFileには絶対パスを指定してください。
まず、スクリプトの前のほう、「プログラムの初期化」の部分で、
use CGI::Carp qw(fatalsToBrowser);
use File::Find;
use strict;
を削除(あるいはコメントアウト)し、代わりに
require 'find.pl';
を追加してください。
それ以外では以下の置換を行ないます。
置換対象文字列 | 置換文字列 |
---|---|
find({wanted => \&SearchFile,no_chdir => 1},$MWS_Config::szTargetPath); | &find($MWS_Config::szTargetPath); |
sub SearchFile | sub wanted |
$File::Find::name | $name |
$File::Find::prune | $prune |
$File::Find::nameは何度も出てきますので、置換漏れに注意してください。たいていの真っ当なテキストエディタには一括置換機能があるので、それを使うと楽です。
これで、Perl 5の標準モジュールが入っていないWebサーバー(繰り返しますが、そんな訳の分からない構成のサーバーは@nifty以外に知りません)でも、何とか本スクリプトを動作させることが可能です。
自分が欲しかった機能は、とりあえずすべて実装しました。何かバージョンアップのネタがあったら、メールでもいただければ幸いです。バグ報告も歓迎しています(バグ自体は歓迎できませんが)。
ほかにも以下のWebページ、書籍を参考にしました。これらの著者、翻訳者の方々に感謝します。
まだまだたくさんありますが、きりがないのでとりあえあずこんなところで(笑)。
約10年ぶりのバージョンアップです。今回JPCERT/CCより脆弱性の指摘を頂き、修正を行いました。名も知らぬ報告者様と、仲介いただいたJPCERT/CCに感謝を。
脆弱性の内容は、入力されたキーワードのエスケープが適切ではないため、CGIの呼び出し時にパラメーター 「keywords」に対してHTMLを含む文字列を指定することにより、その内容がそのまま表示されるというものです。これにより、ブラウザー上で任意のスクリプトが実行される恐れがあります。
攻撃手段としては、スクリプトなどを仕込んだkeywordsパラメーターを含むCGI呼び出し用URLを生成し、別のWebページ上からのリンクなどで誘導する、といったものが考えられます。このように、攻撃者は攻撃対象者を何らかの手段で特定のURLにアクセスさせる必要があるため、本脆弱性の影響は、単にスクリプト等を仕込んだWebページへアクセスを誘導するのと同程度に限定的であると作者は考えています。
なおこの脆弱性により、本スクリプトを設置したサーバー上の情報が漏洩することはありません。
感想、バグ報告などお待ちしております。
yujiro@finalbeta.jpまで。
Webサイト上でもアンケートなどを行なっていますのでぜひお越しください。
「Final β Laboratory」http://finalbeta.jp/