Linux Capabilitiesをマスターする:SUIDに代わるgetcapとsetcapで実現するきめ細かなプロセス権限管理

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

SUID vs. Linux Capabilities:特権付与における2つのアプローチの違い

Linuxサーバーをしばらく管理していれば、SUIDバイナリに出会ったことがあるはずです。sビットが設定されたファイルで、実行時に呼び出し元ではなくファイルオーナーの権限でプロセスを動かすものです。代表的な例が ping、passwd、sudo。機能はしますが、よく考えるほど無視しにくい問題を抱えています。

SUIDは「全か無か」の仕組みです。pingがrootとして実行されると、実際に必要なのはrawソケットを開く権限だけなのに、ファイルシステムへのアクセス、プロセス制御、ネットワーク設定など、すべてのroot権限を得てしまいます。本質的には診断ツールにすぎないものにしては、攻撃対象領域が広すぎます。

Capabilitiesはまさにこの問題を解決するために存在します。root権限を丸ごと付与する代わりに、権限を個々の単位に分解し、プロセスが本当に必要とするものだけを割り当てます。pingに必要なのはCAP_NET_RAWだけ。それ以外は不要で、SUIDも必要ありません。

最近のUbuntuやDebianディストリビューションでは、pingはすでにこの方式で配布されています。次のコマンドを実行すると確認できます:

getcap /usr/bin/ping
# 出力: /usr/bin/ping cap_net_raw=ep

SUIDビットなし。必要なCapabilityがひとつあるだけ。それがすべてのコンセプトです。

メリット・デメリット:Capabilities vs. SUID

SUID — 馴染み深い諸刃の剣

メリット:

  • 設定が簡単:chmod u+s /path/to/binary
  • すべてのLinuxバージョンで広くサポートされている
  • カーネルモジュールや機能フラグ不要
  • バイナリ側での対応不要で透過的に動作する

デメリット:

  • バイナリはUID 0のすべての権限を取得 — 必要以上の権限になる
  • そのバイナリに脆弱性があれば、完全なroot権限昇格につながる
  • 監査が困難:find / -perm -4000でリストは得られるが、なぜ設定されているかはわからない
  • ファイルを実行できるすべてのユーザーに適用 — ユーザーごとの制御はできない

Linux Capabilities — ピンポイントなアプローチ

メリット:

  • 最小権限の原則:必要なものだけを付与
  • バイナリが侵害されても、攻撃者が得るのはそのCapabilityのみ — rootフルアクセスではない
  • getcap -r /で監査可能 — どのCapabilityがどこに設定されているか一目瞭然
  • バイナリ単位の細粒度制御、コード内でスレッドレベルのCapability管理もサポート
  • AppArmor、SELinuxなどのセキュリティフレームワークとの互換性がある

デメリット:

  • すべてのソフトウェアがCapabilityに対応しているわけではない — レガシーツールはSUIDが必要な場合もある
  • Capabilityフラグ(e、p、i)には学習コストがある
  • ファイルのCapabilityは拡張属性として保存される — 一部のファイルシステムやバックアップツールでは削除される場合がある
  • スクリプト言語(Python、Perl)はファイルCapabilityを自動的に継承しない — ラッパーバイナリが必要

推奨構成:何をどう使い分けるか

本番環境での私の考え方をまとめます:

  • Capabilityを使う:特定の権限だけが必要なコンパイル済みバイナリ — ネットワークツール(tcpdump、ping)、時刻同期デーモン、パケットキャプチャエージェントなど
  • SUIDを維持する:完全な権限モデルが意図的かつ十分に監査されているシステムツール:sudo、su、passwd — これらはroot権限を前提に設計されている
  • SUIDを完全に避ける:自分でコンパイルしたもの、またはデプロイするサードパーティツール — Capabilityを使うか、必要なCapabilityのみを持つ専用の低権限ユーザーで実行する
  • 専用ユーザーでサービスを実行し、Capabilityと組み合わせる — デーモンに最も適したモデル

私のUbuntu 22.04サーバーでの実例:以前はカスタムパケットアナライザーをSUIDラッパー経由でrootとして実行していました。これを専用のnetmonシステムユーザーに移行し、バイナリにCAP_NET_RAWとCAP_NET_ADMINのみを付与する作業は約10分で完了。動作は変わらず、攻撃対象領域は大幅に縮小しました。こういった即効性のある改善こそが、セキュリティ強化を苦行ではなく達成感のある作業に変えてくれます。

実装ガイド

ツールのインストール

Debian/Ubuntu の場合:

sudo apt install libcap2-bin

RHEL/CentOS/AlmaLinux の場合:

sudo dnf install libcap

これで getcap、setcap、capsh が使えるようになります。

Capabilityフラグの理解

cap_net_raw=ep のような出力が表示された場合、= の後の文字がCapabilityセットを示しています:

  • e — Effective(有効):現在アクティブなCapability
  • p — Permitted(許可):有効化できるCapability
  • i — Inheritable(継承):子プロセスに引き継がれるCapability

