去年の3月、夜中の2時に電話が来た——productionサーバーがCPU 100%で動いていて、外部へのトラフィックが発生しているのに誰も原因がわからない状態だった。プレイブックも、チェックリストもなく、現場は大混乱。結果として、適切な手順があれば90分で終わるはずのインシデントに、6時間もかかってしまった。
10台以上のproductionサーバーをauditしてきた経験から言うと、ほとんどが同じ問題を抱えている——技術的な問題ではなく、手順の問題だ。ツールも知識も十分にあるのに、いざ本当のインシデントが起きたとき、何を先にやるべきか、何を後回しにすべきか誰もわからない。これは私が実際に使っている手順であり、すぐに使えるコマンドも合わせて紹介する。
Incident Responseとは何か——なぜ専用の手順が必要なのか?
率直に言うと、Incident Response(IR)とは一つのシンプルな問いへの「順序立てた答え」にすぎない——ハックされたとき、最初に何をすべきか? Detect、contain、investigate、recover。シンプルに聞こえるが、手順がなければ本当に混乱する。
直感で対応するのではなく、固定された手順が必要な理由:
- プレッシャー下では重要なステップを見落としがち——例えば、マルウェアを削除する前にログをバックアップするなど
- チェックリストがないと、チームの作業が重複したり、完全に見落としたりする
- ドキュメント化しないと→インシデント後に何が起きたか正確に把握できず、改善もできない
最もよく使われているIRモデルはSANSのPICERLだ:Preparation → Identification → Containment → Eradication → Recovery → Lessons Learned。この順番に沿って実践的な内容を解説していく。
ステップ1:インシデントの検知と確認
何かをする前に、これが本当にインシデントなのか——それとも通常のトラフィックスパイクによる誤検知なのかを確認する必要がある。
異常なプロセスを確認する
# 実行中のプロセスをCPU順で確認する
ps aux --sort=-%cpu | head -20
# 隠しプロセスを確認する(PIDが連続していない = 不審なサイン)
ls /proc | grep -E '^[0-9]+$' | sort -n > /tmp/proc_pids.txt
ps aux | awk '{print $2}' | sort -n > /tmp/ps_pids.txt
diff /tmp/proc_pids.txt /tmp/ps_pids.txt
# プロセスツリーを確認し、不審なシェルの生成を検知する
pstree -a -p
ネットワーク接続を確認する
# アクティブな接続を確認する
ss -tulnp
netstat -antp | grep ESTABLISHED
# プロセスごとの外部トラフィックを確認する
lsof -i -n -P | grep -v LISTEN
ログイン中のユーザーを確認する
who
w
last -n 20 # 直近20回のログイン履歴
lastb -n 20 # 直近20回のログイン失敗履歴
不審なプロセスがCPU・帯域幅を消費している、出所不明の海外IPへの接続がある、あるいは見覚えのないユーザーが深夜3時にオンラインになっている——これは本物のインシデントだ。すぐに次のステップへ進むこと。
ステップ2:システムを即座に封じ込める
このステップが最も軽視されがちだ——そして多くの場合、最もコストの高いミスになる。多くのチームが、攻撃者がまだ接続してデータを窃取し続けている間に、いきなり調査に飛び込んでしまう。絶対的な原則:封じ込めが先、調査は後。
# 全トラフィックをブロックし、自分のIPからの接続のみ許可する
YOUR_IP="1.2.3.4" # 自分の実際のIPアドレスに変更すること
iptables -F && iptables -X
# 自分のIPを許可する
iptables -A INPUT -s $YOUR_IP -j ACCEPT
iptables -A OUTPUT -d $YOUR_IP -j ACCEPT
# ループバックを許可する
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# それ以外をすべてブロックする
iptables -A INPUT -j DROP
iptables -A OUTPUT -j DROP
iptables -A FORWARD -j DROP
なぜすぐにサーバーをシャットダウンしないのか? RAMには多くの貴重なデータが保持されている:実行中のプロセス、暗号化キー、ネットワーク状態——シャットダウンするとすべてが失われる。ネットワークを封じ込めることで証拠を保全しつつ、攻撃者のアクセスを遮断できる。
シャットダウンが必要な場合——例えばランサムウェアがファイルを暗号化している最中など——shutdownコマンドを実行するのではなく、電源を強制的に切ること。理由:攻撃者はグレースフルシャットダウン時に実行されるクリーンアップスクリプトを仕込んでいることが多いためだ。
ステップ3:証拠収集と調査
ネットワークを封じ込めたら、優先度順に証拠を収集する:揮発性データ(RAM、ネットワーク状態)を先に、不揮発性データ(ディスク、ログ)を後に。順序を逆にしないこと。
システム情報を収集する
# タイムスタンプ付きのevidenceディレクトリを作成する
EVIDENCE_DIR="/tmp/ir-$(date +%Y%m%d-%H%M%S)"
mkdir -p $EVIDENCE_DIR
date > $EVIDENCE_DIR/timestamp.txt
uname -a > $EVIDENCE_DIR/sysinfo.txt
uptime >> $EVIDENCE_DIR/sysinfo.txt
ps auxf > $EVIDENCE_DIR/processes.txt
ss -tulnp > $EVIDENCE_DIR/network.txt
netstat -rn > $EVIDENCE_DIR/routes.txt
lsof -n > $EVIDENCE_DIR/open_files.txt
最近変更されたファイルを探す
# 過去24時間に変更されたファイルを確認する(/proc, /sys, /devは除外)
find / -not \( -path /proc -prune \) \
-not \( -path /sys -prune \) \
-not \( -path /dev -prune \) \
-mtime -1 -type f 2>/dev/null > $EVIDENCE_DIR/recent_files.txt
# 不審なSUID/SGIDファイルを確認する
find / -perm /6000 -type f 2>/dev/null > $EVIDENCE_DIR/suid_files.txt
侵入の痕跡を探してログを分析する
# SSHブルートフォースと不審なログイン成功を確認する
grep "Failed password" /var/log/auth.log \
| awk '{print $11}' | sort | uniq -c | sort -rn | head -20
grep "Accepted password\|Accepted publickey" /var/log/auth.log | tail -50
# 新たに追加されたCrontab/atジョブを確認する
grep -i "cron\|atd" /var/log/syslog | tail -50
永続化メカニズムを確認する
# 全ユーザーのCrontabを確認する
for user in $(cut -d: -f1 /etc/passwd); do
echo "=== $user ==="
crontab -u $user -l 2>/dev/null
done
# 実行中の不審なSystemdサービスを確認する
systemctl list-units --type=service --state=running
# SSH authorized_keys — 不審なキーが追加されていないか確認する
find /home /root -name "authorized_keys" \
-exec echo "=== {} ===" \; -exec cat {} \; 2>/dev/null
# 新たに作成されたユーザーを確認する(システムアカウントを除く)
grep -v "nologin\|false" /etc/passwd
ステップ4:脅威を排除する
問題を正確に把握してから削除作業を始めること。感覚で削除しない——後で実際に使えるインシデントレポートを作るために、各操作のタイムスタンプが必要だ。
# 不正ユーザーを削除する
userdel -r suspicious_username
# authorized_keysから不審なSSHキーを削除する
# ファイルを開き、見覚えのないキーの行を見つけて削除する
nano /root/.ssh/authorized_keys
# PIDを指定してマルウェアプロセスをKillする
kill -9 <PID>
# 侵害されたユーザーのCrontabを削除する
crontab -r -u www-data
# 攻撃者に置き換えられた可能性のあるバイナリを再インストールする
apt-get install --reinstall openssh-server nginx
繰り返し目にするミス:チームがマルウェアを削除してから、ハッシュやサンプルを取得していなかったことに気づく。その時点でさらに分析しようとしても、もう何も残っていない。証拠のバックアップが先、削除は後——例外なし。
ステップ5:安全にシステムを復旧する
攻撃ベクターを理解せずにバックアップから復元しても、時間稼ぎにしかならない。脆弱性はそのままで、2〜3週間後に同じインシデントが繰り返される——auditしたサーバーで少なくとも3回、この状況を目にしている。
サーバーをproductionに戻す前のチェックリスト:
- 攻撃ベクターが修正されていることを確認する(CVEのパッチ適用、漏洩した認証情報の変更、設定ミスの修正)
- 侵害が疑われる時点より前のバックアップから復元する
- チェックサムでバックアップの整合性を検証する
- 関連する全ユーザーのパスワードをリセットする
# 次回ログイン時にパスワードの変更を強制する
chage -d 0 username
# SSHホストキーを再生成する(古いキーが漏洩している場合は必須)
rm /etc/ssh/ssh_host_*
dpkg-reconfigure openssh-server
# 復元後の監視のためにauditdを有効化する
systemctl enable --now auditd
インシデント対応を楽にするための事前準備
これまで対応してきたインシデントの複雑さの80%は、事前準備がないことから来ている。今日すぐやるべき3つのことを紹介する——必要になってから準備するのでは遅い:
ベースラインの記録——通常状態のスナップショット
# 定期的に実行する(週次cron)、後で比較するために保存する
mkdir -p /var/lib/ir-baseline
md5sum /usr/sbin/* /usr/bin/* > /var/lib/ir-baseline/bin_hashes.txt
ss -tulnp > /var/lib/ir-baseline/network.txt
ps auxf > /var/lib/ir-baseline/processes.txt
crontab -l > /var/lib/ir-baseline/crontab.txt
ログをすぐにサーバー外へ送信する
攻撃者は侵入後にローカルログを削除することが多い。最初から外部のsyslogサーバーにログを送信しておくこと:
# /etc/rsyslog.confに追加する
echo "*.* @your-syslog-server:514" >> /etc/rsyslog.conf
systemctl restart rsyslog
IRツールキット——必要になる前にインストールしておく
apt-get install -y chkrootkit rkhunter tcpdump strace lsof auditd
結局、最も大きな違いをもたらすのは:どんな小さなインシデントでも必ず記録すること。ポストモーテムは責任者を探すためのものではない——次回50%速く対処できるようにするためのものだ。私はincidents.mdというファイルで各インシデントのタイムライン、攻撃ベクター、そしてlessons learnedを記録している。1年後には、それが最高のトレーニング資料になっている。

