WordPress のセキュリティ対策・後編 (rev.4)
2008-11-19 22:55 /
後編では、Apache, PHP, MySQL というミドルウェアやサーバの設定やプログラミングを要する話題を取り上げます。但し、前回も述べたように、主として共有サーバへインストールするケースを想定していますので、もっと徹底して対策を取りたい方には不十分に見えると思います。その点だけ、あらかじめご了承ください。(なお、rev.2 では NONCE_KEY について、rev.3 では管理画面のベーシック認証について、rev.4 ではキーコードの追加について追記しました。)
適用範囲について
前回と同じく、共有のレンタルサーバへインストールするというケースを想定します。つまり、レンタルサーバ会社がシステムを用意していて、インストールもアップデートもレンタルサーバ側の代行システムを使って行うような、ASP として提供されている環境ではなく、公式サイトから自分でダウンロードした一式をアップロードするようなケースに限定します(レンタルサーバ側でセットアップしている環境では、サーバの運用も含めてレンタルサーバ会社に責任があり、彼らはここで述べるような対策はプロとして十分に心得ている筈です)。ちなみに、前回は明言しませんでしたが、本稿では WordPress のテーマについては取り上げませんので、ご了承ください。恐らくテーマファイルのプログラミングやコーディングは、PHP でのプログラミング全般にまで話が広がってしまうと思うので、本稿の範囲を超えてしまいます(もちろん、テンプレートタグの取扱い方だけに着目して項目を立てられるとは思いますが)。
前回は、利用する機能に合わせてアップロードするファイルを取捨選択するとか、主にロギングやチェックを目的としたプラグインをインストールするという、どちらかと言えば初歩的な対策をご紹介しました。今回は後編として、PHP のコードやサーバの動作をコントロールしてゆきますので、対策が可能なレンタルサーバ・プランが制限されるという意味でも、あるいは対策を実施するために要求されるスキルという意味でも、やや難しくなります。理屈の上ではもっと厳格な対策を加えられるとは思いますが、現実的なラインに妥協するという当初の方針は、たとえプログラミングやサーバの設定に手を入れるくらいの対策までご紹介するにしても、そのまま維持したいと思います。
もっと高度な対策を立てられることは承知しています。しかし、ここで想定しているユーザは、共有レンタルサーバという限られた範囲でしか利用の権利を与えられていない以上、どれほど重要な対策が考えられるにしても、サーバ会社をあるていど信用しなければならないでしょう。したがって、「もっと高度な攻撃にそなえる対策」が可能だからといって、それを「上級レベル」などと称してご紹介するつもりはないのです(他にも対策があるという事実は知っておいて損はないと思います)。今回のタイトルを「前編」「後編」として、わざとレベルの差という含みを与えなかったのには、そういう理由があります。
セキュリティ対策の要約
さて、今回も以下で述べる内容を箇条書きにして列挙しますが、これと同時にリソースもご紹介しておきます。
リソース
- ultraviolet, ‘WordPress Cookie のセキュリティ.‘ (2007.11).
- Frank Bultge, ‘WordPress sicherer machen.‘ (2008.01).
- NOUPE, ‘WordPress Security Tips and Hacks.‘ (2008.02).
- Lorelle VanFossen, ‘WordPress Security Prevention, Reactions, and?Scares.‘ (2008.04).
- ultraviolet, ‘WordPress のコードとしての問題.‘, (2008.07).
- BlogSecurity, ‘WordPress Security Whitepaper.‘ ver.1.2 (2008.08).
セキュリティ対策の要約(前編+後編)
- ブログシステムが必要かどうか判断すること
- 適正なレンタルサーバを選定すること
- 利用する機能を選ぶこと
- インストール後の設定を確認すること
- セキュリティ対策用のプラグインを導入すること
- 適正なレンタルサーバを選定すること(続)
- テーブル名の接頭辞を変更すること
- 固有のキーコードを設定すること
- プラグインで済ませてよいか検討すること
- ディレクトリへのアクセスを制限すること
- 管理画面へのアクセス制限を強化すること
- エラー表示を抑制すること
- ユーザの権限を制限すること
- パスワードの強度を上げること
- システムとプラグインのアップデート
- その他
以上のうち、強調表示している項目が後編の対策となります。
対策(6): 適正なレンタルサーバを選定すること(続)
WordPress 公式サイトに掲載してあるインストール手順は、簡潔で要を得たものになっているものの、実際にファイルをアップロードしてインストールをはじめるには、セキュリティの観点から、他にも考慮しておいてよい点があります。
WordPress のスクリプト一式をサーバへとアップロードするにあたって、そのアップロードという行為に問題がないわけではありません。これは何も WordPress やブログシステムに限った話ではなく、認証を経て何かのリソースへアクセスする場合には、何であれセキュリティを考慮する習慣が必要になってきます。実際、ふだん使っている FTP ソフトだと、途中の経路でパケットをキャプチャーされてしまえば、例えば、
16 2008-11-13 22:37:20.062250 192.168.1.59 219.***.***.*** FTP Request: PASS ************************
といったデータが丸裸で送信されていることが分かります。これは僕が Chicappa! のサーバにアクセスしたときの送信データを Wireshark というソフトでキャプチャーしたものです。言うまでもなく、伏字になっている部分で、FTP サーバの IP アドレスや、FTP アカウントのパスワードが全く暗号化されずに出力されていることが分かります。当然、その前の段階では FTP アカウントのユーザ名もしっかり送信されキャプチャーされているので、この通信を途中の経路で抜き取った人は僕のアカウント情報を使って Chicappa! のサーバにログインできてしまいます(念のため、このあとで既にパスワードは変更してあります)。
また、FTP ソフトで転送しているファイルの中身も、当然ながら全く暗号化もされずにサーバへ送信されているので、同じく Wireshark のパケットモニター画面には、
52 2008-11-13 18:37:26.252594 192.168.1.59 ****.chicappa.jp FTP Request: TYPE I
53 2008-11-13 18:37:26.268750 ****.chicappa.jp 192.168.1.59 FTP Response: 200 Type set to I
54 2008-11-13 18:37:26.281507 192.168.1.59 ****.chicappa.jp FTP Request: PORT 192,168,1,59,32,202
55 2008-11-13 18:37:26.302330 ****.chicappa.jp 192.168.1.59 FTP Response: 200 PORT command successful
56 2008-11-13 18:37:26.314724 192.168.1.59 ****.chicappa.jp FTP Request: STOR wp-config.php
[...]
61 2008-11-13 18:37:26.367483 192.168.1.59 ****.chicappa.jp FTP Request: <?php
[...]
68 2008-11-13 18:37:26.395537 ****.chicappa.jp 192.168.1.59 FTP Response: 226 Transfer complete.
というログが残り、上記の中で強調した部分のパケットを解析すると、しっかりと wp-config.php の中身が記録されていることが分かります。
これでお分かりのように、多くの方は何気なく FTP ソフトでサーバにログインしているわけですが、この行為は情報セキュリティの管理者やシステム開発を仕事にしている人たちから見ると、自然なことでもありませんし、ましてや安全な行為ではありません。通常、少なくとも ISMS(JIS Q 27001:2006)への適合性について認証を受けている企業では、メールに ID とパスワードのペアを書いて平文のメールとして相手に送信するような愚かな行為を禁止するのと同様に、ごくふつうの FTP ソフトでサーバにログインするという行為も禁止している場合が多いのです(逆に、そうしていないシステム開発会社さんやウェブ制作プロダクションさんへは、現状について強く再考されるようお薦めします)。
すると、一般ユーザとしてはどのような対策が可能でしょうか。もっとも簡単なのは、FileZilla や WinSCP を使って SFTP でファイルを転送することでしょう。但し、レンタルサーバ側で OpenSSH などを導入し、ssh での接続がサポートされていなければなりません。ところが、低価格帯の共有レンタルサーバではサポートされていないことも多いので、これはあまり現実的な対策ではないかもしれません。
レンタルサーバによっては、ブラウザでアクセスして利用できる FTP 機能を提供しており、FTPS という SSL で暗号化された FTP 通信によって、ファイルをアップロードできる場合があります(ちなみに、Chicappa! では SFTP が使えない代わりに FTPS でのファイル転送を提供しています)。FTPS の場合は、SmartFTP というクライアントソフトも使えます。しかし、これもサーバ側で FTPS に対応している必要があるので、どこでも使える方法ではありません。したがって、まだサーバを借りていないなら、FTPS/SFTP のどちらかがサポートされているレンタルサーバを選択するのはよい考えです。但し、個人で共用のレンタルサーバを借りる場合は、「適正なレンタルサーバを」とは言っても選択肢が限られるので、もっと他に優先したい条件(利用料金や機能)があるのは当然と思います。
対策(7): テーブル名の接頭辞を変更すること
WordPress のインストールは非常にシンプルなのですが、セキュリティの点から何も問題がないというわけではありません。なぜなら、ブラウザ上のインストール処理はすぐに済みますが、その準備として作成する wp-config.php の内容をしっかり確認しつつ設定しなければならないからです。その点、公式サイトにあるインストールの説明は、インストールして動かすまでの手順という大局的な面では要を得ているものの、セキュリティという面から見ると簡略すぎる印象を受けます。
WordPress の書籍を上梓されたフランク・ビュルジ(Frank Bultge)さんのブログなど、多くのブログやフォーラムで指摘されている点として、データベースの接頭辞(prefix)を変更するというものがあります。デフォルトでは、wp-config.php に、
$table_prefix = ‘wp_’;
という設定項目があります。ふつう、これはレンタルサーバでデータベースが1個だけ使えるという環境のときに、複数の WordPress を運用したい人が ‘wp1_blog_’ とか ‘wp2_blog_’ などと、それぞれのブログで使うテーブル名を区別するために、個別に設定する項目だと理解している人も多いのですが(そしてそれは間違ってはいません)、セキュリティの観点からは別の目的があります。一般に、他人に推測されやすいファイル名やディレクトリ名あるいはデータベース名でシステムを運用することは望ましくありません。そして、WordPress のように中身が誰にでも公開されているシステムの場合、デフォルトの “wp_” という接頭辞で運用すると、データベースへのアクセスは既存の処理に任せて、データの挿入や削除だけを SQL クエリの送信で実行できてしまうようなセキュリティホールが突かれている状況では、攻撃対象となるテーブル名が簡単に推測できてしまいます。
この状況を詳しく説明すると、まずデータベース(サーバ)にデータを追加したい場合、PHP のプログラムは MySQL サーバに対して接続要求をかけます。ホストやアクセス情報を指定して MySQL サーバとの接続が成功しストリームが開いて実行環境にリソースが確保されると、それ以降は接続を明示的に閉じるか、あるいは PHP の仕様に沿って自動的にリソースが解放されるまで、そのアカウントに付与された特権に応じて、SQL クエリを実行できます。スクリプトの処理が終了すると、データベースへの接続は明示的に mysql_close() 関数を使えば閉じられますが、あからさまに接続を閉じなくても、スクリプトの実行が終わると、リファレンスカウンティングという Zend Engine の仕組みを使って自動的にリソースが解放されるわけです。すると、最初にデータベースとの接続さえ通常の処理の範囲で成功させれば、その後に SQL 文を実行するような不正コードが挿入されてしまうと、デフォルトのインストール環境では、
INSERT INTO `wp_posts` (`ID`, `post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_category`, `post_excerpt`, `post_status`, `comment_status`, `ping_status`, `post_password`, `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`, `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`, `post_mime_type`, `comment_count`) VALUES (NULL, ’0′, ’0000-00-00 00:00:00′, ’0000-00-00 00:00:00′, ”, ”, ’0′, ”, ‘publish’, ‘open’, ‘open’, ”, ”, ”, ”, ’0000-00-00 00:00:00′, ’0000-00-00 00:00:00′, ”, ’0′, ”, ’0′, ‘post’, ”, ’0′);
という形式のクエリで、データベース名など分からなくても攻撃者にデータを容易く挿入されてしまいます。上記で強調した “wp_posts” は、wp-config.php で設定した接頭辞と、デフォルトでテーブル名に使われる “posts” との組み合わせに過ぎません。具体的には、WordPress 2.7 beta2 の /wp-includes/wp-db.php を開けば、254行目では次のようになっています。
var $tables = array( ‘users’, ‘usermeta’, ‘posts‘, ‘categories’, ‘post2cat’, ‘comments’, ‘links’, ‘link2cat’, ‘options’, ‘postmeta’, ‘terms’, ‘term_taxonomy’, ‘term_relationships’ );
したがって、’wp_’ の部分がデフォルトのままだとテーブル名を推測するのは非常に簡単です。というよりも、接頭辞と残りの文字列を組み合わせてもデフォルトのままなので、いちいち推測する必要もありません。すると、ひとたびデータベースへの接続が成功している状況で SQL インジェクションのような攻撃を受けると、運用中のテーブルに容易くアクセスできてしまいかねません。
では、どのように対策を加えればよいでしょうか。具体的には、推測されにくい文字列であればよいので、パスワードを生成してくれるサイトを利用して、ランダムな文字列を8~10桁ほど準備すればよいでしょう。パスワード管理ソフトあるいは iGoogle 用のガジェットで生成した文字列を使ってもよいと思います。すると、
$table_prefix = ’5vXrpSFYic_’; // これで、約60ビットの強度
のようになります。
ちなみに、僕が会社でメンテナンスしている WordPress の場合、プロダクトリリースでもありますから、MySQL のテーブル名(識別子)として許容されている最大値の64バイトに近い桁数でテーブル名をつくっています(今後のバージョンアップで接頭辞の後につく文字列が増える可能性もありますから、現状で最も長くなる「接頭辞」+ “post_content_filtered” からつくられるテーブル名を64バイトぎりぎりにしていると余裕がなくなりますから、接頭辞の部分はせいぜい24~32バイトくらいで指定すれば十分と言えます)。なお、MySQL 5.1 以降では、識別子のテーブルに入る文字列はバイト数ではなく文字数でカウントされているため、マルチバイト文字(日本語など)でテーブル名を作っても「テーブル」は4文字とカウントされますが、MySQL 5.1 で運用されている安価なレンタルサーバはそれほど多くないと思われますし、識別子にマルチバイト文字を使うのは、現状では一般的な習慣とは言いがたく、ファイル名や関数名にマルチバイト文字を使える環境も限られていますので、ISO-8859-1 Latin1 に沿って、手堅く半角の英数文字だけを使っておくほうが無難です。
もちろん、そのように対策して設定した wp-config.php を、先にご紹介した SFTP のような方法でファイル転送しない限り、どれだけ文字列の推測可能性を下げるように設定しても無意味になるかもしれません。PHP のコーディングで対処できることには限界があるという、よい事例になるでしょう。
対策(8): 固有のキーコードを設定すること
WordPress のアップデートに際して、テーマやプラグインや wp-config.php を除くシステムファイルを単純に上書きだけしているような豪快な方は、wp-config.php に取り込まれている変更に気づきにくいと思われます。ですが、アップデート情報と共に変更点を確認されている方はお気づきのとおり、現行のバージョン(2.5 以降)では3種類のキーコードを wp-config.php で設定するようになっています(追記: 2009-01-25: バージョン 2.7 からは “NONCE_KEY” というキーコードも追加されました。今後はバージョンアップに伴って本稿を更新したりはしませんので、ご注意ください)。
define(‘AUTH_KEY’, ‘put your unique phrase here’); // 固有の語句に変更してください。
define(‘SECURE_AUTH_KEY’, ‘put your unique phrase here’); // 固有の語句に変更してください。
define(‘LOGGED_IN_KEY’, ‘put your unique phrase here’); // 固有の語句に変更してください。
上記の設定は、日本語版の WordPress に入っている wp-config-sample.php (wp-config.php の雛形)から拾ってきた内容です。これらはハッシュ値を導き出す際の salt 値を与えており、サイトに固有のハッシュ値を出力するためには、上記の定義値をデフォルトの文字列ではなくオリジナルの文字列にしておく必要があります(厳密に言うとハッシュ関数の演算結果は不可避的にコリジョンの可能性をもつのですが、可能性を下げることはできます)。もちろん、デフォルトのままでも md5() や sha1() を使って wp_rand() ルーチンの中でハッシュ値は計算されますし、古いバージョンで使っていた wp-config.php のまま LOGGED_IN_KEY が定義されていない場合ですら、勝手にハッシュ値は計算されます。しかし、AUTH_KEY などの salt 値を導入しないと、パスワード解析のいわば「過去ログ」を使う、レインボーテーブル攻撃への耐性が下がってしまいます。またデフォルトの salt 値を使っていると、ハッシュ関数の引数のうち salt 値の部分は既に攻撃者にはバレてしまうので、salt 値を使う意味が殆どなくなってしまいます。実際、これらの salt 値が wp-config.php に設定されていることに気づかなかったとか、salt 値が三種類に増えていたことを知らなかったユーザがデフォルトの salt 値を使い続けるといったことが、WordPress のセキュリティ上の問題として広く語られていた時期もありました。
このような次第で、上記の三つのキーには(少なくとも、デフォルトの文字列とは別の)オリジナルな値を設定しましょう。この場合も先の節で説明したテーブルの接頭辞と同じく、他人に推測されにくくて、それなりに長い文字列を設定すればOKです。理論値については、Ivan Lucas さんのサイトで紹介されていますので、興味がある方はご参照ください。僕の場合は、パスワード管理ソフトに付属しているパスワード生成機能を使って、64バイトていどの文字列を三つ生成して使っています。なおデジタルコンピュータは、例えば「1から10までのあいだで任意の値」といっても、コンピュータが現実に扱える有理数の範囲でしか値を弾き出せません。WordPress においても、過去に 2.6.2 へのアップデート内容が mt_rand() 関数を単純に(salt をユニークに設定しないで)使っただけでは擬似乱数としての強度が高くないという点にあったこともあります(日本語の解説としては、大垣靖男さんの記事が参考になります。ときどき化けてますが)。このような次第で、設定の例としては以下のようになります。
define(‘AUTH_KEY’, ‘SxNVv8atKcU46UWPbACaMoqlgkhPiQmBM3AwgHE7qzWLUed395H4Q_oXy-no9GKh’);
define(‘SECURE_AUTH_KEY’, ’1s1yVw_4EPQy-r8_EBtFSMv_o8KwRs_zfocte4JHCz_J8mws3hCPWiIlPB9LX1UI’);
define(‘LOGGED_IN_KEY’, ‘oDtyeg8qPwk0Ta9XkSDsQocVaxEL6e9xZW4_JiiVDnK0wpzaHlICXNMzd46hgBnh’);
パスワード生成機能を何度か使って、強度が 300 ビット以上になるようにしています。もちろん、桁数は同じでも更に幾つかの記号を文字列に使えば、salt 値の強度を上げることは可能です。パスワード管理ソフトやパスワード生成ソフトを使っていない方は、WordPress.org の SECRET_KEY ジェネレータで出力してみるとよいでしょう。
追記: 11/21・キーコードを三つとも出せるジェネレータもあります。SECRET_KEY は古いバージョンのキーコードなので、現行のバージョンでは使えないことに注意しましょう。なお、どちらを参照するにしても、HTTPS では接続できない URL なので、表示されたキーコードをそのまま使うのは遠慮するほうがよいでしょう。
追記: 12/13・本日公開された WordPress 2.7 からは、上記のキーに加えて “NONCE_KEY” という設定もあります。wp-condig.php をご確認頂いて、こちらも同様にユニークな文字列を指定して下さい。
追記: 2010-08-22・現在、キーコードは更に増えて八つとなっています。
対策(9): プラグインで済ませてよいか検討すること
どうしてこのような項目を加えたのか、疑問に思うかもしれません。そういう方には、ここで述べているセキュリティ対策が、個々の具体的なプラグインの紹介であるとか、あるいはコーディングの紹介をしているように見えても、本来は「攻撃ベクトルを認識して、対策の指針を考えましょう」という提案を含意しているのだという点を、ご理解いただきたいと思います。ここまでにご紹介した、テーブルの接頭辞を変更するという対策や、wp-config.php の salt 値をユニークに設定する(もちろん厳密にはユニークではないので、少なくともデフォルトから変更するということ)という対策には、実はどちらもその対策を実行してくれるプラグインが存在します。しかし私見としては、そのようなプラグインを使うことには異論を述べておきたいと思います。
この後編に関心をもってお読みいただいている方は、.htaccess や PHP スクリプトの編集もあわせて、セキュリティ対策を検討されているのだと思います。そして、そのような方であれば、wp-config.php をテキストエディタで開いて編集することと、wp-config.php をプラグイン経由で編集することを比較してみたときに、前者の運用負荷が格段に大きくなるとは思えません。テーブルの接頭辞については、インストール時に wp-config.php を編集するか、あるいは定期的に変更するくらいのもので、管理画面へアクセスして記事を書くたびにテーブルの接頭辞を変更するような人はいないでしょう。頻度という点から言って、せいぜいその程度の回数しか行わない対策のためにプラグインを導入するというのは、あまり効果的とは言えません(こういうことを書くと誤解する人がいるかもしれませんが、僕は他人の善意とか努力を「無駄である」とか「余計なお世話だ」と、問答無用に撥ね付けているわけではありません)。
そして、幾つかのブログでも指摘されている点ですが、プラグインを使ってファイルを更新(ファイルの中身を書いたり読んだりする)するという仕組みそのものにもセキュリティ対策が必要となってしまいます。単純に言って、完全無欠なセキュリティ対策を施しているプラグインというものがあるとは思えないので、管理用の設定ファイルを読み込んだり上書きするという重要な作業に、新しい脆弱性が加わる可能性を排除できない以上、そのような目的にはプラグインを利用すべきではないだろうと考えます。もちろん、PHP のコードが分からないという方は、そういうプラグインを使わざるを得ないかもしれませんが、それでもツールを使って設定ファイルを読み書きしていることのリスクは理解してほしいと思います。
また、WordPress において実行されるアクションやファイルの更新履歴をトレースするようなプラグインについても、公開されているプラグインはわたしたちだけでなく攻撃者にも動作原理を明らかにしているのです。もし弱点があれば、プラグインによって記録される履歴情報を偽りつつ不正なアクセスを試みるプログラムが作成されるかもしれません。
対策(10): ディレクトリへのアクセスを制限すること
WordPress のセキュリティ対策として頻繁に提示されるパターンは、大きく言って「ディレクトリアクセスの制限」、「権限の制限」、そして「通信経路の暗号化」となります。つまり、WordPress のコアシステムを改変するという PHP のコーディングではなく、ウェブサーバの挙動をコントロールすることに重点が置かれていると考えられ、これは WordPress というシステムをアップグレードしてもセキュリティ対策の効果が維持されやすいというメンテナンス性にも寄与します。そこで、次にディレクトリアクセスを制限する方法をご紹介しましょう。
ディレクトリへのアクセスを制限する方法は、/wp-admin について加える制限と、/wp-content, /wp-includes について加える制限とで、細かい点が違います。まず、/wp-admin つまり管理用のファイルが入っているディレクトリへのアクセスを制限するには、WordPress の管理ユーザがどのような条件で管理画面にアクセスしているのかを特定しておく必要があります。或る決まったマシンやネットワークセグメントからだけアクセスするなら、例えば自宅で使っているマシンや会社で使っているマシンからだけ管理画面にアクセスするというケースに限定して、アクセスを幾つかのやり方で制限できます。
具体的には、.htaccess に IP アドレスを指定して /wp-admin へのアクセス元を制限します(俗に「IP 制限」と言われます)。例えば、
Order deny,allow
Deny from all
Allow from 202.***.***.***
Allow from 168.***.***.***
のように記入した .htaccess というテキストファイルを /wp-admin 直下にアップロードして、202.***.***.*** と 168.***.***.*** からのアクセスだけを認めるようにすればOKです。
但し、アクセス元の IP アドレスを制限してしまいますから、不意に友人の自宅から記事を書きたいときや、何か緊急の必要があって出先から管理画面を使いたいとしても、許可している IP アドレスとは異なるアクセス元からのアクセスになるので、管理画面は使えません。もちろんそのときに FTP でサーバにアクセスできれば、一時的に .htaccess を削除するかリネームすればよいわけですが、あとで必ず現状復帰しておく必要があります。
また、「自宅マシンの IP アドレス」とか「会社マシンの IP アドレス」と言っても、その調べ方を正確に知らなかったり、.htaccess に記述するときの書式を正確に知らなければ、自分自身が管理画面から締め出されてしまうことになりかねません(笑。一般に、かなり小規模の会社でも、ネット接続を社内で共有している限り、個々のマシンはゲートウェイの役割を果たす機器から DHCP によるローカル IP アドレスの割当てを使ってぶら下がっている場合が多く、ゲートウェイのグローバル IP アドレスはなかなか教えてもらえないかもしれません。また自宅のルータが ISP から貸与されているものであれば、多くの場合に IP アドレスは一定の期間にわたって契約者へ「貸し出し」されるという体裁をとるため、何日か経ってルータを再起動したら IP アドレスが変わってしまっているということもありえます(高木浩光さんの記事が参考になります)。したがって、IP 制限を安定して運用するには、インターネットに接続するためのゲートウェイになる機器へ割り振られた IP アドレスが固定されている環境が最適と言えるので、固定 IP アドレスを取得できる接続契約をしている方は、ぜひ IP 制限を検討してみてはいかがでしょうか。
もちろん、IP アドレスが一定期間ごとに動的に割り振られるような接続環境でも、ISP が使っている IP アドレスの数には制限があるため、サブネットマスクを指定して、動的に割り振られる可能性がある IP アドレスの範囲からくるアクセスをすべて許可してしまうこともできます。ただし、この場合は、あなたと同じプロバイダと契約してネットに接続している人から攻撃されても対処できません(もっとも、まともなクラッカーは自分の使っている IP アドレスが直に判明するようなやりかたでアクセスしたりはしないものですが)。
次に、/wp-content, /wp-includes へのアクセス制限ですが、どちらも JavaScript ファイルやテーマのスタイルシートファイルが入っているので、単純に .htaccess で Deny from all にしてしまうと、ページが正しく表示されません。そこで、リソースの項でご紹介した WordPress Security Whitepaper に紹介されているような、例外のルールを追加します。
Order allow,deny
Deny from all
<FilesMatch “\.(css|jpeg|jpg|png|gif|js|swf)$”>
Allow from all
</FilesMatch>
ブラウザからのアクセスを認めるファイルの拡張子を正規表現としてマッチさせて、該当するファイルをアクセス制限から除外します。もちろん、他に /wp-content に .txt や .doc など公開したいファイルがある場合は、それらの拡張子も登録しておきます。なお、上記でお分かりのように、僕は WordPress Security Whitepaper で紹介されている内容と少し違って、Files ディレクティブではなく FilesMatch ディレクティブを使っています(2.x 系以降の Apache では FilesMatch の方が推奨されています)。それから、/wp-content と /wp-includes ではアクセス制限から除外すべきファイルの種類が違う場合がありますので、ご注意ください。特に /wp-includes は上記のままだとエディタの TinyMCE が動作しなくなる可能性があります。.htaccess の書き方に不安がある方は、AskApache Password Protection というプラグインで .htaccess を使った数多くの設定が行えますから、検討してみるとよいでしょう。
対策(11): 管理画面へのアクセス制限を強化すること
さて、/wp-admin はブログをリモートからコントロールする管理画面を提供するので、更にアクセスを制限する方法が提案されています。しばしば、「多重レイヤー防御 (multi-layer protection)」と呼ばれており、ここではその一つとして、通常のログインに加えて /wp-admin ディレクトリへのアクセスにベーシック認証を導入するという対策を検討します。
既にご存知の方には言うまでもないことですが、ベーシック認証を導入すると /wp-admin 配下のファイルへアクセスする全てのリクエストに認証ステータスが要求されます。したがって、画像や JavaScript スクリプトファイルが一個だけ /wp-admin 配下にあって、ベーシック認証を要求しないウェブページでそれを読み込んでいるという場合でも、認証を要求する小さなウィンドウが出てきます。WordPress の管理画面へログインするときのページは、WordPress のシステムが動作させている認証のしくみなので、PHP の動作と関係のない別の認証方法を組み合わせると、WordPress のシステムに問題があったとしても、それだけでは管理画面に入れません。これが多重レイヤーと呼ばれる理由です。もちろん、ベーシック認証では Base64 にエンコードしただけで ID やパスワードをサーバへ送信するため、先に述べた FTP の場合と同様に、途中の経路でパケットを盗聴されてしまえば危険であることに変わりはありません。したがって、更に強い条件で認証を行うには、SSL 通信を使うことが望ましいと言えます。
しかし共用のレンタルサーバでは、SSL 通信を提供している場合でも、SSL で使うサーバの証明書はドメインに対して発行されるので、サーバを共有する多数のユーザに一つずつ証明書を配布することはできません。最近では SSL のサーバ証明書は安くなってきているとはいえ、それでも有効期限1年のもので3万円~9万円します。したがって、SSL 通信を使うときだけのドメイン(よく、’sv06.domain.com’ などと通し番号になっているドメイン。そのサーバに何百、何千という契約者の領域が割り振られています)でサーバの証明書を取得して、個々のユーザにはそのドメイン配下のディレクトリを ‘sv06.domain.com/user01′, ‘sv06.domain.com/user37′ などとして使ってもらうパターンが多くなります(このあたりの具体的な話は、自分のマシンに Apache, OpenSSL, Bind といったソフトウェアをインストールしてみて、ウェブサーバや DNS サーバの設定を自分自身でやってみることが効果的です。面倒を厭わなければ全て無料で手に入りますし、セキュリティ対策の知識も格段に上がることは保証できます)。このような利用条件だと、WordPress の管理画面だけを SSL 通信が可能な領域に移動させるのは難しくなります。
追記: 2008/12/27
なお、/wp-admin にベーシック認証を設定すると、記事の編集画面で使う FLASH 版のアップローダで画像をアップロードするときに認証ウィンドウが出ます。そのままアップロードできるときはよいのですが、筆者の環境ではブラウザがフリーズするため、セキュリティを考慮している方は、ベーシック認証の方を諦めるのではなく、FLASH アップローダの方を諦めましょう。こちらには HTML 版という代替があるからです。
対策(12): エラー表示を抑制すること
この対策は、PHP の開発全般あるいはウェブサーバの設定という話題でも目にする方が多いでしょう。ブログやウェブサイトへアクセスしたときに、MySQL の接続エラーや PHP のエラー、あるいは Apache の Internal Server Error の画面をご覧になったことがあるかもしれません。一般に、このようなエラー画面をリリースサーバ(または「プロダクション・サーバ」とか「公開用サーバ」あるいは「本番サーバ」などと、会社によって呼び方は色々とあります)で表示するのは、エラー画面に出力される情報が攻撃者へのヒントになるので、好ましくないとされています。
ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/lib/mysql/mysql.sock’ (2)
これは、MySQL に接続したときのソケットファイルにアクセスできないというエラーです。別のバージョンの MySQL を入れて複数起動してしまったとき(古いバージョンが動いていて同じソケットファイルへアクセスし続けている)や、ソケットファイルを root で手動作成したとき(mysql というユーザの権限では開けない)、そういった場合に出るエラーです。
Notice: Call to undefined function hogehoge() in /var/www/public_html/hoge.php on line 3
これは特に説明の必要はないと思いますが(冗談で出力させたエラー表示ですので)、hogehoge() などという関数はないというエラーです。もちろん、ユーザ定義関数として作れますが、この hoge.php には全く定義を書いていませんから、エラーが出るのは当然です。しかし問題はそういうことではなく(笑、
- 上記の表示はスクリプトが返すエラーなので、レスポンスとして 200 が攻撃者のユーザエージェントに返され、少なくとも PHP が動作していることが分かってしまう。display_errors Off なら 500 が返るので、特定できることが少なくなります。
- どちらもファイルのパスを表示しています。酷い場合は DocumentRoot からではなく root からのフルパスが表示されてしまいます。
- PHP スクリプトにもっと具体的なバグがあって表示されてしまうと、どんな抜け道があるかが攻撃者に分かってしまうかもしれません。
といった各点にあるわけですね。開発用のテストサーバ(「ステージング・サーバ」など、これも会社によって呼び方が違っていたりします)で動かすときは詳細なエラー表示は便利かもしれませんが、リリースサーバの運用にあたっては、攻撃されやすくなるヒントは与えるべきではありませんから、こうした表示は出ないように配慮したほうがよいでしょう。具体的な対策としては、
- Apache の httpd.conf(最近のバージョンだと /conf/extra/httpd-default.conf の方に設定する)で ServerSignature Off とディレクティブを設定する。
- PHP の php.ini で display_errors = Off と設定する。
この二つを行います。もちろん上記のやり方そのままだと、どちらもサーバをコントロールできる強いユーザ権限が必要になってしまいます。そのため、共用のレンタルサーバを借りているユーザさんは、条件が許されるなら、上記の設定を .htaccessで行うことになります。「条件が許されるなら」というのは、.htaccess が使えない共用サーバもあるからです。もし .htaccess の利用が認められているなら、
ServerSignature Off
php_flag display_errors Off
php_flag display_startup_errors Off
を、WordPress のホームディレクトリにある .htaccess へ追記すればよいでしょう。なお、Apache の設定項目(ディレクティブ)には「コンテクスト」と呼ばれる条件があり、Apache のディレクティブをなんでもかんでも .htaccess に書けるわけではありません。PHP のディレクティブにしても同じで、php.ini でしか設定できない項目もあります。ServerSignature は、.htaccess に書けるコンテクストがあること、そして display_errors は PHP_INI_ALL のコンテクストにあって php_flag で設定可能なので .htaccess へ記述して使えるわけです。それから PHP の起動シーケンスでエラーが起きると、実行時のオプションとして display_errors が設定されていても PHP のエラーが出力されてしまうので、display_startup_errors も Off にするよう推奨されています。
また、この手の設定スウィッチを PHP スクリプトの中に ini_set() で書く人もいますが、そのスクリプトがコンパイルエラーなどを起こすと、そもそも ini_set() が実行されないので意味がありません。また、header.php などとして全てのファイルで require_once() しなくてはならないという点でもメンテナンス性が低いと言えるでしょう(.htaccess で設定すれば、新しい PHP スクリプトがどれだけ増えても何もしなくてかまいません)。
対策(13): ユーザの権限を制限すること
これは、ブログの記事を書くような作業であれば「制限ユーザ」で行うことという、OS のユーザ管理と全く同じ理由で提案されている対策です。もちろん WordPress をインストールするときは管理者権限のユーザを使うわけですが、ふだんの記事投稿に管理者権限が必要とは思えません。必要かつ最小限の権限で運用することが適切です。
この話題については、WordPress 本体のユーザ権限と、データベースのユーザ権限に分けて提案されています。WordPress をいちユーザとして使うときは権限を弱くするという課題については、ユーザ権限を指定して「制限ユーザ」を作れば簡単に実現できるでしょう。せいぜい「編集者(editor)」か「投稿者(author)」にしておけばよいと思います。あるいは、更に細かく「役割(role)」を設定して、オリジナルのパターンで権限をもったユーザをつくってブログを運用したい場合は、Thomas Schneider さんが Role Manager というプラグインを公開されています。

次に、MySQL のユーザにも権限の概念があるため、インストールが完了してからはテーブルやデータベースを削除するといった強い権限は不要でしょう。専有サーバやホスティングと比べて、共有レンタルサーバのユーザにどこまでの権限があるかはサービスによって違うと思いますが、最低でも作成したデータベースに操作の範囲を制限されているはずです。すると、データベースそのものを削除するような権限までは持っていないと仮定して(つまり、レンタルサーバのコントロールパネルで DB ユーザをつくったときに、そのユーザと root だけが権限を付与されている、ユーザに固有のデータベースが自動的に作成されるような仕様になっていると仮定して)、WordPress つまり PHP の mysql_* 関数で行えたりクエリを実行できる範囲を、不要であれば狭くしていくという対策をとることが望ましいと言えます。ただし、共用のレンタルサーバで作成される DB ユーザには、ユーザの権限そのものを設定するような権限(Privilege)は与えられないのが当たり前とも考えられるので、WordPress の管理者やサーバの FTP ユーザではなく、レンタルサーバのコントロールパネルへアクセスする権限で DB ユーザの権限を制限できなければ、あまり効果的ではないでしょう。上記のような、WordPress のユーザの役割を制限できたからといって、攻撃者が勝手に DROP TABLE のクエリを投げるのを止めさせることはできないからです。ただし、これは共有のレンタルサーバという条件で考えているだけなので、もちろん mysql の root 権限までもっている方は、きちんと GRANT しておくべきでしょう。
対策(14): パスワードの強度を上げること
これも WordPress の運用に限った話ではありませんが、WordPress のユーザについては、権限の弱いユーザで運用するときは自分にとって覚えやすいパスワードを設定しておいて、管理ユーザには強いパスワードを設定しておくという区別をしておく場合もあると思います。逆に言うと、ふだんから使うアカウントは利便性を考慮して比較的弱いパスワードを設定するからこそ、権限を弱くしておく方がよいのだとも言えるでしょう。それ以外の、いちど設定すれば頻繁にログインしないアカウントは、できる限りパスワードの強度を上げておきたいところです。
では、具体的にパスワードはどのていど強くすればよいのでしょうか。これは利便性を考慮すると、権限の弱いユーザについてはなかなか一口に言えませんが、それでも10桁以上の大小英数文字をランダムに組み合わせて作ることが望ましいと思います。_)(*&^%$><”:} といった様々な記号を織り交ぜられたら更に強度は上がります。権限の強い管理ユーザについては、暗記などしなくてもパスワード管理ソフトなどで記録しておけばよいので、大きな桁の文字列で設定しておくようにしましょう。なお、パスワードはデータベースの user_pass フィールドに varchar(64) で定義されていますが、これはメッセージダイジェストの桁数なので、パスワードの桁数が最大で 64 という意味ではありません(試しに 128 桁でパスワードを設定してみるとよいでしょう)。
ご参考までに、僕が勤めている会社で運用している規程を一部だけご紹介すると(僕が策定して情報セキュリティ委員会の承認を受けているわけですが)、FTP アクセスに関してはユーザ名とパスワードの両方を32バイトのランダムな文字列で設定しています。特に、ホームディレクトリ設定で最上位のディレクトリパスを制限していないアカウント(いわゆる admin とか web など)は、最低でも毎月パスワードを変更するようにしています。逆に、案件で一時的に利用するディレクトリをホームにしているアカウントは、8桁ていどのパスワードにして案件が終了するまで貸し出します。
強度にずいぶん差があるという印象をもたれるかもしれませんが、私見では情報セキュリティの管理策はむやみやたらとセキュリティレベルだけを上げることが目的ではないと思うのです。もちろん、社内の不平不満を抑えることよりも、クライアントや株主あるいは情報を登録している一般ユーザの利益を優先して厳格なルールを立てる場合もありますが、だからといって可用性や事業継続性という観点を無視して策定するような規程には、企業のルールとして殆ど価値がありません。情報セキュリティにかんする意識や規程は、現実的な管理策や牽制を図りながらバランスを考えて徐々に根付かせていくしかないと考えます。
なお、パスワードの作り方については、先にテーブルの接頭辞を変更する対策などでご紹介しているので、ここでは割愛します。
対策(15): システムとプラグインのアップデート
これはもちろん、情報セキュリティを扱っている多くのサイトやブログで推奨されている対策です。WordPress 本体やプラグインがアップデートされたら早めに反映させましょうという点では、特に問題なく同意できます。ただ、アップデートの方法については、先にプラグインを使うことの是非を述べた理由と同じく、オンラインでの自動アップデートを行う場合は、管理画面に FTP のアクセス情報を登録するということのリスクを改めて検討しておくべきだと思います。もちろん、サーバ側でサポートされている場合は、

この自動アップデート画面では、いちばん下の「SSL を使う」という設定を有効にしましょう。
対策(16): その他
さてここまで色々な対策を見てきましたが、もちろんこれらの他にも対策は提案されています。
例えば、「ディレクトリ・リスティング」と呼ばれる状態を防ぐために、ダミーの index.html をアップロードするという対策があります。ディレクトリ・リスティングは、Apache の mod_autoindex モジュールがロードされていて Options ディレクティブの設定に -Indexes が指定されておらず、加えて DirectoryIndex ディレクティブに指定されているファイルが存在しなければ、mod_autoindex がディレクトリ内のファイルをリスト表示するという動作を指しています。Apache の動作としては全く正常な動作ですし、こういう動作が必要とされる場合もあるわけですが、必要なければディレクトリ内のファイルが一覧できてしまう事態は避けたいものです。例えば、フレームワークを導入してウェブアプリケーションを動かす場合、mod_rewrite を使ってセグメントベースのアクション呼び出しを行わず、フロントコントローラからのディスパッチだけでアクションを呼び出しているとしましょう。コントローラやビューのスクリプトを格納するディレクトリはドキュメントルート以下に置かず、ディレクトリ名も難読化するのが開発の常識ですが、難読化しているからというだけでドキュメントルートの配下に置いてしまう人もいます。しかし、ダミーの index.html もなく Indexes が有効になっていると、ディレクトリ・リスティングによってディレクトリ名が公開されてしまう可能性もあります。

WordPress の環境では、/wp-includes, /wp-admin はおおむね誰の環境でも同じと言えますが、/wp-content/plugins がディレクトリ・リスティングの状態になっていると、どういうプラグインを使っているか(アクティブかどうかは分かりませんが)が分かってしまい、脆弱なプラグインを使っていれば攻撃の的になるかもしれません。理由は分かりませんが、2.6.3 でもここにダミーの index.html は入っていないので、もしお使いの環境に入っていなければアップロードすることをお薦めします。中身はなくても構いません(逆に、「このディレクトリへのアクセスは許可されていません・・・」などと、いかにもセキュリティ対策をしていますという姿勢を見せると、攻撃者の意欲を刺激してしまう場合があります)。
次に、wp-config.php が重要なファイルであることは既に何度も述べましたが、対策(10) の応用として、.htaccess で wp-config.php へのアクセスも制限しようと言われているので、これは WordPress のインストールディレクトリ直下にある .htaccess へ、
<FilesMatch “^wp-config\.php$”>
deny from all
</FilesMatch>
という記述を追加すればよいでしょう。
それから付け足しの話題として、コアシステムへの「ハック」を安易にやるなというアドバイスを書いているブログがあります。これについては、あなた自身の PHP やセキュリティに関する知見に依存するので、本稿が対象としている共有レンタルサーバのユーザを対象として指針を提案するなら、同意せざるをえません。したがって、本稿でご紹介すべきセキュリティ対策には、コアシステムの改変は含めないことにしたわけです。もっとも、WordPress を使っているユーザの大半はコアシステムに手を入れたりはしないでしょうし、スクラッチから PHP で書けるテーマ作成の方がよほど危険だと言えます。
最後に
ここまでにご紹介した対策の他にも、理屈として考えられる対策だけでなく、共有のレンタルサーバでも実効性のある対策はたくさんあります。中でも、今回は WordPress のコアに手を入れるような対策はご紹介していませんので、不十分だと感じる方もおられるでしょう。それから、テーマの作成や導入にあたって注意したい点についても、もちろん言うべきことは数多くあるのですが、これらも今回は割愛しています。
ここでご紹介した対策だけでも色々とあるわけですが、要点としては具体的にコードをどう書くとか、どういうプラグインを使うということが言いたかったわけではありません。あくまでもセキュリティ対策を導入するときの指針として、みなさんのブログやウェブサイトについて、機密性を一定のレベル以上に保つべき部分はどこなのか、どういう機能を完全性や可用性の点から安定して使えるべきなのかという点を考慮して、リスクの切り分けや評価をしていただくよう提案したかったのです。
「ウェブサイト」と一口に言っても、その構成を理解するには幾つかの枠組みがあります。サーバに入っているファイルの機密性という尺度で個々のファイルに重みをつけてゆくこともできれば、ビジターに認知してもらいたいプッシュコンテンツの重要度という尺度でページやコンテンツに重みをつけることもできます。今回は情報セキュリティという観点から、ウェブサイトのベースになる WordPress というシステムを取り上げていますが、取り組む相手がよく分からないシステムであろうと、馴染み深い画像ファイルあろうと、結局はそのサイトにとって何が重要なのかを決めるのは運用者やクライアント自身です。セキュリティという枠組みの中でウェブサイトの安全性を評価するときにも、個々のセキュリティ対策がウェブサイトにとって本当に必要かつ重要なところを適正なレベルで防御していることが前提です。どれだけファイアーウォールのポリシーを堅くしたり、突破が難しいサニタイズ用のライブラリを導入しても、運用者がドキュメントルートに個人情報の書かれたエクセルファイルを置いていては、セキュリティポリシーの多くは無駄となります。
確かにべからず集のようなものも、気づかなかった点を指摘してくれるという意味で有用だと言えます。しかし、何を守るべきで、自分たちの運用するウェブサイトの価値はどこにあるのかを、運用している当事者が理解していなければ、セキュリティ事故というものは簡単にはなくならないと思います。細かいノウハウをかき集めることに時間を使うよりも、ウェブサイトを開発したり運用するために必要な判断や評価を行うという、情報セキュリティの技術者が代わりにやれないところから、セキュリティについて考えてみましょうという提案でもあったのです。
今回も長いエントリーになりましたが、誤った説明や、逆にリスクを高めてしまう対策とか意見を書いているかもしれません。「これはよくない」と思う箇所がもしあれば、コメントをいただければありがたいです。