ほとんどのユースケースでは =ep で十分です — バイナリ実行時にCapabilityが許可かつ有効な状態になります。

既存のCapabilityを確認する

# 特定のファイルを確認する
getcap /usr/bin/ping

# ファイルシステム全体を再帰的に監査する(rootで実行)
getcap -r / 2>/dev/null

この再帰スキャンは、本番サーバーで四半期ごとに実行しています。素早い健全性チェックです — 自分で設定していないものが表示されたら、調査する価値があります。

バイナリにCapabilityを設定する

rawソケットアクセスが必要なカスタムツール /usr/local/bin/netmon があるとします:

# CAP_NET_RAWを付与する(rawソケットアクセス)
sudo setcap cap_net_raw=ep /usr/local/bin/netmon

# 確認する
getcap /usr/local/bin/netmon
# 出力: /usr/local/bin/netmon cap_net_raw=ep

インターフェースモードの設定も必要なパケットキャプチャツールの場合:

sudo setcap cap_net_raw,cap_net_admin=ep /usr/local/bin/netmon

Capabilityを削除する

# バイナリからすべてのCapabilityを削除する
sudo setcap -r /usr/local/bin/netmon

# 削除されたことを確認する
getcap /usr/local/bin/netmon
# (出力なし = Capabilityなし)

既存のSUIDバイナリをCapabilityに置き換える

具体的な例:tcpdump のSUIDビットを削除してCapabilityに切り替える手順です。

# 現在の状態を確認する
ls -la /usr/bin/tcpdump
# ディストリビューションによって -rwxr-sr-x または -rwsr-xr-x と表示される

# SUIDビットを削除する
sudo chmod u-s /usr/bin/tcpdump

# 必要なCapabilityのみを付与する
sudo setcap cap_net_raw,cap_net_admin=ep /usr/bin/tcpdump

# 一般ユーザーとしてテストする
tcpdump -i eth0 -c 5

非rootユーザーで実行するサービスへのCapability設定

私が最もよく使うパターン:専用ユーザーを作成し、バイナリにCapabilityを設定するだけです。

# システムユーザーを作成する(ログインシェルなし、ホームディレクトリなし)
sudo useradd -r -s /usr/sbin/nologin netmon

# バイナリにCapabilityを設定する
sudo setcap cap_net_raw=ep /usr/local/bin/netmon

# そのユーザーでサービスを実行する(systemdユニット内に記述)
# [Service]
# User=netmon
# ExecStart=/usr/local/bin/netmon

# 実行時にプロセスが正しいCapabilityを持っていることを確認する
cat /proc/$(pgrep netmon)/status | grep Cap

/proc/PID/status の出力にはCapabilityのビットマスクが表示されます。デコードするには:

capsh --decode=0000000000002000
# 0x0000000000002000=cap_net_raw

主要なCapabilityリファレンス

  • cap_net_raw — Rawソケットおよびパケットソケット(ping、tcpdump)
  • cap_net_admin — ネットワークインターフェース設定、ルーティング、iptables
  • cap_net_bind_service — root不要で1024番未満のポートにバインド
  • cap_sys_time — システムクロックの設定(ntpd、chrony)
  • cap_dac_override — ファイルの読み書きパーミッションチェックをバイパス
  • cap_setuid / cap_setgid — プロセスのUID/GIDを変更
  • cap_sys_ptrace — 任意のプロセスをトレース(デバッガー、strace)

完全なリスト:man 7 capabilities

落とし穴:Capabilityとスクリプトの非互換性

PythonやBashスクリプトにCapabilityを設定しても機能しません — カーネルはファイルCapabilityをELFバイナリにのみ適用し、インタープリタスクリプトには適用しないためです。対処法は2つあります:小さなCラッパーを書くか、systemdのユニットファイルで AmbientCapabilities= ディレクティブを使って ambient capabilities を利用する方法です:

[Service]
User=netmon
AmbientCapabilities=CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_RAW
ExecStart=/usr/bin/python3 /opt/netmon/monitor.py

単一のCapabilityが必要なPythonサービスに最も適した方法です — SUIDも不要、rootでの実行も不要。

クイック監査スクリプト

ツールキットに追加しておきたいスクリプトです — SUIDバイナリとCapabilityが設定されたファイルを並べて表示します:

#!/bin/bash
echo "=== SUID バイナリ ==="
find / -perm -4000 -type f 2>/dev/null | sort

echo ""
echo "=== Capability が設定されたファイル ==="
getcap -r / 2>/dev/null | sort

システム変更の前後に実行することで、権限の状態を明確に把握できます。適切に設定されたシステムでは、SUIDのリストは短く予測可能であるべきで — Capabilityのリストに記載された各エントリには、設定された理由が文書化されているべきです。

Capabilitiesは魔法ではありません。各バイナリが実際に何を必要としているかを慎重に考える必要があります — 間違えれば、過剰な権限を持つバイナリを別のものに置き換えただけになります。しかし正しく使えば、SUIDに対する本物の改善をもたらし、セキュリティ監査が古い遺物を掘り起こすような退屈な作業ではなくなります。

Share: