rootパスワード不明のMySQLがブルートフォース攻撃で落ちてしまった話

SQL

あらすじ

「AWSのインスタンスが立ち上げられて時間が経ってる、Iショップ(仮称)というサービスがある。音沙汰ないんだけど、ちょっと現状調査して」

いつだったか、チームの上長に依頼されたこの案件。
ECCUBEで構築された、テスト公開段階のECサイトの調査です。

長い間ほったらかしにしていたせいか(するな)、ユーザー向けのトップ画面も、管理者用のログイン画面も、どちらもエラーを吐いていたのです。

SSH接続はできるし、Gitの変更履歴もなし。MySQLへの接続だけができなくなっている模様。
これ、のちにブルートフォース攻撃(総当たり攻撃)によってMySQLサーバーが落とされていたと発覚するんです。

今後(あってほしくないけど)似たような障害対応をすることになったときのためにも、やったことを書き残しておきます。

MySQLが起動しているか確認する

とりあえずMySQLが動いているかどうか、以下のコマンドで確認しましょう。

$ systemctl list-units --type=service | grep -i mysql
  • list-units --type=service 現在システムに読み込まれているサービスを一覧表示してくれます。
  • |(パイプ) 左側のコマンドの出力結果を、右側のコマンドへの入力として渡す記号です。
    今回の場合、サービス一覧をそのまま grep に渡しています。
  • grep -i mysql 渡されたテキストの中から mysql という文字列を含む行だけを抽出します。
    -i は大文字・小文字を区別しないオプションなので、MySQL でも mysqld でも引っかかります。

ようは、全サービスの一覧を出して、その中から mysql に関係する行だけ教えてってことですね。
当時、このコマンドを叩いた結果がこれです。

● mysqld.service   loaded failed failed   MySQL Server

failed failed…。
大事なことだから2回言ったのかな…。

素人目に見ても、MySQLサービスが起動できていないことはなんとなくわかります。

ステータス
active (running) → 正常稼働中
failed → 起動失敗・停止中
inactive (dead) → 停止中(手動停止など)

SSH接続・ソースに問題がないということは、MySQLが落ちているのが直接的な原因ではありそうです。

MySQLのログを読む

さっそくMySQLのエラーログを漁ってみましょう。
ログファイルはサーバー環境によって場所が異なりますが、たいてい /var/log/mysqld.log で開ける気がします。

このコマンドだと、直近100行を見ることが可能です。

$ sudo tail -n 100 /var/log/mysqld.log

で、期間を絞ったり、条件を付けたり、あれこれログを読み進めていったわけですが。

怪しいアクセスの痕跡

[Warning] IP address '171.8.xxx.xxx' has been resolved to the host name
         '8.171.broad.ha.dynamic.163data.com.cn',
         which resembles IPv4-address itself.
[Note] Got an error reading communication packets

[Warning] IP address '194.164.xxx.x' could not be resolved: Name or service not known
[Note] Got an error reading communication packets

出るわ出るわ、怪しい記録が~~~~。

見慣れない海外のIPアドレスから、大量の接続エラーが発生していたんです。
テスト段階のECサイトですし、普通はアクセスされる理由なんてない。

これがブルートフォース攻撃(総当たり攻撃)の痕跡です。初めて見た。

ブルートフォース攻撃とは?
パスワードを手当たり次第に試し続ける攻撃手法。
データベースの場合、3306番ポートに対して外部から何千・何万回もログインを試行されるらしい。

メモリ不足

さらにログを追うと、決定的なエラーが見つかりました。

[ERROR] InnoDB: mmap(137428992 bytes) failed; errno 12

errno 12「メモリが足りない」 を意味するエラーコードです。
なるほど、全体像がわかってきましたね。

  1. 大量の不正接続リクエストがサーバーに殺到した
  2. 接続を処理しようとするたびにメモリを消費した
  3. MySQLが必要なメモリ(約128MB)を確保できなくなった
  4. MySQLがクラッシュして起動を試みるも、また失敗……を繰り返した
  5. 最終的にダウンしたまま放置状態になった

てことは、これ以上不正にアクセスされないようにすればよいのでは?(大正解)

MySQLを再起動する

