Linux SUID、SGID、スティッキービット:システムセキュリティのための特殊パーミッション完全ガイド

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

クイックスタート:特殊パーミッションを5分で確認・設定する

ls -lでディレクトリを参照していると、通常xがあるはずの場所にsがあったり、パーミッション文字列の末尾にtがついていたりして、何か変だと気づくことがある。これらがLinuxの特殊パーミッションビットだ:SUID、SGID、スティッキービット。多くのシスアドはこのシンボルを知っている。しかし、これらのビットが実際に動作するとき、内部で何が起きているのかを正確に理解している人は、はるかに少ない。

すぐに確認する方法はこちら:

# SUID — 所有者の実行ビット位置にある 's' に注目
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Apr 16  2024 /usr/bin/passwd

# システム上のすべてのSUIDファイルを検索
find / -perm -4000 -type f 2>/dev/null

# すべてのSGIDファイルを検索
find / -perm -2000 -type f 2>/dev/null

# スティッキービットが設定されたディレクトリを検索
find / -perm -1000 -type d 2>/dev/null

数値での設定は単純だ:

  • SUID = 4 — パーミッションの前に付ける。例:chmod 4755
  • SGID = 2 — 例:chmod 2755
  • スティッキービット = 1 — 例:chmod 1777

シンボル表記も使える:

# 実行ファイルにSUIDを設定
chmod u+s /path/to/binary

# 共有ディレクトリにSGIDを設定
chmod g+s /shared/project

# 書き込み可能なディレクトリにスティッキービットを設定
chmod +t /shared/uploads

詳細解説:各ビットが内部で実際に行うこと

SUID — 一時的にファイル所有者になる

古典的なパズルがある:一般ユーザーがpasswdを実行してパスワードを変更できるのに、/etc/shadowはrootが所有し、他の誰も読めない。これはどういう仕組みなのか?

ls -l /etc/shadow
# ---------- 1 root shadow 1234 Jun  1 10:00 /etc/shadow

ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Apr 16 2024 /usr/bin/passwd

passwdの所有者実行スロットにあるsがSUIDだ。このバイナリが実行されると、カーネルはプロセスの実効UIDを呼び出したユーザーのUIDではなく、ファイルの所有者(この場合はroot)に一時的に変更する。プログラムは実行中の間だけ昇格した権限で動作し、終了すると権限は消える。

セキュリティ上の意味:SUID rootバイナリはすべて、権限昇格の標的になりうる。バッファオーバーフローやパスインジェクションなど、悪用可能なバグが1つあれば、攻撃者はrootシェルを手に入れられる。本番環境のSUIDリストは短く保ち、各エントリがそこにある理由を正確に把握しておくこと。

SGID — チームの問題を本当に解決するグループ継承

SGIDは対象がファイルかディレクトリかによって動作が異なる。

実行ファイルの場合:SUIDと同じ概念だが、所有者ではなくファイルのグループを使う。プロセスの実効GIDがファイルのグループになる。

ls -l /usr/bin/write
# -rwxr-sr-x 1 root tty 14328 Jan 20 2024 /usr/bin/write
# グループ実行位置の 's' = SGID

ディレクトリの場合:ディレクトリへのSGID設定こそ、実際の運用でその真価を発揮する場面だ。SGIDディレクトリ内に作成されたファイルは、作成者が属するプライマリグループに関係なく、自動的にディレクトリのグループを継承する。

# devteamグループ用の共有プロジェクトディレクトリを設定
sudo groupadd devteam
sudo mkdir /projects/webapp
sudo chown :devteam /projects/webapp
sudo chmod 2775 /projects/webapp

ls -ld /projects/webapp
# drwxrwsr-x 2 root devteam 4096 Jun  2 09:00 /projects/webapp

# ここに作成されたファイルは自動的にdevteamに属する
touch /projects/webapp/app.py
ls -l /projects/webapp/app.py
# -rw-rw-r-- 1 youruser devteam 0 Jun  2 09:01 app.py

SGIDがなければ、開発者はそれぞれ自分のプライマリグループでファイルを作成してしまう。他のメンバーがそのファイルに書き込むには、ファイルを作成するたびに手動でchmodが必要になる。ディレクトリへのSGIDはその手間を完全に解消する。

スティッキービット — 共有書き込み可能ディレクトリの保護

スティッキービットが解決する問題:/tmpが誰でも書き込める(そうでなければならない)場合、ユーザーbobaliceの一時ファイルを削除するのを何が防ぐのか?

ls -ld /tmp
# drwxrwxrwt 18 root root 4096 Jun  2 09:30 /tmp
# 末尾の 't' — スティッキービット

スティッキービットが設定されると、ディレクトリへの書き込み権限があっても他人のファイルを削除できなくなる。そのディレクトリ内のファイルを削除できるのは、ファイルの所有者ディレクトリの所有者、またはrootだけだ。

# aliceが一時ファイルを作成
touch /tmp/alice_session.sock

# bobが削除しようとする — 拒否される
rm /tmp/alice_session.sock
# rm: '/tmp/alice_session.sock' を削除できません: 操作は許可されていません

