VPSマルウェア感染から復旧した話 - n8n/Dify再構築まで
VPSマルウェア感染から復旧した話 - n8n/Dify再構築まで
セルフホストのVPS上でDifyとn8nを運用していたところ、マルウェアに感染し、外部通信が全断する事態になった。復旧までの過程で得たノウハウをまとめる。
何が起きたか
ある日、VPSホスティング会社から「外部への不審なアクセスを検知したため、ポート制限を実施した」という通知が届いた。
調査してみると、VPSが**仮想通貨マイナー(XMRig)**に感染しており、不正採掘に利用されていた。
感染の証拠
ps aux
# 以下のようなプロセスが稼働中
# pkill -f donate-level ← XMRigの競合プロセス排除
# pkill -f curl
donate-level はXMRigに特徴的なキーワード。これが見えたら感染確定。
侵入経路
- 主因:実行していたWebアプリの脆弱性を突かれた
- 副因:rootへのSSHパスワード認証が有効だった
通知メールは届いていたが見逃していたことが最大の失敗。
感染後の状態
アウトバウンド通信:全断
インバウンド:正常(サービスは動作中に見えた)
certbot:Let's Encryptに繋げず証明書更新失敗
Docker pull:タイムアウト
SSH:ポート22のみ通過(ログインは可能)
ホスティング会社がネットワークレベルでブロックしていたため、OS側でいくら設定しても解決しない状況だった。
対応の判断
感染したサーバーは調査・修復よりOS再インストールが確実。理由は以下:
- 汚染範囲の特定が困難
- 時間をかけるほどリスクが増す
- 感染状態のWebアプリを使い続けるとデータ漏洩リスクがある
再インストール前にバックアップ
# n8nワークフローのエクスポート
docker exec [n8nコンテナ名] n8n export:workflow --all --output=/tmp/workflows.json
docker cp [n8nコンテナ名]:/tmp/workflows.json /root/workflows_backup.json
# ローカルPCにダウンロード
scp root@[VPSのIPアドレス]:/root/workflows_backup.json ~/Desktop/
注意:バイナリファイルやDockerイメージはバックアップしない。マルウェアが混入している可能性がある。JSONと設定ファイルのみが安全。
再インストール後のセキュリティ強化
SSH設定
# /etc/ssh/sshd_config
PermitRootLogin prohibit-password # 鍵認証のみ許可
PasswordAuthentication no # パスワード認証を無効化
再インストール後はホストキーが変わるため、ローカルPCで以下を実行してから接続:
ssh-keygen -R [VPSのIPアドレス]
余談:パスワード認証を無効にしてもブルートフォース攻撃自体は来続ける。ログに
Failed passwordが大量に出るが、全て弾かれているので正常。Fail2banを入れるとさらに安全。
Dify + n8n の再構築
構成方針
サブドメインではなくパスで振り分ける構成を採用。
example.com/ → Dify
example.com/n8n/ → n8n
理由:
- サブドメインはDNS管理が必要
- VPS付属ドメイン(xvps.jpなど)はサブドメイン追加不可
- パス方式ならSSL証明書も既存のものがそのまま使える
- 将来的に他のアプリも同じパターンで追加できる
docker-compose.yamlへのn8n追加
# services セクションの末尾に追加
n8n:
image: n8nio/n8n:latest
container_name: docker-n8n
restart: always
ports:
- "5678:5678"
environment:
- N8N_RUNNERS_ENABLED=true
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- N8N_PATH=/n8n/
- WEBHOOK_URL=https://example.com/n8n/
- N8N_EDITOR_BASE_URL=https://example.com/n8n/
volumes:
- n8n_data:/home/node/.n8n
# volumes セクションの末尾に追加
n8n_data:
NginxへのLocation追加
DifyのNginx設定は conf.d/default.conf.template で管理されている。
ハマりポイント:
| やってしまったこと | 問題 |
|---|---|
conf.d/n8n.conf に location{} のみ記述 |
server{} ブロックが必要なためNginx起動失敗 |
proxy_set_header Upgrade $http_upgrade; を追加 |
envsubst で $http_upgrade が空に置換されてNginx起動失敗 |
default.conf を直接編集 |
自動生成ファイルのため再起動で上書きされる |
正解:default.conf.template に以下を追加する
location /n8n/ {
proxy_pass http://n8n:5678/;
include proxy.conf;
}
proxy.conf に必要なヘッダーが含まれているので、余計な proxy_set_header は不要。
追加場所は location / の直前。
# 編集前にバックアップ
cp default.conf.template default.conf.template.bak
# 再起動
docker compose restart nginx
監視の仕組み化
通知メールを見逃さないための仕組みが重要。
不審プロセスの自動検知
# /usr/local/bin/security_check.sh
#!/bin/bash
MAILTO="通知先メールアドレス"
SUSPICIOUS=$(ps aux | grep -E "xmrig|minerd|donate-level" | grep -v grep)
if [ -n "$SUSPICIOUS" ]; then
echo "$SUSPICIOUS" | mail -s "【警告】VPSに不審なプロセスを検知" $MAILTO
fi
# crontabに登録(1時間ごと)
echo "0 * * * * /usr/local/bin/security_check.sh" | crontab -
DockerイメージのWatchtower自動更新
watchtower:
image: containrrr/watchtower
environment:
- WATCHTOWER_NOTIFICATIONS=email
- WATCHTOWER_SCHEDULE=0 0 2 * * * # 毎日深夜2時
- WATCHTOWER_CLEANUP=true
診断コマンド集
# 不審プロセス確認
ps aux | grep -E "xmrig|minerd|donate-level|kworkerds"
# アウトバウンド通信確認
curl -I https://google.com --max-time 10
# certbot接続確認
curl -v https://acme-v02.api.letsencrypt.org/directory --max-time 10
# Nginxエラーログ
docker logs [nginxコンテナ名] --tail 20
# SSH攻撃ログ
grep "Failed\|Accepted" /var/log/auth.log | tail -30
まとめ
- 脆弱性通知は即日対応。見逃し防止のため転送設定やWatchtowerを活用する
- 感染したサーバーは迷わずOS再インストール
- n8nのワークフローはエクスポートすれば復元できる
- DifyのNginx設定はtemplateを編集、
$nginx変数はenvsubstに注意 - パス振り分け構成は他アプリを追加しやすいのでおすすめ