応急処置として、まずはダウンしてしまったMySQLを再起動&確認してみます。

$ sudo systemctl restart mysqld
$ sudo systemctl status mysqld

実行結果はこちら。

● mysqld.service - MySQL Server
   Active: active (running) since Tue 2025-07-22 18:35:05 JST; 15min ago

おお、普通に active (running) にはなるみたい。
ECサイトのユーザーページ、管理画面へのアクセスも復活してる。

インバウンドルールの再定義

今回はAWSを利用していたので、セキュリティグループのインバウンド設定を確認します。
調べてみると、MySQLが使う3306番ポートが「0.0.0.0/0」になってました。

そりゃ攻撃も食らうわな。
0.0.0.0/0って誰でもアクセスできちゃう状態ですもの。

MySQLは基本的に外部から直接接続する必要はなく、同じサーバー内のアプリケーションからのみアクセスできれば十分です。
チーム内で相談し、3306番ポートのインバウンドルールはオフィスのIPアドレスだけに開放、それ以外は削除というかたちに。

MySQLのrootパスワードを変更する

攻撃を受けた後は、パスワードが漏れていないとも限りません。
念のためrootパスワードを変更しておきましょう。

…と思った矢先に問題発生。

なんと自社で運用していたはずなのにrootパスワードは誰も知らないという事実。
前任は退社済。引き継いでくれ頼む。

まぁ、これ以上言及してもしゃーないので、セーフモードで起動して強制的にパスワードを変更する段取りになりました。

MySQLを停止する

さっき再起動したばかりですが、セーフモード実行のためMySQLを停止しておきましょう。

$ sudo systemctl stop mysqld
$ sudo systemctl status mysqld

実行結果がinactive (dead) になっていればOK、停止成功です。

● mysqld.service - MySQL Server
   Active: inactive (dead) since Thu 2025-07-31 18:50:38 JST; 1s ago

セーフモード用の一時設定を作成する

セーフモードというのは、パスワードなしでもrootログインできてしまう、緊急用の起動モードです。

昔は mysqld_safe というコマンドが一般的だったのですが(最初はこのコマンドを実行するつもりだった)、昨今こちらは廃止されてます。
というのも、やってることがほぼ systemd でできちゃうよね、って話で。
この復旧作業でも systemd のオーバーライド設定を使用しました。

オーバーライド用ディレクトリ:/etc/systemd/system/mysqld.service.d/

ここに一時的なオーバーライド設定を作成し、mysqld.service の動作を今だけ上書きしちゃいましょう。

上書きした設定

MySQLには --skip-grant-tables というオプションがありまして、これが権限テーブルを無視してMySQLを起動できる優れモノです。

パスワードなしでログイン可能な状態になるので、かなり危うい機能ではあるのですが…セットで --skip-networking を付ければ大丈夫。

--skip-networking
このオプションはネットワーク接続を無効にします。
--skip-grant-tables だけだと外部から誰でも接続できる危険な状態になるため、
セーフモード中は必ずセットで使用してください。

こちらを先ほどのディレクトリに設定ファイルとして作成します。

$ sudo mkdir -p /etc/systemd/system/mysqld.service.d/
$ sudo tee /etc/systemd/system/mysqld.service.d/safe-mode.conf << 'EOF'
[Service]
ExecStart=
ExecStart=/usr/sbin/mysqld --skip-grant-tables --skip-networking --user=mysql --datadir=/var/lib/mysql
EOF

tee コマンドはファイルへ書き込んだ内容をそのまま標準出力にも表示してくれるので、ちゃん書き込まれたか一発でわかるんですよね。
と言いつつ、結局 less で確認しちゃうんですが…。

セーフモードで起動する

設定ファイルを作ったところで、MySQLを再起動してみましょう。

$ sudo systemctl daemon-reload
$ sudo systemctl start mysqld
Job for mysqld.service failed because the control process exited with error code. See "systemctl status mysqld.service" and "journalctl -xe" for details.

あれ?
筆者、ここでなぜか失敗。

mysqld.service を見ろという文言から察するに、さっきの設定内容に問題があったことは明白です…。
こういう時はひとまずエラーログを遡ってみます。