応用編:実際の本番シナリオ

チームディレクトリへのSGIDとスティッキービットの組み合わせ

この設定を本番のUbuntu 22.04サーバーで実際に運用した。結果:数ヶ月間、開発者からパーミッション関連のサポート依頼がゼロになった。ファイル作成後にchownchmodを覚えておく必要がなくなり、グループ継承と削除保護がそのまま機能した。

# SGID(2) + スティッキー(1) = 3 — 組み合わせる
sudo mkdir /var/shared/team-data
sudo chown root:devteam /var/shared/team-data
sudo chmod 3775 /var/shared/team-data

ls -ld /var/shared/team-data
# drwxrwsr-t 2 root devteam 4096 Jun  2 10:00 /var/shared/team-data
# 's' = SGID(グループ継承)、't' = スティッキービット(所有者のみ削除可能)

カスタム管理バイナリへのSUID — 正しいやり方

rootでないユーザーに特定のサービスを再起動させたい場合がある。SUIDが解決策のように思えるが、落とし穴がある:Linuxはシェルスクリプト上のSUIDを意図的に無視する。コンパイルされたCラッパーが必要だ。

# シェルスクリプトへのSUIDは黙って無視される
chmod u+s /usr/local/bin/restart-nginx.sh  # 効果はない

# 正しいアプローチ:最小限のCラッパー
cat << 'EOF' > /tmp/restart-nginx.c
#include <unistd.h>
int main() {
    setuid(0);
    execl("/bin/systemctl", "systemctl", "restart", "nginx", NULL);
    return 1;
}
EOF

gcc -o /usr/local/bin/restart-nginx /tmp/restart-nginx.c
chown root:webadmin /usr/local/bin/restart-nginx
chmod 4750 /usr/local/bin/restart-nginx
# 'webadmin'グループメンバーのみ実行可能、rootとして動作

正直なところ、ほとんどの状況ではカスタムSUIDバイナリよりsudoルールを使う方がすっきりしている。SUIDは控えめに使うこと — システム上のSUIDファイルが少ないほど、攻撃対象領域が小さくなる。

セキュリティ監査のためのベースラインと差分確認

# システム変更前にベースラインを作成
find / -perm /6000 -type f 2>/dev/null | sort > /root/suid_baseline.txt

# パッケージやパッチのインストール後に比較
find / -perm /6000 -type f 2>/dev/null | sort > /root/suid_current.txt
diff /root/suid_baseline.txt /root/suid_current.txt
# 新しい行 = システムに追加された新しいSUID/SGIDバイナリ

重要なパッケージ更新のたびにこれを実行すること。予期せず新しいSUIDバイナリが現れた場合は、すぐに調査する価値がある — パッケージマネージャーが意図的に追加したと思い込まないこと。

実践的なヒント:システムの強化

不要なSUID/SGIDビットの除去

サーバーはデスクトップ向けディストリビューションに付属するSUIDバイナリのほとんどを必要としない。不要なものを確認して除去しよう:

# ファイルからSUIDを除去
chmod u-s /usr/bin/at

# SGIDを除去
chmod g-s /usr/bin/newgrp

# ヘッドレスサーバーでよく見直されるもの:
ls -l /usr/bin/at       # バッチジョブスケジューラ
ls -l /usr/bin/newgrp   # グループ切り替え
ls -l /usr/bin/chsh     # ログインシェルの変更
ls -l /usr/bin/chfn     # ユーザー情報の変更

データパーティションへのnosuidマウントオプション

SUID実行ファイルが不要なパーティション(アップロードディレクトリ、/tmp、ユーザーホームパーティションなど)にはnosuidマウントオプションを使える。これにより、悪意のあるファイルが置かれても、そのパーティション上のSUIDおよびSGIDビットは完全に無効化される:

# /etc/fstab
/dev/sdb1  /data   ext4  defaults,nosuid,noexec  0  2
/dev/sdc1  /home   ext4  defaults,nosuid         0  2

# 再起動なしでライブリマウント
sudo mount -o remount,nosuid /data

クイックリファレンス:各ビットの使いどころ

  • 実行ファイルへのSUID:プログラムが本当に所有者として実行する必要がある場合のみ。SUID rootバイナリはすべて権限昇格の経路になりうる。
  • ディレクトリへのSGID:一貫したグループ所有権が重要なチーム共有ディレクトリに。安全で実用的、かつ広く役立つ。
  • ディレクトリへのスティッキービット:誰でも書き込めるまたはグループで書き込める共有ディレクトリすべてに。/tmp、アップロードディレクトリ、作業用スペースでは標準的。
  • シェルスクリプトへのSUID:Linuxは無視する — 代わりにsudoルールまたはコンパイル済みCラッパーを使うこと。

ペンテストやCTFに取り組んでいる場合、findvimcp、またはbashにSUID rootが設定されているのを見つけたら、実質的に無料の権限昇格だ — find -exec /bin/sh \;vimの組み込みシェルエスケープで数秒で到達できる。本番システムでは、他の誰かに見つけられる前にその設定ミスを修正すべきだ

Share: