setuid/setgid ビットと Python スクリプト
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.pyC ラッパーの例
どうしても 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 経由で実行
./wrappercapabilities の利用
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 の「魔法」に頼ろうとせず、適切な権限管理の仕組みを使うことが重要だ。