午前2時のサーバーアラートと、世界に丸ごと公開されたphpMyAdmin
先月、午前2時にfail2banからアラートを受信しました。本番サーバーの/phpmyadminに対して、毎分400近くのリクエストが来ていたのです。ボットスキャナーによるブルートフォース攻撃でした。同僚が3ヶ月前にとりあえずインストールしたまま放置されていたものです。
さらに恐ろしかったのは、phpMyAdminがポート80で動いており、追加の認証なし、IP制限なし、rootユーザー名はデフォルトのままという状態でした。iptablesで急いでブロックしながら、セットアップした担当者に確認すると“社内でしか使わないと思っていた。”という返答でした。
以前、午前3時にデータベース破損の障害に遭遇してバックアップから復元したことがあり、それ以来毎日バックアップを確認しています。しかし、攻撃者が発覚前にすべてのデータをダンプしてしまっていたら、バックアップでは解決できません。
分析:phpMyAdminはどこが危険なのか?
phpMyAdminはWebからMySQLを管理できる便利なツールですが、だからこそボットスキャナーの最大のターゲットにもなります。よくある脆弱なポイントをいくつか挙げます:
- デフォルトURLが推測しやすい:
/phpmyadmin、/pma、/mysql— ボットが24時間365日スキャンし続けています。 - 追加の認証レイヤーがない:MySQLのユーザー名とパスワードだけに依存しているため、パスワードが弱ければデータベース全体が漏洩します。
- すべてのIPに公開:社内のみで使用するなら、phpMyAdminをインターネットに公開する理由はまったくありません。
- 古いバージョンにはCVEが存在:phpMyAdminは深刻なRCE脆弱性の歴史があります。古いバージョンを外部に公開することは高リスクです。
phpMyAdminのセキュリティ対策方法
方法1:デフォルトURLの変更(最低限の対策、不十分)
パスを/phpmyadminから推測しにくいものに変更します。Nginxの場合はlocationブロックを変えるだけです。ボットスキャナーからのノイズを減らせますが、標的型攻撃は防げません。フィンガープリンティングで発見される可能性があります。
方法2:iptables/ufwによるIPアクセス制限
特定の固定IP(オフィス、VPN)からのみ管理する場合は、それ以外のIPをすべてブロックします:
# オフィスとVPNのIPを許可
sudo ufw allow from 203.0.113.10 to any port 443 proto tcp comment "pma office"
sudo ufw allow from 10.0.0.0/8 to any port 443 proto tcp comment "pma vpn"
sudo ufw deny 443
効果的ですが、動的IPやリモートワーク時には不便です。ホワイトリストを手動で更新する必要があります。
方法3:HTTP Basic Auth(より効果的、組み合わせに最適)
phpMyAdminのログインフォームに到達する前に、認証レイヤーを追加します:
# パスワードファイルの作成
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd admin_pma
# プロンプトが表示されたら強力なパスワードを入力
このレイヤーにより、ボットはphpMyAdminのログインフォームを見る前にHTTP Basic Authを突破しなければなりません。URLを変えるだけよりもはるかに効果的です。
最善策:Nginxリバースプロキシ + IPホワイトリスト + Basic Auth
3つすべてを組み合わせます:phpMyAdminをローカルで実行(直接公開しない)、NginxをIPホワイトリストとBasic Auth付きのリバースプロキシとして機能させます。このセットアップを本番環境で1年以上使っていますが、問題は一度も発生していません。
ステップ1:phpMyAdminのインストール
sudo apt update
sudo apt install phpmyadmin php-mbstring php-zip php-gd php-json php-curl
# Webサーバーを聞かれたら:Nginx + PHP-FPMを使う場合は"none"を選択
# Configure database for phpmyadmin: Yes
# phpMyAdminの内部データベース作成のためMySQLルートパスワードを入力
ステップ2:phpMyAdminの設定 — rootログインを無効化
/etc/phpmyadmin/config.inc.phpを編集して、rootログインを無効にしローカル接続に限定します:
<?php
// TCPではなくローカルソケット経由で接続
$cfg['Servers'][$i]['host'] = '127.0.0.1';
// rootログインを無効化 — 専用ユーザーの使用を強制
$cfg['Servers'][$i]['AllowRoot'] = false;
// 不要な場合は大容量ファイルのインポート機能を無効化
$cfg['UploadDir'] = '';
$cfg['SaveDir'] = '';
ステップ3:phpMyAdmin専用のMySQLユーザーを作成
phpMyAdminへのログインにrootは絶対に使用しないでください。権限を制限したユーザーを作成します:
-- ローカル接続のみ許可するユーザーを作成
CREATE USER 'pma_admin'@'127.0.0.1' IDENTIFIED BY 'StrongPass_2024!';
-- 必要最小限の権限を付与 — SUPERなし、GRANT OPTIONなし
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
ON *.* TO 'pma_admin'@'127.0.0.1';
-- または特定のデータベースのみに制限
GRANT ALL PRIVILEGES ON myapp_db.* TO 'pma_admin'@'127.0.0.1';
FLUSH PRIVILEGES;
ステップ4:Nginxリバースプロキシの設定
Nginx設定ファイルを作成します。URLは誰にも推測されないものに変更しています:
server {
listen 443 ssl;
server_name db.example.com;
ssl_certificate /etc/letsencrypt/live/db.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/db.example.com/privkey.pem;
# IP制限 — オフィスとVPNのみ
allow 203.0.113.10;
allow 10.0.0.0/8;
deny all;
# デフォルトURLをブロック — 404を返す
location ~ ^/(phpmyadmin|pma|mysql|dbadmin) {
return 404;
}
location /dbmanager_x7k2 {
# 第2認証レイヤー:HTTP Basic Auth
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
alias /usr/share/phpmyadmin;
index index.php;
try_files $uri $uri/ =404;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
}
location ~* \.(js|css|png|jpg|ico)$ {
expires max;
log_not_found off;
}
}
}
# 構文テストとリロード
sudo nginx -t
sudo systemctl reload nginx
ステップ5:レートリミットとfail2banの設定
# nginx.confのhttpブロック内:
limit_req_zone $binary_remote_addr zone=pma:10m rate=5r/m;
# phpMyAdminのlocationブロック内:
limit_req zone=pma burst=10 nodelay;
# /etc/fail2ban/filter.d/phpmyadmin-auth.conf
[Definition]
failregex = ^<HOST> .* "POST .*(phpmyadmin|dbmanager).* HTTP.*" (200|401)
ignoreregex =
# /etc/fail2ban/jail.local
[phpmyadmin-auth]
enabled = true
port = http,https
filter = phpmyadmin-auth
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
sudo systemctl restart fail2ban
sudo fail2ban-client status phpmyadmin-auth
全体の確認
セットアップ後、すべてが正しく動作しているか簡単に確認します:
# デフォルトURLは404を返す必要がある
curl -k -o /dev/null -w "%{http_code}" https://db.example.com/phpmyadmin
# Expected: 404
# Basic Auth認証情報なしでは401を返す必要がある
curl -k -o /dev/null -w "%{http_code}" https://db.example.com/dbmanager_x7k2/
# Expected: 401
# ホワイトリスト外のIPからアクセスしてみる(モバイルデータまたは別のVPSを使用)
# Expected: 403 Forbidden
# rootがログインできないことを確認
mysql -u root -p -e "SHOW GRANTS FOR 'pma_admin'@'127.0.0.1';"
最後にいくつかの注意点
- 常にHTTPSを使用:HTTP経由のBasic Auth認証情報はプレーンテキストで送信されるため意味がありません。Let’s Encryptは無料なので使わない理由はありません。
- phpMyAdminを定期的にアップデート:毎月GitHubリリースを確認し、6ヶ月以上古いバージョンを動かし続けないでください。
- 使用しないときは無効化:たまにしかphpMyAdminが必要ない場合は、使用間隔の間Nginxのlocationブロックをコメントアウトするのがもっとも安全です。
- ログを十分な期間保持:Nginxのアクセスログはインシデント発生時の分析のために、最低30日間保存することをお勧めします。
このセットアップは手順が多く聞こえますが、実際には約30分で完了します。午前2時にブルートフォース攻撃の対応に追われたあの夜を経験してからは、その30分が深夜のデータベース復元作業に費やす数時間よりもはるかに価値があると実感しています。さらに、顧客のデータが漏洩する危険があった理由を説明しなければならない状況も考えると、なおさらです。

