[PHP]posix_seteuid関数とは?実効ユーザーIDを設定する方法を徹底解説

PHP

こんにちは!今回はPHPのPOSIX関数の中から、posix_seteuid関数について詳しく解説していきます。プロセスの実効ユーザーID(Effective UID)を設定し、権限を動的に制御する方法を、実践的なサンプルコードと共にお伝えします。

posix_seteuid関数とは?

posix_seteuidは、現在のプロセスの**実効ユーザーID(Effective UID)**を設定する関数です。ファイルアクセスやシステムコールの権限チェックに使用されるユーザーIDを一時的に変更できます。

基本構文

posix_seteuid(int $user_id): bool
  • 引数: 設定したいユーザーID(整数)
  • 戻り値: 成功時にtrue、失敗時にfalse

実ユーザーIDと実効ユーザーIDの違い

Unix/Linuxシステムには3種類のユーザーIDがあります。

ユーザーIDの種類

<?php
// 実ユーザーID (Real UID)
// - プロセスを起動した実際のユーザー
// - posix_getuid()で取得
$real_uid = posix_getuid();

// 実効ユーザーID (Effective UID)
// - 実際のアクセス権限チェックに使用されるID
// - posix_geteuid()で取得
$effective_uid = posix_geteuid();

// 保存セットユーザーID (Saved Set-user-ID)
// - setuidプログラムで元の権限を保持するために使用
// - PHPからは直接取得できない

echo "実UID: {$real_uid}\n";
echo "実効UID: {$effective_uid}\n";

if ($real_uid !== $effective_uid) {
    echo "→ setuidプログラムとして実行されています\n";
}
?>

なぜ実効UIDが必要?

実効UIDの仕組みにより、一時的に異なるユーザーの権限でファイルやリソースにアクセスできます。

<?php
// 例: 一般ユーザー(UID: 1000)として実行中
// しかし一時的にroot権限(UID: 0)が必要

$original_euid = posix_geteuid(); // 1000

// root権限に変更(setuidビットが設定されている場合のみ可能)
if (posix_seteuid(0)) {
    echo "root権限で実行中\n";
    
    // 特権が必要な操作を実行
    // 例: システムファイルへのアクセス
    
    // 元の権限に戻す
    posix_seteuid($original_euid);
    echo "元の権限に戻しました\n";
}
?>

実践的な使い方

例1: 基本的な実効UIDの変更

<?php
// ユーザー情報を表示する関数
function displayUserInfo() {
    $ruid = posix_getuid();
    $euid = posix_geteuid();
    
    $ruser = posix_getpwuid($ruid);
    $euser = posix_getpwuid($euid);
    
    echo "実UID: {$ruid} ({$ruser['name']})\n";
    echo "実効UID: {$euid} ({$euser['name']})\n";
    echo "\n";
}

echo "=== 初期状態 ===\n";
displayUserInfo();

// 別のユーザーの実効UIDに変更(権限があれば)
$target_user = posix_getpwnam('www-data');

if ($target_user) {
    echo "=== www-dataユーザーに変更を試みる ===\n";
    
    if (posix_seteuid($target_user['uid'])) {
        displayUserInfo();
        
        // 元に戻す
        echo "=== 元のユーザーに戻す ===\n";
        posix_seteuid(posix_getuid());
        displayUserInfo();
    } else {
        echo "実効UIDの変更に失敗しました\n";
        $error = posix_strerror(posix_get_last_error());
        echo "エラー: {$error}\n";
    }
}
?>

例2: 一時的な権限変更クラス

<?php
class TemporaryUserPrivilege {
    private $original_euid;
    private $changed = false;
    
    public function __construct($username_or_uid) {
        $this->original_euid = posix_geteuid();
        
        // ユーザーIDを取得
        if (is_string($username_or_uid)) {
            $user_info = posix_getpwnam($username_or_uid);
            if (!$user_info) {
                throw new Exception("ユーザーが見つかりません: {$username_or_uid}");
            }
            $uid = $user_info['uid'];
        } else {
            $uid = $username_or_uid;
        }
        
        // 実効UIDを変更
        if (!posix_seteuid($uid)) {
            $error = posix_strerror(posix_get_last_error());
            throw new Exception("実効UIDの変更に失敗: {$error}");
        }
        
        $this->changed = true;
    }
    
