はじめに
PHP騒動の対応ついでにAWS Lightsailで運用しているWordPressサイトのアクセスログを調べたところ、以前にもまして、不審なアクセスが多数記録されていることに気づきました。
特に目立ったのが、WordPressのログイン機能やXML-RPCインターフェースを狙ったアクセスです。
/wp-login.php/xmlrpc.php/wp-admin/
これらはWordPressの管理・認証に関係するURLであり、攻撃者によるログイン試行や脆弱性探索の対象になりやすい部分です。
そこで今回は、AWS Lightsail上のApacheが持つIPアドレスベースのアクセス制御機能を利用して、管理・認証系URLへのアクセスを日本国内IPアドレスに限定することにしました。なんか制限やら分断やらばっかりが続いてさみしいものです。みんな仲良くしようぜー。
Cloudflareなどの外部サービスは導入せず、既存のLightsailとApacheだけで対策します。
1. 運用環境
今回の環境は次のとおりです。
| 項目 | 環境 |
|---|---|
| クラウド | AWS Lightsail |
| OS | Debian系Linux |
| Webサーバー | Apache 2.4.68 |
| CMS | WordPress(非Bitnami構成) |
| Apache設定ディレクトリ | /etc/apache2/ |
| ドキュメントルート | /var/www/html |
Lightsailにはファイアウォール機能がありますが、今回のように特定のWordPress URLだけを国別に制限するには、Apache側でアクセス制御を行う必要があります。
2. GoAccessでアクセスログを分析する
最初に、ApacheのアクセスログをGoAccessで可視化しました。
使用したログファイルは次のものです。
/var/log/apache2/access.log
GoAccessによってHTMLレポートを生成します。
sudo goaccess /var/log/apache2/access.log \
--log-format=COMBINED \
-o /tmp/access-report.html
そのうち、特に気になったのが次のアクセスです。
| 対象URL | リクエスト数 |
|---|---|
//xmlrpc.php | 758 |
//wp-login.php | 637 |
| 合計 | 1,395 |
全リクエストの約25.7%を、この2種類のURLへのアクセスが占めていました。
そこで、管理・認証系URLへのアクセスを制限することにしました。
3. Apacheのアクセス制御を利用する
Apache 2.4には、IPアドレスに基づいてアクセスを許可・拒否する仕組みがあります。
今回使用するのは次の機能です。
mod_authz_core:アクセス認可の基本機能mod_authz_host:IPアドレスによるアクセス制御<LocationMatch>:制限するURLの指定Require ip:許可するIPアドレスの指定
まず、必要なモジュールが有効か確認します。
sudo apache2ctl -M | grep -E 'authz_core|authz_host'
今回の環境では、両方のモジュールが有効になっていました。
authz_core_module (shared)
authz_host_module (shared)
Apache自体に国別判定機能があるわけではありません。
今回は、日本国内に割り当てられたIPアドレスの一覧を取得し、そのアドレス範囲からのアクセスだけを許可する方式を採用します。
4. 日本国内のIPアドレス一覧を取得する
日本国内のIPアドレス一覧は、IPdenyが公開しているデータを利用しました。
まず、保存用のディレクトリを作成します。
sudo mkdir -p /etc/apache2/geoip
続いて、IPv4とIPv6のデータを取得します。
sudo curl -fLsS \
https://www.ipdeny.com/ipblocks/data/aggregated/jp-aggregated.zone \
-o /etc/apache2/geoip/jp-v4.txt
sudo curl -fLsS \
https://www.ipdeny.com/ipv6/ipaddresses/aggregated/jp-aggregated.zone \
-o /etc/apache2/geoip/jp-v6.txt
取得結果を確認します。
wc -l /etc/apache2/geoip/jp-*.txt
今回取得できた件数は次のとおりです。
| 種類 | 件数 |
|---|---|
| IPv4 | 3,201 |
| IPv6 | 668 |
| 合計 | 3,869 |
IPアドレスはCIDR形式で記述されています。
例えば、次のような形式です。
1.0.16.0/20
1.0.64.0/18
IPv6についても同様にCIDR形式のデータを取得できます。
これらの一覧は時間とともに変化するため、継続運用では定期的な更新が必要です。
5. Apache用のIP許可リストを作成する
取得したデータをApacheの Require ip ディレクティブに変換します。
Pythonを使って設定ファイルを生成しました。
sudo python3 - <<'PY'
import ipaddress
from pathlib import Path
base = Path("/etc/apache2/geoip")
networks = []
for filename in ("jp-v4.txt", "jp-v6.txt"):
for line in (base / filename).read_text().splitlines():
line = line.strip()
if line and not line.startswith("#"):
networks.append(
ipaddress.ip_network(line, strict=False)
)
if len(networks) < 1000:
raise SystemExit("ERROR: Too few IP ranges")
output = base / "jp-allow.conf"
output.write_text(
"\n".join(f"Require ip {net}" for net in networks) + "\n"
)
print(f"Generated {len(networks)} Japanese IP ranges")
PY
生成される設定は、次のような内容です。
Require ip 1.0.16.0/20
Require ip 1.0.64.0/18
実際には、取得したIPv4とIPv6のすべてのアドレス範囲が記述されます。
6. WordPressの管理・認証系URLを制限する
次に、Apacheの設定ファイルを作成します。
対象とするのは次のURLです。
/wp-login.php/xmlrpc.php/wp-admin/以下の管理画面
ただし、/wp-admin/admin-ajax.php は例外とします。
このファイルはWordPressの公開ページでも使用される場合があるためです。
Apacheの設定ファイルは、次の場所に作成します。
/etc/apache2/conf-available/wp-japan-only.conf
設定例は次のとおりです。
<LocationMatch "^/+(?:(?:wp-login|xmlrpc)\.php|wp-admin(?:/(?!admin-ajax\.php$).*)?)/?$">
Include /etc/apache2/geoip/jp-allow.conf
</LocationMatch>
この正規表現では、通常のURLだけでなく、アクセスログで確認された //wp-login.php のような二重スラッシュも考慮しています。
また、公開ページで利用される可能性がある admin-ajax.php は制限対象から外しています。
この設定では、日本国内の許可リストに含まれないIPアドレスから対象URLへアクセスすると、Apacheが原則として 403 Forbidden を返します。
ただし、ApacheのURL正規化や他の認可設定との組み合わせによって結果が変わる可能性があるため、実際のリクエストで動作を確認する必要があります。
7. Apacheの設定を有効化する
設定ファイルを作成したら、Apacheに読み込ませます。
sudo a2enconf wp-japan-only
続いて構文をチェックします。
sudo apache2ctl configtest
今回の環境では、次の結果が表示されました。
Syntax OK
構文に問題がなければ、Apacheを再読み込みします。
sudo systemctl reload apache2
ただし、Syntax OK は構文が正しいことを示すだけで、アクセス制御が期待どおりに動作することを保証するものではありません。
管理者のIPアドレスが許可リストに含まれることを確認し、SSH接続を維持した状態で適用するのが安全です。
8. 動作確認と復旧方法
設定後は、日本国内から管理画面にアクセスできることを確認します。
さらに、海外の接続元から対象URLにアクセスし、HTTP 403が返ることを確認します。
確認したい動作は次のとおりです。
| URL | 日本国内 | 海外 |
|---|---|---|
/wp-login.php | 許可 | 拒否 |
/xmlrpc.php | 許可 | 拒否 |
/wp-admin/ | 許可 | 拒否 |
/wp-admin/admin-ajax.php | 許可 | 許可 |
| 通常の公開ページ | 許可 | 許可 |
もし正常なアクセスまで遮断されてしまった場合は、SSHから設定を無効にできます。
sudo a2disconf wp-japan-only
sudo apache2ctl configtest &&
sudo systemctl reload apache2
このように、管理画面にログインできなくてもSSHから復旧できるようにしておくことが重要です。
9. この方式の利点と注意点
今回の方法には、次の利点があります。
- Cloudflareなどの外部サービスを追加する必要がない
- Apacheの標準的なアクセス制御機能を利用できる
- サイト全体ではなく、特定URLだけを制限できる
- IPv4とIPv6の両方に対応できる
一方、注意点もあります。
日本国内のIPアドレスを利用した攻撃は防げません。また、海外滞在中にWordPressを管理する場合は、ログインできなくなる可能性があります。
IPアドレスの国別割り当ては変化するため、一覧の定期更新も必要です。更新時にはデータの取得失敗や破損に備えた検証処理を用意する必要があります。
さらに、今回の方式はWAFではありません。SQLインジェクションやクロスサイトスクリプティングなど、Webアプリケーションへの攻撃を包括的に防御するものではありません。
そのため、WordPress本体やプラグインの更新、強固なパスワード、多要素認証などと組み合わせることが望まれます。
おわりに
まあ、なんていうか今回の対応は気休めにすぎないといいますか。全く重要じゃないサイトでお試し程度にやった内容なので、本格的なサイト保護ならWAFを始めもっと多重的に色々やらないとだめですね。自動化された攻撃に手作業で対応なんて状況自体がナンセンスすぎるので、がんばれほわいとはっかー。

コメント