setuid/setgid ビットと Python スクリプト

setuid/setgid ビットは実行ファイルに特別な権限を与える仕組みだ。しかし Python スクリプトでは期待どおりに動作しないことが多い。

setuid/setgid とは

通常、プログラムは実行したユーザーの権限で動作する。setuid ビットが設定されていると、ファイルの所有者の権限で動作する。

通常の実行

プログラムは実行ユーザーの UID/GID で動作する

setuid 付きの実行

プログラムはファイル所有者の UID で動作する。root 所有なら root 権限で動作する

# setuid ビットの例: passwd コマンド
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Jan 1 00:00 /usr/bin/passwd
#    ^-- s は setuid ビット

# 一般ユーザーが実行しても、root 権限で /etc/shadow を編集できる

Python スクリプトに setuid は効かない

Python スクリプトに setuid ビットを設定しても、期待どおりに動作しない。

# setuid を設定(意味がない)
sudo chown root:root script.py
sudo chmod u+s script.py

ls -l script.py
# -rwsr-xr-x 1 root root 100 Jan 1 00:00 script.py

# 実行しても root 権限にならない
./script.py

これはセキュリティ上の理由で、多くの Unix 系 OS がスクリプトの setuid を無視するためだ。

なぜスクリプトの setuid は危険か

シェバン行の脆弱性

#!/usr/bin/python を含むスクリプトは、カーネルが /usr/bin/python script.py として実行する。この間にスクリプトを差し替える攻撃(race condition)が可能だった。

インタプリタの複雑さ

Python インタプリタは環境変数(PYTHONPATH など)に影響される。攻撃者が環境変数を操作して悪意のあるコードを実行させる可能性がある。

Linux は「nosuid」マウントオプションやカーネルの設定でスクリプトの setuid を無効化している。

Python で特権を得る方法

Python スクリプトで root 権限が必要な場合、いくつかの代替手段がある。

sudo で実行

最も単純で安全。ユーザーが明示的に権限昇格を許可する。

setuid バイナリラッパー

C で書いた setuid バイナリから Python スクリプトを呼び出す。

capabilities

Linux の capabilities を使って、root 権限の一部のみを付与する。

setuid ヘルパープログラム

pkexec、dbus 経由のサービスなど、権限昇格を安全に行う仕組みを利用する。

sudo の例

# script.py - root 権限が必要な処理
import os
import sys

if os.geteuid() != 0:
    print("Root権限が必要です。sudo で実行してください。")
    sys.exit(1)

# root 権限での処理
with open("/etc/shadow", "r") as f:
    print("shadow ファイルを読めました")
sudo python script.py

C ラッパーの例

どうしても setuid が必要な場合、C ラッパーを使う。

// wrapper.c
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    // 環境変数をクリア(セキュリティ対策)
    clearenv();
    
    // 安全な環境変数だけ設定
    setenv("PATH", "/usr/bin:/bin", 1);
    
    // Python スクリプトを実行
    execl("/usr/bin/python3", "python3", "/opt/myapp/script.py", NULL);
    
    return 1;
}
# コンパイルして setuid を設定
gcc -o wrapper wrapper.c
sudo chown root:root wrapper
sudo chmod u+s wrapper

# wrapper 経由で実行
./wrapper

capabilities の利用

Linux capabilities は root 権限を細分化した仕組みだ。

# Python に特定のポートをバインドする権限を付与
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/python3

# これで一般ユーザーでも 80 番ポートを使える
python3 -c "import socket; s=socket.socket(); s.bind(('', 80))"
# capabilities を確認する
import subprocess
result = subprocess.run(
    ["getcap", "/usr/bin/python3"],
    capture_output=True, text=True
)
print(result.stdout)

ただし、Python インタプリタに capabilities を付与すると、そのシステム上のすべての Python スクリプトが影響を受けるため注意が必要だ。

setgid の利用例

setuid ほど危険ではないが、setgid も使える場面がある。

# 特定グループに属するファイルにアクセスさせたい場合
sudo chown root:secretgroup script.py
sudo chmod g+s script.py

# スクリプト内で補助グループを確認
python3 -c "import os; print(os.getgroups())"

まとめ

方法安全性用途
sudoユーザーが明示的に権限昇格を許可する場合
C ラッパー自動化された権限昇格が必要な場合
capabilities特定の権限のみ必要な場合
setuid スクリプト不可OS が無視するため使えない
Python で特権が必要なら

sudo を使うのが最も安全で推奨される方法。自動化が必要なら、sudoers で NOPASSWD を設定するか、専用のサービスとして実装する。

setuid の「魔法」に頼ろうとせず、適切な権限管理の仕組みを使うことが重要だ。