    public function __destruct() {
        // 元の実効UIDに戻す
        if ($this->changed) {
            posix_seteuid($this->original_euid);
        }
    }
}

// 使用例
try {
    echo "現在のユーザー: " . posix_getpwuid(posix_geteuid())['name'] . "\n";
    
    // スコープを使った自動的な権限管理
    {
        $temp = new TemporaryUserPrivilege('www-data');
        echo "一時的に変更: " . posix_getpwuid(posix_geteuid())['name'] . "\n";
        
        // この範囲内でwww-dataの権限で実行
        // ...
        
    } // スコープを抜けると自動的に元の権限に戻る
    
    echo "権限を復元: " . posix_getpwuid(posix_geteuid())['name'] . "\n";
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例3: 特権操作マネージャー

<?php
class PrivilegedOperationManager {
    private $is_privileged = false;
    
    public function __construct() {
        // rootまたはsetuidプログラムとして実行されているか確認
        $this->is_privileged = (posix_getuid() === 0) || 
                               (posix_geteuid() === 0);
    }
    
    public function canElevatePrivilege() {
        // root権限に昇格可能かチェック
        // setuidビットが設定されていれば可能
        return $this->is_privileged;
    }
    
    public function executeAsRoot(callable $callback) {
        if (!$this->canElevatePrivilege()) {
            throw new Exception("root権限に昇格できません");
        }
        
        $original_euid = posix_geteuid();
        
        try {
            // root権限に昇格
            if ($original_euid !== 0) {
                if (!posix_seteuid(0)) {
                    throw new Exception("root権限への昇格に失敗");
                }
                echo "→ root権限に昇格しました\n";
            }
            
            // 特権操作を実行
            $result = $callback();
            
            return $result;
            
        } finally {
            // 必ず元の権限に戻す
            if ($original_euid !== 0 && posix_geteuid() === 0) {
                posix_seteuid($original_euid);
                echo "→ 元の権限に戻しました\n";
            }
        }
    }
    
    public function executeAsUser($username, callable $callback) {
        $user_info = posix_getpwnam($username);
        if (!$user_info) {
            throw new Exception("ユーザーが見つかりません: {$username}");
        }
        
        $original_euid = posix_geteuid();
        $target_uid = $user_info['uid'];
        
        try {
            // 指定されたユーザーの権限に変更
            if (!posix_seteuid($target_uid)) {
                throw new Exception("ユーザー '{$username}' への変更に失敗");
            }
            
            echo "→ {$username} として実行中\n";
            
            // 操作を実行
            $result = $callback();
            
            return $result;
            
        } finally {
            // 元の権限に戻す
            posix_seteuid($original_euid);
        }
    }
    
    public function createPrivilegedFile($filepath, $content, $owner, $perms = 0644) {
        return $this->executeAsRoot(function() use ($filepath, $content, $owner, $perms) {
            // ファイルを作成
            if (file_put_contents($filepath, $content) === false) {
                throw new Exception("ファイルの作成に失敗");
            }
            
            // 所有者を設定
            $user_info = posix_getpwnam($owner);
            if (!$user_info) {
                unlink($filepath);
                throw new Exception("ユーザーが見つかりません: {$owner}");
            }
            
            if (!chown($filepath, $user_info['uid'])) {
                unlink($filepath);
                throw new Exception("所有者の変更に失敗");
            }
            
            // パーミッションを設定
            chmod($filepath, $perms);
            
            echo "特権ファイルを作成: {$filepath}\n";
            return true;
        });
    }
}

// 使用例(setuidプログラムまたはroot権限で実行する必要がある)
try {
    $manager = new PrivilegedOperationManager();
    
    if ($manager->canElevatePrivilege()) {
        // root権限で実行
        $manager->executeAsRoot(function() {
            echo "システム設定を変更中...\n";
            // 特権が必要な操作
        });
        
        // 特定ユーザーの権限で実行
        $manager->executeAsUser('www-data', function() {
            echo "Webファイルを処理中...\n";
            // www-dataの権限で実行
        });
        
        // 特権ファイルを作成
        $manager->createPrivilegedFile(
            '/etc/myapp/config.conf',
            'CONFIG_DATA',
            'root',
            0600
        );
    } else {
        echo "このプログラムはsetuidビットが設定されていません\n";
    }
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例4: セキュアなファイル操作システム

<?php
class SecureFileSystem {
    private $original_euid;
    
    public function __construct() {
        $this->original_euid = posix_geteuid();
    }
    
    public function readFileAsOwner($filepath) {
        if (!file_exists($filepath)) {
            throw new Exception("ファイルが見つかりません: {$filepath}");
        }
        
        // ファイルの所有者を取得
        $file_uid = fileowner($filepath);
        $owner_info = posix_getpwuid($file_uid);
        
        echo "ファイル: {$filepath}\n";
        echo "所有者: {$owner_info['name']} (UID: {$file_uid})\n";
        
        try {
            // ファイル所有者の権限で読み取り
            if (!posix_seteuid($file_uid)) {
                throw new Exception("所有者権限への変更に失敗");
            }
            
            $content = file_get_contents($filepath);
            
            if ($content === false) {
                throw new Exception("ファイルの読み取りに失敗");
            }
            
            return $content;
            
        } finally {
            // 元の権限に戻す
            posix_seteuid($this->original_euid);
        }
    }
    
    public function writeFileAsOwner($filepath, $content) {
        $file_exists = file_exists($filepath);
        
        if ($file_exists) {
            $file_uid = fileowner($filepath);
        } else {
            // 新規ファイルの場合は現在のユーザーで作成
            $file_uid = $this->original_euid;
        }
        
        try {
            // 所有者の権限で書き込み
            if (!posix_seteuid($file_uid)) {
                throw new Exception("所有者権限への変更に失敗");
            }
            
            $result = file_put_contents($filepath, $content);
            
            if ($result === false) {
                throw new Exception("ファイルの書き込みに失敗");
            }
            
            return $result;
            
        } finally {
            // 元の権限に戻す
            posix_seteuid($this->original_euid);
        }
    }
    
    public function changeFileOwner($filepath, $new_owner) {
        if (!file_exists($filepath)) {
            throw new Exception("ファイルが見つかりません");
        }
        
        $user_info = posix_getpwnam($new_owner);
        if (!$user_info) {
            throw new Exception("ユーザーが見つかりません: {$new_owner}");
        }
        
        try {
            // root権限に昇格(必要な場合)
            if (posix_geteuid() !== 0) {
                if (!posix_seteuid(0)) {
                    throw new Exception("root権限への昇格に失敗");
                }
            }
            
            // 所有者を変更
            if (!chown($filepath, $user_info['uid'])) {
                throw new Exception("所有者の変更に失敗");
            }
            
            echo "所有者を変更: {$filepath} → {$new_owner}\n";
            return true;
            
        } finally {
            // 元の権限に戻す
            posix_seteuid($this->original_euid);
        }
    }
}

// 使用例
$fs = new SecureFileSystem();

try {
    // ファイルを所有者の権限で読み取る
    $content = $fs->readFileAsOwner('/tmp/test.txt');
    echo "ファイル内容を読み取りました\n";
    
    // ファイルを所有者の権限で書き込む
    $fs->writeFileAsOwner('/tmp/test.txt', "Updated content\n");
    echo "ファイルを更新しました\n";
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例5: setuidプログラムのシミュレーション

<?php
class SetuidSimulator {
    public function demonstrateSetuid() {
        echo "=== setuidビットの動作説明 ===\n\n";
        
        $ruid = posix_getuid();
        $euid = posix_geteuid();
        
        $ruser = posix_getpwuid($ruid);
        $euser = posix_getpwuid($euid);
        
        echo "現在の状態:\n";
        echo "  実UID: {$ruid} ({$ruser['name']})\n";
        echo "  実効UID: {$euid} ({$euser['name']})\n\n";
        
        if ($ruid === $euid) {
            echo "通常のプログラムとして実行されています。\n";
            echo "setuidビットが設定されていません。\n\n";
        } else {
            echo "setuidプログラムとして実行されています!\n";
            echo "実効UIDがファイル所有者のUIDに設定されています。\n\n";
        }
        
        // 有名なsetuidプログラムの例
        $setuid_programs = [
            '/usr/bin/passwd',  // パスワード変更
            '/usr/bin/sudo',    // 管理者権限実行
            '/bin/ping',        // ネットワーク診断
        ];
        
        echo "システム上のsetuidプログラム例:\n";
        foreach ($setuid_programs as $prog) {
            if (file_exists($prog)) {
                $stat = stat($prog);
                $mode = $stat['mode'];
                
                if (($mode & 04000) === 04000) {
                    $owner = posix_getpwuid($stat['uid']);
                    echo "  {$prog}\n";
                    echo "    所有者: {$owner['name']} (UID: {$stat['uid']})\n";
                    echo "    setuidビット: 設定あり\n";
                    echo "    → 実行すると実効UID = {$stat['uid']}\n\n";
                }
            }
        }
    }
    
    public function demonstratePrivilegeDrop() {
        echo "\n=== 権限降格のデモンストレーション ===\n\n";
        
        $original_ruid = posix_getuid();
        $original_euid = posix_geteuid();
        
        echo "1. 初期状態:\n";
        echo "   実UID: {$original_ruid}\n";
        echo "   実効UID: {$original_euid}\n\n";
        
        if ($original_euid === 0) {
            echo "2. root権限を持っています。一般ユーザーに降格します。\n";
            
            // nobodyユーザーに降格
            $nobody = posix_getpwnam('nobody');
            if ($nobody && posix_seteuid($nobody['uid'])) {
                echo "   実効UID → {$nobody['uid']} ({$nobody['name']})\n";
                echo "   権限を降格しました\n\n";
                
                echo "3. root権限に戻します。\n";
                if (posix_seteuid(0)) {
                    echo "   実効UID → 0 (root)\n";
                    echo "   権限を復元しました\n";
                } else {
                    echo "   権限の復元に失敗(保存セットUIDがない場合)\n";
                }
            }
        } else {
            echo "2. 一般ユーザーで実行されています。\n";
            echo "   root権限への昇格はできません(setuidビットなし)\n";
        }
    }
    
    public function createSetuidScript($filepath) {
        echo "\n=== setuidスクリプトの作成(参考) ===\n\n";
        echo "注意: セキュリティ上の理由から、多くのシステムでは\n";
        echo "スクリプトのsetuidビットは無視されます。\n\n";
        
        $script_content = <<<'PHP'
#!/usr/bin/php
<?php
// このスクリプトはsetuidビットが設定されている前提

echo "実UID: " . posix_getuid() . "\n";
echo "実効UID: " . posix_geteuid() . "\n";

// 特権操作を実行...

// 権限を降格
if (posix_geteuid() === 0) {
    posix_seteuid(posix_getuid());
    echo "権限を降格しました\n";
}
?>
PHP;
        
        if (file_put_contents($filepath, $script_content) !== false) {
            chmod($filepath, 0755);
            echo "スクリプトを作成: {$filepath}\n";
            echo "\nsetuidビットを設定するには(root権限が必要):\n";
            echo "  sudo chown root:root {$filepath}\n";
            echo "  sudo chmod u+s {$filepath}\n";
            echo "  ls -l {$filepath}\n";
            echo "  # -rwsr-xr-x の 's' がsetuidビット\n";
            
            return true;
        }
        
        return false;
    }
}

// 使用例
$simulator = new SetuidSimulator();
$simulator->demonstrateSetuid();
$simulator->demonstratePrivilegeDrop();

// スクリプト作成例
// $simulator->createSetuidScript('/tmp/setuid_example.php');
?>

例6: 完全な権限管理システム

<?php
class ComprehensivePrivilegeManager {
    private $uid_stack = [];
    private $gid_stack = [];
    
    public function getCurrentPrivileges() {
        return [
            'real_uid' => posix_getuid(),
            'effective_uid' => posix_geteuid(),
            'real_gid' => posix_getgid(),
            'effective_gid' => posix_getegid(),
            'groups' => posix_getgroups(),
        ];
    }
    
    public function displayPrivileges($label = "現在の権限") {
        $priv = $this->getCurrentPrivileges();
        
        $ruser = posix_getpwuid($priv['real_uid']);
        $euser = posix_getpwuid($priv['effective_uid']);
        $rgroup = posix_getgrgid($priv['real_gid']);
        $egroup = posix_getgrgid($priv['effective_gid']);
        
        echo "\n=== {$label} ===\n";
        echo "実UID: {$priv['real_uid']} ({$ruser['name']})\n";
        echo "実効UID: {$priv['effective_uid']} ({$euser['name']})\n";
        echo "実GID: {$priv['real_gid']} ({$rgroup['name']})\n";
        echo "実効GID: {$priv['effective_gid']} ({$egroup['name']})\n";
        
        $group_names = [];
        foreach ($priv['groups'] as $gid) {
            $ginfo = posix_getgrgid($gid);
            $group_names[] = $ginfo['name'];
        }
        echo "補助グループ: " . implode(', ', $group_names) . "\n";
    }
    
    public function pushPrivileges() {
        array_push($this->uid_stack, posix_geteuid());
        array_push($this->gid_stack, posix_getegid());
    }
    
    public function popPrivileges() {
        if (!empty($this->uid_stack)) {
            $euid = array_pop($this->uid_stack);
            posix_seteuid($euid);
        }
        
        if (!empty($this->gid_stack)) {
            $egid = array_pop($this->gid_stack);
            posix_setegid($egid);
        }
    }
    
    public function switchToUser($username) {
        $user_info = posix_getpwnam($username);
        if (!$user_info) {
            throw new Exception("ユーザーが見つかりません: {$username}");
        }
        
        // 現在の権限をスタックに保存
        $this->pushPrivileges();
        
        // 新しい権限に切り替え
        if (!posix_setegid($user_info['gid'])) {
            $this->popPrivileges();
            throw new Exception("GIDの変更に失敗");
        }
        
        if (!posix_seteuid($user_info['uid'])) {
            posix_setegid($this->gid_stack[count($this->gid_stack) - 1]);
            $this->popPrivileges();
            throw new Exception("UIDの変更に失敗");
        }
        
        return true;
    }
    
    public function restorePreviousPrivileges() {
        $this->popPrivileges();
    }
    
    public function dropAllPrivileges($username = 'nobody') {
        $user_info = posix_getpwnam($username);
        if (!$user_info) {
            throw new Exception("ユーザーが見つかりません: {$username}");
        }
        
        // 完全に権限を降格(戻れない)
        if (!posix_setgid($user_info['gid']) ||
            !posix_setuid($user_info['uid'])) {
            throw new Exception("権限の完全な降格に失敗");
        }
        
        echo "権限を完全に降格しました(元に戻せません)\n";
    }
}

// 使用例
$pm = new ComprehensivePrivilegeManager();

try {
    $pm->displayPrivileges("初期状態");
    
    // www-dataユーザーに切り替え
    $pm->switchToUser('www-data');
    $pm->displayPrivileges("www-dataに切り替え後");
    
    // nobodyユーザーに切り替え(ネスト可能)
    $pm->switchToUser('nobody');
    $pm->displayPrivileges("nobodyに切り替え後");
    
    // 1つ前の権限に戻る(www-data)
    $pm->restorePreviousPrivileges();
    $pm->displayPrivileges("1つ戻す");
    
    // 最初の権限に戻る
    $pm->restorePreviousPrivileges();
    $pm->displayPrivileges("初期状態に復帰");
    
} catch (Exception $e) {
    echo "\nエラー: " . $e->getMessage() . "\n";
}
?>

注意点とトラブルシューティング

権限の制限事項

<?php
// 一般ユーザーが実効UIDを変更できるのは限られた場合のみ

function canChangeToUID($target_uid) {
    $current_uid = posix_getuid();
    $current_euid = posix_geteuid();
    
    // 変更可能な条件:
    // 1. 自分の実UIDに戻す
    // 2. root権限を持っている
    // 3. setuidプログラムで保存セットUIDに戻す
    
    return ($target_uid === $current_uid) ||  // 実UIDに戻す
           ($current_euid === 0);              // root権限
}

$target_uid = 1001;

if (canChangeToUID($target_uid)) {
    echo "UID {$target_uid} への変更が可能です\n";
    posix_seteuid($target_uid);
} else {
    echo "UID {$target_uid} への変更権限がありません\n";
}
?>

権限変更の順序

<?php
// 権限を降格する際の正しい順序

function dropPrivilegesSafely($username) {
    $user_info = posix_getpwnam($username);
    if (!$user_info) {
        throw new Exception("ユーザーが見つかりません");
    }
    
    // 重要: グループを先に変更してからユーザーを変更
    // ユーザーを先に変更すると、グループ変更の権限を失う可能性がある
    
    echo "権限降格の手順:\n";
    
    // 1. 補助グループリストを初期化
    echo "1. 補助グループを初期化\n";
    posix_initgroups($username, $user_info['gid']);
    
    // 2. 実効GIDを変更
    echo "2. 実効GIDを変更\n";
    if (!posix_setegid($user_info['gid'])) {
        throw new Exception("実効GIDの変更に失敗");
    }
    
    // 3. 実GIDを変更
    echo "3. 実GIDを変更\n";
    if (!posix_setgid($user_info['gid'])) {
        throw new Exception("実GIDの変更に失敗");
    }
    
    // 4. 実効UIDを変更
    echo "4. 実効UIDを変更\n";
    if (!posix_seteuid($user_info['uid'])) {
        throw new Exception("実効UIDの変更に失敗");
    }
    
    // 5. 実UIDを変更(これ以降root権限に戻れない)
    echo "5. 実UIDを変更(不可逆)\n";
    if (!posix_setuid($user_info['uid'])) {
        throw new Exception("実UIDの変更に失敗");
    }
    
    echo "\n権限降格完了\n";
}
?>

エラーハンドリング

<?php
function safeSetEUID($uid) {
    $original_euid = posix_geteuid();
    
    if (!posix_seteuid($uid)) {
        $errno = posix_get_last_error();
        $error = posix_strerror($errno);
        
        // よくあるエラー
        switch ($errno) {
            case 1: // EPERM
                throw new Exception(
                    "権限がありません。UID {$uid} への変更が許可されていません"
                );
            case 22: // EINVAL
                throw new Exception("無効なユーザーID: {$uid}");
            default:
                throw new Exception(
                    "実効UID変更失敗: {$error} (errno: {$errno})"
                );
        }
    }
    
    // 変更が成功したか確認
    $new_euid = posix_geteuid();
    if ($new_euid !== $uid) {
        // ロールバック試行
        posix_seteuid($original_euid);
        throw new Exception("実効UIDの設定に失敗しました(確認エラー)");
    }
    
    return true;
}

// 使用例
try {
    $target_uid = 33; // www-data
    safeSetEUID($target_uid);
    echo "実効UIDを {$target_uid} に変更しました\n";
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

Windows環境での制限

<?php
if (!function_exists('posix_seteuid')) {
    die("posix_seteuidはこの環境では使用できません\n" .
        "Unix/Linux/macOS環境で実行してください\n");
}

// クロスプラットフォーム対応ラッパー
class PlatformUserManager {
    public function setEffectiveUser($uid) {
        if (function_exists('posix_seteuid')) {
            return posix_seteuid($uid);
        }
        
        // Windows環境では代替処理
        throw new Exception("この環境ではユーザー変更がサポートされていません");
    }
    
    public function isPosixSystem() {
        return function_exists('posix_seteuid');
    }
}
?>

関連する便利な関数

ユーザーID関連関数の完全セット

<?php
// ユーザーID取得
$uid = posix_getuid();       // 実ユーザーID
$euid = posix_geteuid();     // 実効ユーザーID

// ユーザーID設定
posix_setuid($uid);          // 実ユーザーIDを設定(不可逆)
posix_seteuid($euid);        // 実効ユーザーIDを設定(可逆)

// グループID取得
$gid = posix_getgid();       // 実グループID
$egid = posix_getegid();     // 実効グループID
$groups = posix_getgroups(); // 補助グループリスト

// グループID設定
posix_setgid($gid);          // 実グループIDを設定(不可逆)
posix_setegid($egid);        // 実効グループIDを設定(可逆)

// ユーザー情報取得
$info = posix_getpwuid($uid);     // UIDからユーザー情報
$info = posix_getpwnam('username'); // 名前からユーザー情報

// グループ情報取得
$info = posix_getgrgid($gid);      // GIDからグループ情報
$info = posix_getgrnam('groupname'); // 名前からグループ情報

// 補助グループの初期化
posix_initgroups('username', $gid);
?>

まとめ

posix_seteuid関数は以下の特徴があります。

✅ 実効ユーザーIDを一時的に変更 ✅ ファイルアクセス権限の動的な制御 ✅ setuidビットの動作理解に必須 ✅ セキュアなマルチユーザーシステムの基盤 ✅ 権限の昇格と降格の両方に対応

実効ユーザーIDの制御は、Unixセキュリティモデルの中核です。適切に使用することで、最小権限の原則を守りながら、必要な時だけ特権操作を実行できます。

ベストプラクティス

  1. 必ず元に戻す: try-finallyで確実にロールバック
  2. 最小権限の時間: 必要最小限の時間だけ権限を保持
  3. 順序を守る: GID→UIDの順で権限変更
  4. 検証: 変更後に実際に変更されたか確認
  5. ログ記録: セキュリティ監査のため記録を残す

セキュリティの考慮事項

setuidプログラムの危険性

<?php
// setuidプログラムを書く際の注意点

// ❌ 危険: 環境変数を信頼
// $path = getenv('PATH'); // 攻撃者が操作可能

// ✅ 安全: 絶対パスを使用
$path = '/usr/bin:/bin';

// ❌ 危険: ユーザー入力を直接使用
// system($_GET['cmd']); // コマンドインジェクション

// ✅ 安全: 入力を検証・サニタイズ
function safeCommand($input) {
    $whitelist = ['start', 'stop', 'restart'];
    if (!in_array($input, $whitelist)) {
        throw new Exception("無効なコマンド");
    }
    return $input;
}

// ❌ 危険: 権限を持ったまま外部コード実行
// include($_GET['file']); // 実効UID=0で実行される

// ✅ 安全: 権限を降格してから実行
posix_seteuid(posix_getuid()); // 元のユーザーに戻す
include($safe_file);
?>

TOCTTOU (Time-of-check to time-of-use) 攻撃の回避

<?php
// 権限チェックとファイル操作の間にrace conditionが発生する可能性

// ❌ 脆弱: チェックと使用の間に隙がある
if (is_writable($file)) {
    posix_seteuid(0);
    file_put_contents($file, $data); // シンボリックリンクに置き換えられるかも
}

// ✅ より安全: ファイルディスクリプタを使用
$fd = fopen($file, 'w');
if ($fd) {
    posix_seteuid(0);
    fwrite($fd, $data);
    fclose($fd);
}
?>

実用的なセキュリティパターン

パターン1: 権限の一時的な昇格

<?php
// 必要な時だけroot権限を使用
$original = posix_geteuid();
posix_seteuid(0);           // 昇格
// 特権操作
posix_seteuid($original);   // すぐに戻す
?>

パターン2: 権限の完全な降格

<?php
// 初期化後はroot権限を永久に放棄
posix_setgid($gid);  // 不可逆
posix_setuid($uid);  // 不可逆(これ以降rootに戻れない)
?>

パターン3: スタック型権限管理

<?php
// 権限をスタックで管理(ネスト可能)
$stack = [posix_geteuid()];
posix_seteuid($new_uid);    // 変更
array_push($stack, $new_uid);
// ...
posix_seteuid(array_pop($stack)); // 復元
?>

セキュアな権限管理を! この記事が役に立ったら、ぜひシェアしてください。PHPのシステムセキュリティとプログラミングについて、他にも知りたいことがあればコメントで教えてくださいね。

タイトルとURLをコピーしました