setuid/setgid ビットは実行ファイルに特別な権限を与える仕組みだ。しかし Python スクリプトでは期待どおりに動作しないことが多い。
setuid/setgid とは
通常、プログラムは実行したユーザーの権限で動作する。setuid ビットが設定されていると、ファイルの所有者の権限で動作する。
プログラムは実行ユーザーの UID/GID で動作する
プログラムはファイル所有者の 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 権限が必要な場合、いくつかの代替手段がある。
最も単純で安全。ユーザーが明示的に権限昇格を許可する。
C で書いた setuid バイナリから Python スクリプトを呼び出す。
Linux の capabilities を使って、root 権限の一部のみを付与する。
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 が無視するため使えない |
sudo を使うのが最も安全で推奨される方法。自動化が必要なら、sudoers で NOPASSWD を設定するか、専用のサービスとして実装する。
setuid の「魔法」に頼ろうとせず、適切な権限管理の仕組みを使うことが重要だ。