するとすぐに目星がつきました。

[ERROR] unknown variable 'validate-password=OFF’

validate-password=OFF という設定項目が起動を邪魔しているっぽい。
っていうかこれどういう意味なんだろう。

validate-password=OFF とは

これ、MySQLのパスワード強度をチェックするプラグインなんですね。

  • ONのとき → 「8文字以上」「大文字・小文字・数字を含む」などのルールを強制する
  • OFFのとき → そのチェックを無効にする

つまり、その名のとおり「パスワードはバリデーションチェックしないよ!」という意味になります。

起動を邪魔していたワケ

どうやら、validate-password=OFF という書き方自体がこのバージョンのMySQL(5.7)では無効な記述形式らしいんです。

バージョン5.7では、ハイフンじゃなくてアンダースコアで書く必要があり、MySQLは設定ファイルを読み込む際にこの記述を理解できず

[ERROR] unknown variable ‘validate-password=OFF’

というエラーを出して起動を止めていました。バージョンが古すぎるからかも。

今回はこのオプションを削除し、デフォルトのバリデーションチェックに戻す方向で進めます。

とはいっても、基本的には重要な設定ファイルはいじらないほうがよいと思います。
どうしても編集の必要があるなら、対外影響を考えて作業しましょうね(私も)。

以下のコマンドで、設定ファイルから該当行を削除&保存します。

$ sudo vi /etc/my.cnf

もう一度再起動すると、無事にセーフモードでの起動ができました。
念のため、status コマンドでも確認しましょう。

$ sudo systemctl daemon-reload
$ sudo systemctl start mysqld
$ sudo systemctl status mysqld

実行結果はこちら。

● mysqld.service - MySQL Server
   Active: active (running) since Thu 2025-07-31 18:55:00 JST; 3s ago

よかった、active (running) になってますね!

rootパスワードを変更する

セーフモードでの起動ができたら、パスワードなしでも root ログインできてしまうのです。
さっそくやってみます。

$ mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.xx MySQL Community Server (GPL)
...
mysql>

ウェルカムメッセージが表示されればログイン大成功。
こんな簡単にログインできちゃうんですから、--skip-networking が無かったらと思うとゾッとします。

以下のSQLを順番に実行していきましょう。

mysql> USE mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

FLUSH PRIVILEGES は、--skip-grant-tables で無効化されていた権限テーブルを再度有効にするコマンドです。
これを実行してから ALTER USER を行わないとパスワード変更が失敗することがあります。

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '新しいパスワード';
Query OK, 0 rows affected (0.00 sec)

Query OK と表示されればパスワード変更成功です。
exit でMySQLの作業を終了します。

セーフモードから通常起動に戻す

rootパスワードを忘れないよう控えて(超大事)、セーフモードを終了しましょう。
先ほどの工程で作成した一時ファイルを削除していきます。

$ sudo rm -rf /etc/systemd/system/mysqld.service.d/
$ sudo systemctl daemon-reload // リロード
$ sudo systemctl restart mysqld // 再起動

systemctl status mysqld を叩いてみると…。

● mysqld.service - MySQL Server
   Active: active (running) since Thu 2025-07-31 19:02:00 JST; 8s ago
     Docs: man:mysqld(8)
  Process: ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid (code=exited, status=0/SUCCESS)
 Main PID: xxxxx (mysqld)

で、できてる~~~~~~!!!!!
セーフモード用のオプション(--skip-grant-tables--skip-networking)が表示されていないので、通常モードでの起動ができていると伺えます。

以上がエンジニア人生初のサイバー攻撃対処でした。
最低限のことではありますが、ブルートフォース攻撃への対策はできたのではないでしょうか。

あとDBのアカウント情報はちゃんとチーム内で共有してくれよな!(クソデカ大声)

おまけ

そういえば、上長から別の指摘もありました。

「ファビコンがECCUBEデフォルトのままだね」
「これだと使っているシステムを宣言しているようなものだから、攻撃されやすくなるよ」

攻撃者がそんなところまで見ているとは…。
簡単にできることですし、本番環境ではオリジナルのファビコンを用意するようにしましょう。

ではまた次回!

タイトルとURLをコピーしました