サーバーにログインして作業しようとしたら非常に反応が悪く、どうしたことかと見てみると(普段と違って、コマンドを入力してから応答があるまで数十秒も待たされるのでヒヤヒヤしましたが)、大量の apache プロセスが走っていて、ログには wp-login.php
への大量のアクセスが記録されていました。最近よく耳にする wp-login.php
へのブルートフォースアタックです。
1つの IP アドレスから5,6回のアクセスがあり、すぐに(あるいは同時に)別の IP アドレスからのアクセスがあります。結局、2時間ほどのあいだに数千回のアクセスがあり、その後沈静化しました。
特に対策らしい対策をしていなかったので、パスワードの強度のみで耐えたことになりますが、実際目の当たりにすると気持ちのいいものではありません。それにどちらかというとサーバーに対する高負荷のほうが心配です。
そこでいまさらではありますが、少し手を打つことにしました。
強いパスワードにする
今回行った対策ではありませんが、そこそこ強いパスワードにしていたので、攻撃に気づくまでの30分間ほどはこのことだけで侵入を防ぎました。
.htaccess
で制限する
攻撃に気づいてすぐ、 .htaccess
に次のように書いて wp-login.php
へのアクセスを制限しました。これは効果てきめんで、すぐにサーバーの負荷は通常の範囲まで下がりました。
<Files wp-login.php> Order deny,allow Deny from all Allow from xxx.xxx.xxx.xxx </Files>
前述のように攻撃側の IP アドレスは絞り切れないので、Deny に列挙することができません。
さて当面はこれでしのいだとしても、正当に管理画面にアクセスするのが複数の人間で、それぞれの接続元の IP アドレスが固定していなかったりすると、Allow に列挙するのもやっかいです。
WordPress のロックダウン
IP アドレスでふるい分けられないとすれば、wp-login.php
のところで何らかの制限をかける方法が考えられます。たとえば Force Email Login は、中心となる機能は「ログインをユーザー名ではなくメールアドレスで」という点なのですが、そのほかに
- ログインに失敗すると、ログイン処理に対して10秒間
wp_die()
を発火させて膨大な量のブルートフォースアタックによるサーバー負荷を軽減します。
ということも行います。
こちらの問題のサイトでは「ログインをユーザー名ではなくメールアドレスで」という機能は必要ないので(というか、ようやく「ユーザー名」が何かを理解するくらいの人たちにもう一度ログイン方法を周知徹底するのが面倒すぎるので)、その部分を削ぎ落とすことにしました。
ただ、これだと誰か(攻撃者)がログインに失敗するとその後10秒間は誰も(正当なユーザーも)ログインできません。たとえば5秒に1回、1週間にわたってログインを失敗し続けるという、もはや「ブルートフォース」とは呼べないようなぬるい攻撃(?)で、そのあいだ正当なユーザーのログインを妨害することができるという欠点があります。
やはり「IP アドレスごとに」という機能をつけざるを得ないのかもしれません。そこで、Limit Login Attempts in WordPress … のようなものにすることにしました。これだと、同一 IP アドレスから1秒間にn回のログインの失敗があったらその後m秒間、その IP アドレスからのアクセスを禁止します[1]。ユーザー名/パスワードを試みる回数は、IP アドレスをチェックしないものよりは増えますが、何も対策しないよりはずいぶん減らせるでしょう。
さて、たとえば1秒間に2か所からそれぞれ2回、3回の攻撃があった場合の対応は、この対策を行わない場合、
wp-login.php
にアクセス → データベースでユーザー名とパスワードを調べる → 失敗wp-login.php
にアクセス → データベースでユーザー名とパスワードを調べる → 失敗wp-login.php
にアクセス → データベースでユーザー名とパスワードを調べる → 失敗wp-login.php
にアクセス → データベースでユーザー名とパスワードを調べる → 失敗wp-login.php
にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
となっていたわけですが、これが
wp-login.php
にアクセス → データベースで IP アドレスごとの失敗回数を調べる → データベースでユーザー名とパスワードを調べる → 失敗wp-login.php
にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)wp-login.php
にアクセス → データベースで IP アドレスごとの失敗回数を調べる → データベースでユーザー名とパスワードを調べる → 失敗wp-login.php
にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)wp-login.php
にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)
となります。いずれにせよデータベースにはアクセスするわけで、1回の試行の負荷はどれほど違い、全体での負荷はどれほど違ってくるのでしょうか。そこまでは調べていません。
apache のモジュールで制限する
データベースへのアクセス前に制限できれば負荷を小さくできるのは明らかなので、前述の .htaccess
によるものに代わる、apache のモジュールはないか探してみました。
mod_bw は回数制限ではない、mod_dosdetector は特定ページ(wp-login.php
)のみに限定できない、mod_dosblock は IP アドレスごとではない……と、どれも一長一短のようです。ModSecurity だと要求にかなっていそうですが、これだけのためには機能が豊富すぎてルールが難しく、まだ理解できていません。
mod_evasive というのは、IP アドレスごとのアクセス回数を制限できますが特定ページの指定はできないようで、 mod_dosdetector と同じです。しばらくのあいだ、これを試してみることにしました。
管理するいくつかのサイトで、実情に合わせて上述の対策のいずれかをとることにしました。
- 実際は、1回の失敗ですぐ制限をかけるようにするため、このページのコードを少し修正しました。↑