🔥 TryHackMe「Linux PrivEsc」Task11〜15 詳細 WriteUp
〜 SUID が背負っている root の呪いを、5つの角度から解体する 〜

Linux PrivEsc
TryHackMe / WriteUp
■ はじめに
前回の Task7〜10 では、
- OS が「自分の実行環境(PATH / ファイル名 / cron)」を疑わずに信じている
- そこにユーザが毒を混ぜると、その“前提”ごと崩壊する
というテーマで、環境変数や cron、ワイルドカードを使った権限昇格を見てきました。
今回の Task11〜15 は、さらに一歩踏み込んで
「SUID / SGID 実行ファイルが背負っている root の権限そのものを、どう奪うか?」
という視点で構成されています。
キーワードはただひとつ。
SUID = 「実行した人」ではなく「ファイルの持ち主」として動く仕組み
つまり root 所有の SUID 実行ファイルは
「root によるリモート操作装置」に近い存在です。
Task11〜15 では、この SUID を軸に
- 既知のサービス Exploit
- 共有ライブラリ注入
- PATH 汚染
- Bash 関数による絶対パス乗っ取り
- Bash デバッグ機能の悪用
という 5パターンで「root への一本道」を実践していきます。
■ Task11:SUID / SGID Executables – Known Exploits
〜 古いサービスを “root 爆弾” に変える 〜
● 何が問題なのか?
まず最初に確認したのは、SUID / SGID が付いた実行ファイルの一覧です。
find / -type f -a \( -perm -u+s -o -perm -g+s \) -exec ls -l {} \; 2>/dev/null
この中に、
/usr/sbin/exim-4.84-3
という行が登場します。
Exim はメール転送エージェントで、root 権限で動作することが多いサービスです。
古いバージョンの Exim は、ローカル権限昇格の CVE が大量に見つかっている“爆発物”のような存在で、4.84-3 も例外ではありません。
TryHackMe の VM には、すでに対応する Exploit が置かれています。
/home/user/tools/suid/exim/cve-2016-1531.sh
これを実行するだけで root シェルを奪取できます。
/home/user/tools/suid/exim/cve-2016-1531.sh
whoami # → root
● 背景 – なぜこれで root になるのか?
- Exim はメール配送のために root で起動している
- 古いバージョンには「引数処理や権限ドロップが甘い」バグがある
- ローカルユーザが特定のオプションや入力を与えると、
root 権限のまま任意コマンドを実行できてしまう
つまり Task11 でやっていることは、
「バージョンが特定できる SUID サービス」
×「公開済みのローカル Exploit」
を組み合わせて root を取る、という非常に現実的なパターンの再現です。
● 実務的に何を学ぶべきか?
- 「外向きのサービス」だけが危険ではない
→ ローカルで root で動いているサービスも立派な攻撃面 - パッケージ管理とバージョン管理は、それだけで “権限昇格対策” になる
- 「SUID + 古いバージョン + 公開 Exploit」の組み合わせは即レッドフラグ
■ Task12:SUID / SGID – Shared Object Injection
〜 “依存ライブラリ” に毒を入れて root を奪う 〜
● 何をやったか?
対象は SUID バイナリ:
/usr/local/bin/suid-so
これを実行すると、
/usr/local/bin/suid-so
# → 「進捗バーが出て終わり」
一見問題なさそうな挙動ですが、strace で内部を覗きます。
strace /usr/local/bin/suid-so 2>&1 | grep -iE "open|access|no such file"
ここで、決定的な行が出てきます。
open("/home/user/.config/libcalc.so", O_RDONLY) = -1 ENOENT (No such file or directory)
つまりこの SUID バイナリは、
- ユーザのホームディレクトリ配下
/home/user/.config/libcalc.so
という共有ライブラリを root権限でロードしようとしているが、ファイルが存在しない 状態です。
攻撃者視点ではこう見えます。
「root で動くプロセスが、
自分が書き込める場所にある .so を読み込もうとしている」
これは Shared Object Injection(共有ライブラリ注入)が成立する典型パターンです。
● 攻撃手順
-
.configディレクトリを作成
mkdir /home/user/.config
-
/home/user/tools/suid/libcalc.cをコンパイルしてlibcalc.soを生成
gcc -shared -fPIC -o /home/user/.config/libcalc.so /home/user/tools/suid/libcalc.c
libcalc.c の中身は「単に /bin/bash -p を起動するだけ」のシンプルなコードです。
- もう一度実行
/usr/local/bin/suid-so
whoami # → root
今度はプログレスバーではなく root シェルが降ってきます。
● 背景 – どこが崩壊ポイントか?
- SUID バイナリは「ファイル所有者の UID」で動く
→ root 所有なら root として実行される - そのプロセスが動的に .so をロードする場合、
「ロード先パス」も root 権限で扱われる - そのパスが「ユーザ書き込み可能」かつ「ファイル未存在」のとき、
ユーザはそこに好きな共有ライブラリを置ける
結果:
root がユーザの用意した libcalc.so を信じてロードし、
その中の/bin/bash -pが root 権限で実行される。
● 学べること
- 「実行ファイル本体が安全に見えても、依存関係(共有ライブラリ)が崩れていればアウト」
- ホームディレクトリ直下や
~/.configを root が信用した時点で負け -
setuidプログラム + 動的リンクは、それだけで要注意コンボ
■ Task13:SUID / SGID – Environment Variables(PATH 汚染)
〜 PATH を書き換えて “偽のコマンド” を root に踏ませる 〜
● 状況の確認
対象は:
/usr/local/bin/suid-env
まず実行すると、apache2 を起動しようとしていることが分かります。
/usr/local/bin/suid-env
# → Starting web server: apache2 ...
strings で中身を覗くと、こんな行が見つかります。
strings /usr/local/bin/suid-env
# ...
service apache2 start
つまり内部で
system("service apache2 start");
のように、絶対パス無しで service コマンドを呼び出していることが分かります。
● PATH の仕様がどう絡んでくるか?
Linux では、コマンド名に / が含まれていない場合、
PATH 環境変数に列挙されたディレクトリを先頭から順に探索し、
最初に見つかった実行ファイルを起動します。
PATH=.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
という状態なら、現在ディレクトリの ./service が
/usr/sbin/service よりも優先されます。
SUID バイナリは、本来「こうした環境変数を信用してはいけない」存在です。
しかし今回の suid-env は、そのままユーザ環境の PATH を継承しています。
● 攻撃手順
- 偽の
serviceコマンドを作成
gcc -o service /home/user/tools/suid/service.c
(中身は /bin/bash -p を実行するだけのコード)
- PATH 先頭にカレントを追加、
suid-envを実行して root を奪取
PATH=.:$PATH /usr/local/bin/suid-env
- rootになっているかを確認
whoami # → root
SUID プロセスは
“本物の
/usr/sbin/service” ではなく
“ユーザ作成の./service”
を root 権限で起動してしまいます。
● 崩壊した前提
-
OS 側の前提:
「PATH に並んでいるディレクトリの中身は、
そのユーザにとって信頼できるものだろう」 -
現実:
「PATH はユーザが自由に変更可能」
「user が置いた偽コマンドを root が踏んでしまうことがある」
Task13 は、
「絶対パスを指定しないコマンド呼び出しは、SUID環境ではそれだけで爆弾」
という事実を実感させるタスクです。
■ Task14:SUID / SGID – Abusing Shell Features (#1)
〜 Bash の“仕様”が絶対パスを無効化する瞬間 〜
● 一見、安全対策済みに見える suid-env2
今回のターゲットは以下の SUID バイナリです。
/usr/local/bin/suid-env2
まず、内部挙動を調査します。
strings /usr/local/bin/suid-env2
結果の中に、次の記述が確認できます。
/usr/sbin/service apache2 start
Task13 では service に絶対パスがなく、PATH 汚染が成立しました。
しかし今回は、絶対パス /usr/sbin/service を明示しているため、一見安全に思えます。
普通ならこう考えるでしょう:
「絶対パス指定なら PATH 汚染できない → 安全」
でも、Bash はそんな単純ではない。
● 攻撃の成立条件を必ず確認する(最重要)
この攻撃は 古い Bash の仕様を利用しています。
まず、Bash のバージョンを確認します。
/bin/bash --version
TryHackMe の VM では、以下のように 4.2-048 未満であることがわかります。
GNU bash, version 4.1.5(1)-release
⚠️ Bash 4.4 以降ではこの仕様は修正されており、この攻撃は無効化されています。
つまり、脆弱性ではなく“歴史的仕様”の副作用が攻撃面になっています。
この「成立条件確認」が Task14 最大の学習ポイントです。
● Bash の “関数エクスポート”を悪用する
古い Bash には、次のような仕様があります:
- 関数名に絶対パス風の名前を付けられる
-
export -fにより関数定義を子プロセスに継承できる - コマンド解決順で ファイルより関数が優先される
つまり、
絶対パスを書いても、本物が実行される保証はない
ということです。
では exploit を実行してみましょう。
function /usr/sbin/service { /bin/bash -p; }
export -f /usr/sbin/service
/usr/local/bin/suid-env2
whoami # → root
● 何が起きているのか?
| 仕組み | 動作 |
|---|---|
| suid-env2 |
/usr/sbin/service apache2 start を実行 |
| Bash | 環境変数にエクスポートされた“関数名”を参照 |
| 結果 | 実ファイル /usr/sbin/service ではなく、攻撃者定義の関数が root 権限で起動 |
つまり、OS 管理者の直感である
絶対パス指定 = 安全
は Bash の設計思想に基づいた現実の前では崩壊します。
● Task14 が示す本質
| 直感 | 現実 |
|---|---|
| 絶対パスは無敵 | Bash は関数を優先することがある |
| PATH だけ守れば良い | シェル実装も攻撃対象になり得る |
| コマンドはファイルに紐づく | “名前解決”が攻撃面になる |
つまり、これは exploit ではなく OS 仕様の悪用
そして 前提の理解が欠落したときに権限境界は崩壊することを示したタスクです。
● まとめ
Task14 は Task13 の発展形ではありません。
「絶対パスで防御できる」という誤解を破壊するタスクです。
- PATH を汚染できないようにした
→ まだ足りない - Bash は“関数名がパスに見えても優先する”
→ 仕様そのものが攻撃面
そのため、攻撃可能条件(Bash バージョン)を確認する工程こそがこの Task の核心なのです。
■ Task15:SUID / SGID – Abusing Shell Features (#2)
〜 PS4 / デバッグトレースを root の実行トリガーに変える 〜
● PS4 と SHELLOPTS=xtrace の悪夢
Bash にはデバッグモード(set -x)があります。
このとき、各コマンド実行前に
PS4 で指定された文字列 + コマンド内容
が出力されます。
ここで問題になるのが、
-
SHELLOPTS=xtraceを環境変数に設定すると
「デバッグモード ON の状態で Bash が起動する」 - PS4 の中でコマンド置換
$( ... )を使うと、そのコマンドが実行される
という仕様です。
TryHackMe の手順はこうです。
env -i SHELLOPTS=xtrace PS4='$(cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash)' /usr/local/bin/suid-env2
-
env -i: 環境をクリーンにしてから指定した変数だけセット -
SHELLOPTS=xtrace: Bash をデバッグモードで起動 -
PS4='$( ... )': デバッグ出力のたびに中のコマンドを実行
suid-env2 の内部で Bash が使われている場合、
この「PS4 に埋め込んだコマンド」が root 権限で実行されます。
結果として、
-
/bin/bashが/tmp/rootbashにコピーされる -
/tmp/rootbashに SUID (setuid root) が付与される
最後に:
/tmp/rootbash -p
whoami # → root
これで、SUID 化された bash を使って root シェルが完成します。
● ここで壊しているもの
Task15 は、もはや「設定ミス」というレベルを超えて、
- Bash のデバッグ機能
- PS4 / SHELLOPTS という内部用の仕組み
そのものが攻撃面になる、という状態を示しています。
SUID バイナリが「Bash を経由して何かを実行する」だけで、
こうした内部仕様を突かれる余地が生まれます。
■ Task11〜15 の総括
〜 SUID は「root の代理人」であり、少しの油断で裏切る 〜
Task11〜15 で壊した「OSの前提」を整理すると、こうなります。
| Task | 壊した前提 | 現実 |
|---|---|---|
| 11 | サービスは適切に権限管理され、脆弱でない | 古いサービス+CVE でローカルから root 取り放題 |
| 12 | ロードされる共有ライブラリは安全な場所から来る | ユーザ書き込み可能なパスから root が .so を読み込む |
| 13 | PATH は信頼できる。コマンド名だけで呼んでも平気 | PATH を汚染すると SUID が偽コマンドを root として実行 |
| 14 | 絶対パスで実行ファイルを指定すれば安全 | Bash 関数が絶対パスすら乗っ取る |
| 15 | デバッグ機能(PS4 / xtrace)は安全な補助機能 | PS4 に埋め込んだコマンドが root 権限で実行されうる |
共通しているのは、
SUID プログラムは「root の権限を代理実行しているだけ」であり、
その周辺にある「前提」「仕組み」「おまけ機能」が
少しでも崩れると、そのまま root まで滑り落ちる
という点です。
● 攻撃者視点でのチェックポイント
- SUID / SGID 実行ファイルの列挙
- 古いバージョンのサービス+既知CVE
- 動的リンク / 共有ライブラリのロードパス
-
system()/popen()などで呼ばれるコマンドのパス指定 - Bash に依存しているかどうか(関数 / デバッグ)
これらを “変な方向” から眺めていくと、
「OSの設計思想と現実の運用のズレ」がそのまま脆弱性になります。
● 防御側の観点
- そもそも SUID / SGID を最小限に削る
- SUID プログラムでシェル(
/bin/sh,/bin/bash)を使わない - 絶対パス指定+環境変数のサニタイズ
- ユーザ書き込み可能パスをライブラリ検索や PATH から排除
- 古いサービス+CVE の棚卸しとアップデート
■ おわりに
Task11〜15 は、単なる「root になるテクニック集」ではなく、
「SUID という仕組みが、どれほど多くの“前提”の上に立っているか」
「その前提を 1つずつ崩すと、どうやって root に直結するか」
を体験するためのパートでした。
SUID は便利な仕組みですが、
その実体は「root の権限を借りるための穴」です。
- サービスのバージョン管理をサボる
- ライブラリロードパスを雑に設計する
- PATH や Bash の機能を“なんとなく”で使う
こうした積み重ねが、そのまま「ローカル権限昇格」の温床になります。
次の Task16〜18 では、
SUID ではなく Passwords & Keys(人間のミスや秘密管理) に焦点が移り、
さらに 19〜21 では NFS や Kernel Exploit、そして自動化ツールによる PrivEsc 探索へと進んでいきます。
そこでも共通するテーマは変わりません。
OS や管理者が「そうであってほしい」と思っている前提
vs
攻撃者が「本当にそうか?」と揺さぶる視点
このギャップの中に、権限昇格の全てが詰まっています。
以上、TryHackMe「Linux PrivEsc」Task11〜15 の完全 WriteUp でした。
SUID 周りを体系的に理解する一助になれば幸いです。