WordPress のページキャッシュプラグイン YASAKANI Cache に HTTP header キャッシュ機能と簡易的なセキュリティ機能を実装しました
特徴としては、サイトアクセスのレスポンスにあまり影響せずに高速で動作するように実装しています。
簡易的なものなので WAF(Web Application Firewall)や .htacess を使ってセキュリティを強化している人には不要かもしれませんが、追加した機能は WAFや .htaccess で行っているサーバーレベルのセキュリティーではなく(.htaccess を変更しない)、WordPress レベルで対策する補助的なものです
簡単に使えますので、セキュリティ対策をちょっと強化したいとお思いなら試してみて下さい
YASAKANI キャッシュの全体機能に関しては下記ページで紹介していますので、合わせて参照していただければと思います
WordPress Plugin : YASAKANI Cache
HTTP header キャッシュ機能
この前、WP Super Cache プラグインの更新情報が来ていたので、何がかわったのかちょっとソースを眺めていたら、HTTP header のキャッシュ機能が追加されていました
これ、前から少し気になっていたんですよね (^^)
ページキャッシュって通常コンテンツをファイルに保存して、それをキャッシュデータとして出力することで高速な表示を行っていますが、HTTP header 部のデータはどうなっているんだろうって
以前、いくつかのページキャッシュプラグインを調べた時もこのあたりまで処理しているのを見つけられなかったので目をつむっていたのですが、今回 WP Super Cache でサポートしてきたのでまるっと参考にして実装しました
apache_response_headers とか headers_list 関数で header に設定されている要素を取得できるようですが、何故か私の利用しているエックスサーバーで動作確認してみると apache_response_headers では、[“Content-Typ”]=>”text/html; charset=UTF-8″ のような感じにキーの最後の1文字(本来は Content-Type のはず)が欠落します。仕方ないので apache_response_headers は使わずに headers_list で処理するようにしました
headers_list 関数が使えない環境では header 部のキャッシュは行いません
例えば、セキュリティのクリックジャッキング対策で headerへ X-Frame-Options 等を出力するよう設定してた場合に、前バージョンではキャッシュデータ出力時には X-Frame-Options は欠落していました。今バージョンではページデータをキャッシュデータとして保存する時に headers データも併せてキャッシュするので、キャッシュデータによる応答時でも X-Frame-Options がセットされるはずですので、header 出力を確認してみて下さい
ちなみに、この機能は、WP Super Cache では オプション設定により有効化する必要がありますが、YASAKANI Cache では 常時有効にしていますので、何もする必要はありません
不正アクセスとセキュリティのポイント
キャッシュプラグインを作っていると、サイトにはびっくりするぐらいボットアクセスがあったり、サイトを攻撃してくる怪しいアクセスがあるのがわかります
どんな不正アクセスがあるのか知っておくことも大切なので、いろいろググってみました
不正アクセスってどんな攻撃があるの?
以下の記事が大変参考になります
いろんな攻撃がありすぎて、全部は理解できていません (^_^;)
WordPress セキュリティ
そこで、WordPress で行うべきセキュリティ対策は何か? と調べると下記記事を見つけました
ちょっと広範囲に渡っていますので、ここでは ドキュメントルート下の WordPress サイトに対して注目していきます
基本は、ファイルパーミッションと .htaccess による制限ですね
ファイルパーミッション
WordPress の便利な機能のいくつかは、ウェブサーバーがファイルに書き込みできることに基づいています。しかし、アプリケーションがファイルに書き込み権限を持つことは危険です。公開環境では特に危険です。
セキュリティの観点からベストなのは、ファイルパーミッション(書き込み権限)を可能な限り制限して、書き込み権限が必要な時のみ制限を緩くする、あるいは画像アップロード等の目的のために制限の緩い特別のフォルダを作成することです。
考えられるパーミッションスキーマの1つを示します。
すべてのファイルの所有者はあなたのユーザーアカウントで、あなたのみ書き込み可能。WordPress が書き込み権限を必要とするファイルは、ウェブサーバーで使用するユーザーアカウントのグループ所有。
/
- ルート WordPress ディレクトリ。すべてのファイルを自分のユーザーアカウントのみから書き込み可能にするべきです。例外は
.htaccess
: で、自動的にリライトルールを生成させたいときはこれを WordPress によって書き込み可能にします。/wp-admin/
- WordPress 管理領域: すべてのファイルを自分のユーザーアカウントのみから書き込み可能にする。
/wp-includes/
- WordPress アプリケーションロジックの大部分。すべてのファイルを自分のユーザーアカウントのみから書き込み可能にする。
/wp-images/
- WordPress が使用する画像ファイル。すべてのファイルを自分のユーザーアカウントと Web サーバーのプロセスのみから書き込み可能にする。
/wp-content/
内には以下が含まれる。
/wp-content/
- ユーザー提供の様々なコンテンツ。開発者/en により、すべて (所有者、グループ、全体) 書き込み可能に意図されている。
/wp-content/themes/
- テーマファイル。ビルトインテーマエディタを使用する場合は、Web サーバーのプロセスに書き込み権限を与える。ビルトインテーマエディタを使用しない場合は、自分のユーザーアカウントのみから書き込み可能にする。
/wp-content/plugins/
- プラグインファイル。すべてのファイルを自分のユーザーアカウントのみから書き込み可能にする。
/wp-content/
の他のディレクトリは、プラグイン/テーマの要求次第で異なり、パーミッションは様々である。
パーミッションと .htaccess で適切なアクセス制限をおこなえばかなり強固になります
.htaccess では、様々な操作を行えるのですが、ここでは主にセキュリティに関するディレクトリとファイルへのアクセス制限についてみていきたいと思います
基本的な書き方は、正規表現を使ってディレクトリやファイルへのリクエストに対して、許可(allow)と拒否(deny)の条件を記述します
例えば、下記のような記述があれば wp-config.php や .htaccess 等の重要なファイルへのアクセスへの拒否や、wordpress の主な include ディレクトリ下へのアクセスを拒否できます
<FilesMatch "^(wp-config.php|wp-mail.php|install.php|.htaccess|.htpasswd)"> order allow,deny deny from all </FilesMatch> # Block the include-only files. <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^wp-admin/includes/ - [F,L] RewriteRule !^wp-includes/ - [S=3] RewriteRule ^wp-includes/[^/]+.php$ - [F,L] RewriteRule ^wp-includes/js/tinymce/langs/.+.php - [F,L] RewriteRule ^wp-includes/theme-compat/ - [F,L] </IfModule> # BEGIN WordPress
ぱっと見よくわからないかもしれませんが、おおむね正規表現と定数等による簡単な条件が設定されているだけなので、以下の表を参考にしていただければある程度想像できるのではないかと思います
正規表現で使用する主なメタ文字
^ | 検索対象の文字列の始まり |
$ | 検索対象の文字列の終わり |
| | OR条件 [a|b] – a または b |
[a-z] | 文字の範囲指定 この例では a から z まで |
. | 任意の1文字にマッチ |
+ | 前の文字の1回以上の繰り返し |
* | 前の文字の0回以上の繰り返し |
? | 前の文字の0回または1回のマッチ |
条件判定等のルールを定義している主な定数と式
NC | No-case 大文字小文字を無視する |
L | Last rule 条件にマッチしたらそこで終了(以降のルールは無視) |
R R=301 |
Redirect 指定URLへのリダイレクト(リダイレクトコードの指定も可能) |
F | Forbidden URLページはアクセス禁止( 403 送信) |
G | Gone URLページは削除された (410 送信) |
S=x | Skip マッチしたら以降の x個のルールをスキップする |
P | Proxy プロキシを介してリクエストを処理(プロキシにて処理するので以降のルールは無視) |
T=mime-type | MIME type マッチしたらMIME-typeを付加 |
H=handler | Set handler 実行するアプリケーションハンドラを指定 |
! | 否定 |
<, <= | 左辺と右辺の文字列比較 |
>, >= | 左辺と右辺の文字列比較 |
= | 左辺と右辺の文字列比較 |
-d | 指定ディレクトリの存在で true |
-f | 指定ファイルの存在で true |
-s | 指定ファイルが存在して空でなければ true |
-F | アクセス可能なファイルパスならば true |
-U | アクセス可能なURLならば true |
-x | 実行権限があれば true |
ググれば .htaccess の設定例がたくさん見つかると思いますので、各自調べてみて下さい。
WordPress セキュリティプラグインだけでは防げない PHPスクリプトへの直接アクセス
WordPress の .htaccess には下記のような記述があります。
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress
これはリクエストしたディレクトリやファイルがなければすべてドキュメントルートにある WordPress の index.php ファイルへリクエストを渡すという意味です。
すなわち、通常の WordPress サイトへのアクセスは 最初に index.pnp が読み込まれた後にリクエスト内容に応じて必要な各PHPスクリプトが順に読み込まれて出力データを生成して送信されるのが基本的な流れとなります。ところが、プラグイン等のPHPファイルなどを直接呼び出されてしまえば、WordPressを起動させずにスキップ(セキュリティ等のプラグインも読み込まれない)され、そのPHPに脆弱性があれば不正アクセスによりコードを仕込まれたりサイトを改変されたりする可能性があるということです
従って、Webサーバーレベルの攻撃や、未知の PHP へのアクセスによる攻撃(プラグイン等の 脆弱性がある PHP ファイルへの直接アクセス)は、基本的には WAF や .htaccess 等で防御する必要があります
Web Application Firewall 読本 あたりの資料を参考にしてみて下さい
公式サイトで公開されているプラグインに脆弱性があることがあることが公表されたら、おそらくあなたのサイトにもその脆弱性に対する攻撃が行われる可能性が高いです
どんなに強固なセキュリティ対策をしていても、脆弱性のあるプラグインやテーマをインストールしていた場合(無効化されている場合を含む)は防ぐことが難しくなるので wp-content/plugins や wp-content/themes ディレクトリなんかを保護することも重要です
本来 plugin や theme のPHPファイル等は直接呼び出して実行できないよう各PHPファイルの先頭に対策コードを入れるべきなのですが、実際は対策していないプラグインやテーマも見受けられます
私も以前はPHPファイルの先頭にある下記のようなコードって何んだろうって思ってました
defined( 'ABSPATH' ) || exit;
この1行が不正アクセス対策に有効で、通常の手順に従って呼び出されずに直接呼び出された場合には終了するようになってます
ただ、これらの対策コードがない場合もありますので、 .htaccess を用いて plugin や theme ディレクトリの PHP への直接アクセスを禁止させるよう対策するのが望ましいです
ただ、このように多重に対策しても、そもそも WordPress の ディレクトリやPHPファイルはたくさんあるので、何らかの方法で php ファイルを仕込まれて不正アクセスされたら防ぐのはとても難しいことがわかります
他の方法としては、PHPには auto_prepend_file という機能で全ての PHP スクリプトの実行前に実行させるPHPを登録することが出来ますので、これを使えば未知の PHP へのアクセスによる攻撃への対策を行うことも可能です
YASAKANI cache のセキュリティ機能
おおよその不正アクセスの内容や対処方法が少しわかったところで、今回行っている不正アクセス対策について紹介していきます
私の利用しているエックスサーバーでは、国外からの不正アクセス対策機能があるので、エックスサーバーのアクセス制限機能を補完するような軽量で効果的な対策機能を実装しました
追加したのは、下記2つの不正アクセスの防御機能です
- 不正アクセス [ヌルバイト/ディレクトリトラサーバル/コマンドインジェクション等 ]
- ブルートフォース
エックスサーバーでは、以下のアクセス制限や .htaccess を使ってアクセス制限を利用できるようになっています
- /wp-admin, /wp-login.php への国外IPアドレスからのダッシュボード アクセス制限
- /xmlrpc.php への国外IPアドレスからのXML-RPC API アクセス制限
- /wp-json への国外IPアドレスからのREST API アクセス制限
- ログイン試行回数制限(ブルートフォースアタック)
- 大量コメント・トラックバック制限
- 国外IPアドレスからのコメント・トラックバック制限
※国情報を使って不正なアクセスをブロックしたい場合は、tokkonopapa さんの作成した IP Geo Block というプラグインの評判が良いようです
ブルートフォース対策に関しては、対策するプラグインは沢山ありますので既に導入されている方も多いと思いますが、キャッシュプラグインの機能の一つとして実装しているおかげで、ブルートフォース攻撃によるサイトレスポンスへの影響が最小限になるように実装できました。まだ使っていないという方がいましたら試してみて下さい
セキュリティ機能を使うには、設定画面のオプションユーティリティに3つの機能のチェックボックスがありますので、チェックして有効化してください
実際にどのような不正アクセス対策を行っているかをもう少し詳しく紹介します
アクセスログを見ると主な不正アクセスはリクエスト(REQUEST_URI)に不正なコードを挿入するか、脆弱性があると知られているプラグインの php ファイルへ直接アクセスしてくるケースが多いです
そこで、REQUEST_URI データや _POST データに不正アクセスと疑われれるようなコードがないかをチェックしています
不正アクセス攻撃の対策
クロスサイトスクリプティング
REQUEST_URI から スペース, r,n,t は取り除いています
また、REQUEST_URI データに何らかのタグ <> が含まれている場合は不正アクセスと見なします
スーパーグローバル汚染
_GET, _POST, _COOKIE にスーパーグローバル変数が含まれているものを不正アクセスと見なします
ヌルバイト攻撃
REQUEST_URI データや _POST データに NULL (ゼロ)が含まれているものを不正アクセスと見なします
ディレクトリトラサーバル
REQUEST_URI データや _POST データに ../ 相対アドレスが含まれているものを不正アクセスと見なします
コマンドインジェクション
REQUEST_URI データや _POST データにシェル操作等が可能なPHPのコードが含まれていると疑われる場合に不正アクセスと見なします
重要なファイルの保護
保護対象ファイルやそのバックアップ(wp-config. .htaccess, .htpasswd, /passwd )へのアクセスが疑われる場合も不正アクセスと見なします
このプラグインでは、疑わしくは罰せずではなく、疑わしければそのIPからの以降のアクセスを日付変わりまで自動的にブロックします
ただし、コメントや bbPress フォーラムの投稿(_POSTデータ)に対しては、単純に不正な文字列が含まれているかどうかだけでは判断できないので、WordPress 標準機能や Akismet によるスパム対策を活用することを想定して対象から除外しています
判定コードとしては以下のような簡単なものですが、これだけでも結構効果的です (^^)
$block = false; if($step == 1){ //REQUEST_URI, Super Global //XSS 何らかのタグが request_url に含まれている if ( preg_match( '/<.+>/s', $checkdata) || preg_match( '/%3C.+%3E/is', $checkdata) ) { $block = true; } //スーパーグローバル変数汚染 if(!$block){ foreach(array($_GET , $_POST , $_COOKIE) as $arr) { if (!empty($arr)){ foreach (array('GLOBALS','_SERVER','_GET','_POST','_FILES','_COOKIE','_REQUEST','_SESSION','_ENV') as $sg) { if (!empty($arr[$sg])){ $block = true; break; } } } } } } if(!$block){ //NULL(� x00 %00) バイト攻撃, ディレクトリトラサーバル (../), 重要な保護ファイル foreach (array('%00','�','x00','../','..%2F','wp-config.','.htaccess','.htpasswd','/passwd') as $key) { if ( strpos($checkdata, $key ) !== false){ $block = true; break; } } } if(!$block){ //PHP シェルインジェクション攻撃 (eval preg_replace usort call_user_func callback system include require exec passthru popen proc_open shell_exec, よく使われるコード変換と `バッククオート) if ( preg_match( '/(eval|preg_replace|usort|call_user_func|system|exec|passthru|popen|proc_open|pcntl_exec|pcntl_fork|shell_exec|ini_set|base64_decode|uudecode|str_rot13)s?(/i', $checkdata, $matches ) ) { $block = true; } else if ( preg_match( '/`.+`/', $checkdata, $matches ) ) { $block = true; } }
404 エラーとなったPHPスクリプト
これは、いままでのチェックとは少し趣が異なります
ファイルが見つかりません(404)のエラーが発生した後の次への対応です
REQUEST_URI データ内に .php が含まれている場合に不正アクセスと見なし、そのIPからの以降のアクセスをブロックします
運悪く脆弱性のあるプラグイン等をインストールしていて、アクセスしてきた php ファイルが存在した場合には、コードを仕込まれたりサイトを改変されたかもしれませんが、幸い404 になったということは対象ファイルがなくて攻撃が未遂に終わったということです。
従って、同一のIPからまたアクセスがくる可能性が高く(犯人はよく現場に戻ってくることが多いですが不正なボットもまた必ず来ます)、そうならないようにそのIPからの以降のアクセスをブロックします
また、REQUEST_URI データ内に .php が含まれていない場合でも短時間に繰り返し 404 となるリクエストを行ってくることは異常(悪意ある不正の気配がします)ですので、同一IPから1分以内の 404 アクセスが5回あった場合は不正アクセスと見なしています
※これをしたら iPhone からの apple-touch-icon.png を探してくる 404 エラーが引っかかって自動ブロックしてしまいました。なんて行儀の悪いリクエストをしてくるんだと思いましたが、不正アクセスにならないよう除外するようにしました
ゼロデイ攻撃対策
エキスパートモードならば、ホワイトリストに登録されている以外の PHP へのダイレクトアクセスを不正アクセス(ゼロディ攻撃)と見なしてブロックします
ブロック解除
これらの不正アクセスの検出条件にマッチしたIPからのアクセスは以降の問題のないアクセスも含め自動的にブロックされます
ブロックされる期間は、日付変わりまでで翌日にはブロックを解除しますので、常時ブロックしたい場合は、管理画面で対象となるIPをブラックリストへ登録してください
また、その日にログイン履歴のあるIPからのアクセスに対しては、基本的に自動ブロック対象から除外しているので通常は管理者等の登録ユーザーはブロックされないようにしているのですが、しばらくログインしていない登録ユーザーが不正なアクセスと判断されるリクエストを送った場合は、そのIPがブロックされサイトへアクセスできなくなります
登録ユーザがサイトへアクセスできなくなった場合は以下の方法でブロック解除できます
サイトのログインページ(wp-login.php)へアクセスしてログインが成功すれば解除します
ブルートフォース攻撃対策
このプラグインでは、5回ログインに失敗すると、そのIPからのアクセスは自動的に30分程度ブロックします
※ログインへの制限だけでなくサイトへのアクセスもブロックされます
※ログモードの統計情報が有効になっている場合のみ機能します
ブロック解除
ログインに失敗して、管理者なのに自分のIPがブロックされてしまった場合は以下の方法でブロック解除できます
急いでいる場合
- 異なるIPからログインして、YASAKANI Cache 設定画面から自動ブロックしているIPアドレスリストをクリアする
- サイトのログインページ(wp-login.php)へアクセスして、パスワードをお忘れですか?のリンクをクリックして通常のパスワード再発行処理を行えばブロックが解除されます
急いでない場合
30分程度経過すれば自動的にブロックが解除されます(wp-cron を使って解除処理を行っているのでおおよその時間です)ので、正しいログイン名、パスワードを入力してログインしてください
最新の不正アクセスログ
不正なアクセスのみのログや特定のIPのみのアクセスログをログフィルター機能で絞り込んで表示することが出来ます
データベースには、1日単位に7日分のログが保存されています。最大7日以内のログなら、日時を指定して参照することが可能です
ログフィルター機能
- 今日/昨日&時間指定 : 調べたい時間帯のログを参照
- タイプ指定 : ログインや不正アクセス等リクエストの種別を絞り込んで参照
- IP指定 : 特定のIPのアクセスを絞り込んで参照
- ページング : 指定条件のログを100件ごとにページ分けして参照
※ログデータを含むデータベースファイルは、直接アクセス出来ないように Apache サーバー用に .htaccess ファイルで制限していますが、念のためログデータのログイン処理のログイン名やメールアドレスは暗号化しています、またパスワードはログインに失敗したもののみログに残し、成功したパスワードはログに残さないようにしています
以上
YASAKANI キャッシュ の header キャッシュ機能とセキュリティ機能を紹介しました
元々は不正アクセスしてくるボットによるサーバーリソースの使用を減らしたいという発想で実装したセキュリティ対策なので、十分なセキュリティ対策でないかもしれませんが、少しでもサイトへの余計な負荷を取り除くことが出来るのではと思っています (^^)