本文原始內容示範透過 Setuid C 包裝程式讓 PHP (Apache/www-data) 以 Root 權限執行系統指令 (如 `iptables`、新增用戶)。這是極度危險的反模式,會徹底摧毀伺服器安全防線。本文轉為安全警示,分析風險並提供正確的權限分離與授權機制。
同理,`popen("su root -c ...")` 需終端機輸入密碼,無法自動化,且同樣極度危險。
核心原則:**Web 進程 (低權限) 不直接執行高權限操作,而是透過安全通道請求獨立的高權限服務執行,並嚴格驗證、審計、限流。**
[Unit]
Description=Privileged Task Executor
# 不設定 User=root (預設即為 root),但僅透過 Socket 啟動
[Service]
Type=oneshot
ExecStart=/usr/local/bin/privileged-task.sh
# 關鍵:標準輸入/輸出連接到 Socket,不直接暴露 Shell
StandardInput=socket
StandardOutput=journal
# 安全強化
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp/data
CapabilityBoundingSet=CAP_NET_ADMIN CAP_DAC_OVERRIDE # 僅授予必要 Capability
AmbientCapabilities=CAP_NET_ADMIN CAP_DAC_OVERRIDE
[Install]
WantedBy=multi-user.target
[Unit]
Description=Privileged Task Socket
[Socket]
ListenStream=/run/my-privileged-task.sock
# 僅允許特定用戶/組連接 (如 www-data)
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660
[Install]
WantedBy=sockets.target
<?php
function requestPrivilegedAction(string $action, array $params): array
{
$socket = '/run/my-privileged-task.sock';
$payload = json_encode(['action' => $action, 'params' => $params, 'request_id' => uniqid()]);
$client = stream_socket_client("unix://{$socket}", $errno, $errstr, 5);
if (!$client) throw new Exception("Socket 連線失敗: $errstr");
fwrite($client, $payload . "\n");
fclose($client); // 關閉寫入端,服務端讀到 EOF 執行
// 這裡可改為雙向通訊讀取回應,或由服務端寫入結果檔/資料庫/回呼 Webhook
return ['status' => 'queued', 'request_id' => $payload['request_id']];
}
// 使用
$result = requestPrivilegedAction('iptables_add_rule', ['chain' => 'INPUT', 'rule' => '-p tcp --dport 80 -j ACCEPT']);
優點:Web 進程零 Root 權限、Systemd 管理生命週期/日誌/資源限制/Capability 細粒度控制、Socket 激活無常駐進程攻擊面。
僅允許 `www-data` 以 `root` 身分執行特定、參數固定的腳本,絕不允許通用 Shell。
#!/bin/bash
# 嚴格驗證參數,白名單模式
ACTION="$1"
shift
case "$ACTION" in
allow_port)
PORT="$1"
[[ "$PORT" =~ ^[0-9]+$ ]] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ] || exit 1
iptables -C INPUT -p tcp --dport "$PORT" -j ACCEPT 2>/dev/null || iptables -A INPUT -p tcp --dport "$PORT" -j ACCEPT
;;
block_ip)
IP="$1"
# 簡易 IP 格式驗證
[[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || exit 1
iptables -C INPUT -s "$IP" -j DROP 2>/dev/null || iptables -A INPUT -s "$IP" -j DROP
;;
*)
exit 1
;;
esac
exit 0
# 允許 www-data 無密碼執行特定腳本及特定參數
www-data ALL=(root) NOPASSWD: /usr/local/sbin/iptables-manager.sh allow_port [0-9]*, /usr/local/sbin/iptables-manager.sh block_ip [0-9.]*
# 禁止執行任何其他指令
<?php
function allowPort(int $port): bool
{
$cmd = sprintf('sudo /usr/local/sbin/iptables-manager.sh allow_port %d', $port);
$output = [];
$returnVar = 0;
exec($cmd, $output, $returnVar);
return $returnVar === 0;
}
關鍵:`sudoers` 以**參數模式**限制 (如 `allow_port [0-9]*`),腳本內再嚴格驗證。絕不寫 `www-data ALL=(root) NOPASSWD: ALL` 或允許執行 `/bin/bash`、`/usr/bin/iptables` 等通用指令。
適用於圖形介面或系統服務互動,定義 `.policy` 檔案規範動作、需求認驗、授權規則。Web 後端較少直接使用,但概念同樣是「最小權限授權」。
讓 PHP (Web 進程) 直接或間接獲得 Root 權限,是架構層級的災難性錯誤。 歷史上無數伺服器淪陷源於此類「偷懶」設計。正確路徑是:**權限分離**,Web 進程只負責接收請求、驗證參數、發起授權請求;高權限操作由**獨立、受控、可審計**的服務 (Systemd Socket Service, Sudoers 白名單腳本, Worker 進程) 執行。請立即審計現有專案,移除所有 Setuid/Root 執行機制,重構為安全架構。