| ID 類型 | 宏/欄位 | 含義 | SetUID 影響 |
| Real UID | ruid / getuid() | 啟動進程的真實使用者 | 不變 |
| Effective UID | euid / geteuid() | 權限檢查時使用的 UID | 變為檔案擁有者 UID |
| Saved Set-UID | suid / getresuid() | 保存的 SetUID,用於切換回 | 設為檔案擁有者 UID |
| File System UID | fsuid | 檔案系統權限檢查用(通常等同 euid) | 跟隨 euid |
二、SetUID 運作流程
- 使用者執行帶有 SetUID 位的可執行檔(
chmod u+s prog 或 chmod 4755 prog)
- 核心檢查:檔案可執行、非目錄、檔案系統未掛載
nosuid
- 核心設定進程 euid = 檔案 st_uid,suid = 檔案 st_uid
- 進程以檔案擁有者身分進行權限檢查(開啟檔案、綁定埠口等)
- 進程可呼叫
setuid()、seteuid() 在 ruid/euid/suid 間切換
三、實戰演示:C 程式讀取 root 專屬檔案
1. 準備受保護檔案
# root 執行
echo "secret data" > /root/secret.txt
chmod 600 /root/secret.txt
ls -l /root/secret.txt
# -rw------- 1 root root 12 ... /root/secret.txt
2. 一般使用者編寫讀取程式
// read_secret.c
#include
#include
#include
#include
int main() {
int fd = open("/root/secret.txt", O_RDONLY);
if (fd == -1) {
perror("open failed");
return 1;
}
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
printf("%s\n", buf);
}
close(fd);
return 0;
}
3. 編譯與測試(無 SetUID)
$ gcc read_secret.c -o read_secret
$ ./read_secret
open failed: Permission denied
4. 啟用 SetUID(需 root)
# root 執行
chown root:root read_secret
chmod 4755 read_secret # 或 chmod u+s read_secret
ls -l read_secret
# -rwsr-xr-x 1 root root ... read_secret (s 表示 SetUID + 執行權限)
5. 一般使用者再次執行
$ ./read_secret
secret data
# 成功讀取!進程 euid 變為 root (0)
四、程式碼方式設定 SetUID(對應原文範例)
#include
int main() {
struct stat buf;
stat("read_secret", &buf);
buf.st_mode |= S_ISUID; // 設定 SetUID 位
chmod("read_secret", buf.st_mode);
return 0;
}
五、SetGID 與 Sticky Bit 關聯
| 機制 | 位元 | 八進位 | 檔案效果 | 目錄效果 |
| SetUID | S_ISUID | 4000 | 執行時 euid=檔案擁有者 | 無效 |
| SetGID | S_ISGID | 2000 | 執行時 egid=檔案群組 | 新檔繼承目錄群組 |
| Sticky | S_ISVTX | 1000 | 無效 | 僅擁有者可刪除檔案 |
六、安全風險與防護最佳實踐
⚠️ 風險場景
- SetUID 程式存在緩衝區溢位、指令注入、路徑遍歷等漏洞 → 攻擊者取得 root shell
- 環境變數污染(
LD_PRELOAD、PATH、IFS)劫持程式行為
- 不當使用
system()、popen() 呼叫 Shell
- 臨時檔案競態條件(TOCTOU)
✅ 開發防護清單
- 盡量避免 SetUID:改用
sudo、capabilities、systemd socket activation、polkit
- 最小權限原則:僅在必要程式碼段提權,隨即降權(
seteuid(getuid()))
- 清理環境:
clearenv() 或手動重設 PATH、IFS、LD_*
- 避免 Shell:直接呼叫
execve(),不使用 system()
- 驗證輸入:嚴格檢查所有外部輸入
- 安全臨時檔:
mkstemp()、O_EXCL
- 審計日誌:記錄關鍵操作供事後追蹤
✅ 系統管理防護
# 1. 定期掃描 SetUID 程式
find / -type f -perm -4000 -ls 2>/dev/null
# 2. 掛載選項限制
# /etc/fstab: /home nosuid,nodev,noexec
# 禁止在家目錄執行 SetUID 程式
# 3. 移除不必要的 SetUID
chmod u-s /usr/bin/unnecessary-setuid-binary
# 4. 使用 capabilities 替代(Linux 2.1+)
# 例:ping 需原始 socket,改用 CAP_NET_RAW
setcap cap_net_raw+ep /usr/bin/ping
# 移除 SetUID
chmod u-s /usr/bin/ping
七、經典 SetUID 程式清單
| 程式 | 用途 | 現代替代 |
/usr/bin/passwd | 修改密碼(寫 /etc/shadow) | PAM + sudo |
/usr/bin/su | 切換使用者 | sudo |
/usr/bin/sudo | 提權執行 | 核心工具 |
/usr/bin/ping | ICMP 需原始 socket | CAP_NET_RAW |
/usr/bin/mount | 掛載檔案系統 | sudo + /etc/fstab user 選項 |
/usr/bin/umount | 卸載檔案系統 | 同上 |
SetUID 是一把雙面刃:正確使用能實現最小權限提升,濫用則是提權漏洞溫床。現代 Linux 傾向以 Capabilities、systemd、Polkit 替代 SetUID,建議新專案優先評估替代方案。