[PHP]posix_getegid関数の使い方を完全解説!実効グループIDを取得する方法

PHP

はじめに

PHPでシステムプログラミングを行う際、「このプロセスはどのグループ権限で動作しているのか?」を知りたいと思ったことはありませんか?

Unixライクなシステムでは、プロセスは実グループID実効グループIDという2つのグループIDを持ちます。特に実効グループIDは、ファイルアクセス権限やリソースへのアクセス制御に直接影響します。

そんな実効グループIDを取得するのが**posix_getegid関数**です。この関数を使えば、現在のプロセスがどのグループ権限で動作しているかを正確に把握できます。

この記事では、posix_getegidの基本から実践的な活用方法まで、詳しく解説します。

posix_getegidとは?

posix_getegidは、現在のプロセスの実効グループID(Effective Group ID)を返す関数です。

基本構文

posix_getegid(): int

パラメータ

この関数はパラメータを取りません。

戻り値

  • 整数: 実効グループID(GID)

実グループIDと実効グループIDの違い

種類関数説明
実グループID (RGID)posix_getgid()プロセスを起動したユーザーの実際のグループ
実効グループID (EGID)posix_getegid()ファイルアクセス権限に使用されるグループ

通常は同じですが、setgidビットが設定されたプログラムを実行すると異なる場合があります。

対応環境

  • POSIX準拠システム(Linux、Unix、macOS)
  • Windows では利用不可
  • POSIX拡張モジュールが必要

対応バージョン

  • PHP 4.0.0 以降で使用可能

基本的な使い方

実効グループIDの取得

<?php
$egid = posix_getegid();
echo "実効グループID: {$egid}\n";

// グループ名を取得
$group = posix_getgrgid($egid);
if ($group) {
    echo "グループ名: {$group['name']}\n";
}

// 出力例:
// 実効グループID: 33
// グループ名: www-data
?>

実グループIDとの比較

<?php
function compareGroupIDs() {
    $rgid = posix_getgid();   // 実グループID
    $egid = posix_getegid();  // 実効グループID
    
    echo "=== グループID比較 ===\n\n";
    
    // 実グループID
    $real_group = posix_getgrgid($rgid);
    echo "実グループID (RGID): {$rgid}\n";
    echo "  グループ名: {$real_group['name']}\n";
    
    // 実効グループID
    $effective_group = posix_getgrgid($egid);
    echo "\n実効グループID (EGID): {$egid}\n";
    echo "  グループ名: {$effective_group['name']}\n";
    
    // 比較
    echo "\n状態: ";
    if ($rgid === $egid) {
        echo "同じ(通常の状態)\n";
    } else {
        echo "異なる(setgidプログラムまたは権限昇格)\n";
    }
}

compareGroupIDs();
?>

実践的な使用例

例1: プロセス権限情報の表示

<?php
class ProcessPermissionInfo {
    /**
     * 現在のプロセスの権限情報を表示
     */
    public static function display() {
        echo "=== プロセス権限情報 ===\n\n";
        
        // ユーザーID
        $ruid = posix_getuid();
        $euid = posix_geteuid();
        
        $real_user = posix_getpwuid($ruid);
        $eff_user = posix_getpwuid($euid);
        
        echo "【ユーザーID】\n";
        echo "  実UID: {$ruid} ({$real_user['name']})\n";
        echo "  実効UID: {$euid} ({$eff_user['name']})\n";
        
        if ($ruid !== $euid) {
            echo "  ⚠ 権限が異なります(setuid/sudo実行)\n";
        }
        
        // グループID
        $rgid = posix_getgid();
        $egid = posix_getegid();
        
        $real_group = posix_getgrgid($rgid);
        $eff_group = posix_getgrgid($egid);
        
        echo "\n【グループID】\n";
        echo "  実GID: {$rgid} ({$real_group['name']})\n";
        echo "  実効GID: {$egid} ({$eff_group['name']})\n";
        
        if ($rgid !== $egid) {
            echo "  ⚠ 権限が異なります(setgid実行)\n";
        }
        
        // 補助グループ
        $groups = posix_getgroups();
        echo "\n【補助グループ】\n";
        
        if ($groups) {
            foreach ($groups as $gid) {
                $group = posix_getgrgid($gid);
                $is_effective = ($gid === $egid) ? ' [実効]' : '';
                echo "  GID {$gid}: {$group['name']}{$is_effective}\n";
            }
        }
        
        // プロセスID
        echo "\n【プロセス情報】\n";
        echo "  PID: " . posix_getpid() . "\n";
        echo "  親PID: " . posix_getppid() . "\n";
        echo "  プロセスグループID: " . posix_getpgid(posix_getpid()) . "\n";
    }
}

ProcessPermissionInfo::display();
?>

例2: ファイルアクセス権限チェック

<?php
class FileAccessChecker {
    /**
     * ファイルへのアクセス可否をグループ権限から判定
     */
    public static function checkGroupAccess($filepath) {
        if (!file_exists($filepath)) {
            return [
                'accessible' => false,
                'reason' => 'ファイルが存在しません'
            ];
        }
        
        $egid = posix_getegid();
        $file_stat = stat($filepath);
        $file_gid = $file_stat['gid'];
        
        echo "=== グループアクセスチェック ===\n";
        echo "ファイル: {$filepath}\n\n";
        
        // 実効グループ情報
        $eff_group = posix_getgrgid($egid);
        echo "実効グループ: {$eff_group['name']} (GID: {$egid})\n";
        
        // ファイルグループ情報
        $file_group = posix_getgrgid($file_gid);
        echo "ファイルグループ: {$file_group['name']} (GID: {$file_gid})\n";
        
        // グループが一致するかチェック
        $group_match = ($egid === $file_gid);
        echo "\nグループ一致: " . ($group_match ? 'はい' : 'いいえ') . "\n";
        
        // 補助グループもチェック
        $groups = posix_getgroups();
        $in_supplementary = in_array($file_gid, $groups);
        
        if ($in_supplementary && !$group_match) {
            echo "補助グループに含まれる: はい\n";
        }
        
        // パーミッション確認
        $perms = $file_stat['mode'];
        $group_read = ($perms & 0040) !== 0;
        $group_write = ($perms & 0020) !== 0;
        $group_exec = ($perms & 0010) !== 0;
        
        echo "\nグループパーミッション:\n";
        echo "  読み取り: " . ($group_read ? '✓' : '✗') . "\n";
        echo "  書き込み: " . ($group_write ? '✓' : '✗') . "\n";
        echo "  実行: " . ($group_exec ? '✓' : '✗') . "\n";
        
        // アクセス可否判定
        $can_access = ($group_match || $in_supplementary);
        
        echo "\n結果: ";
        if ($can_access) {
            echo "グループ権限でアクセス可能\n";
            
            if ($group_read) echo "  - 読み取り可能\n";
            if ($group_write) echo "  - 書き込み可能\n";
            if ($group_exec) echo "  - 実行可能\n";
        } else {
            echo "グループ権限ではアクセス不可(otherパーミッションが適用)\n";
        }
        
        return [
            'accessible' => $can_access,
            'group_match' => $group_match,
            'permissions' => [
                'read' => $group_read,
                'write' => $group_write,
                'execute' => $group_exec
            ]
        ];
    }
}

// 使用例
FileAccessChecker::checkGroupAccess('/etc/group');
?>

例3: 権限昇格の検出

<?php
class PrivilegeEscalationDetector {
    /**
     * 権限昇格が行われているかチェック
     */
    public static function detect() {
        echo "=== 権限昇格検出 ===\n\n";
        
        $escalated = false;
        $warnings = [];
        
        // ユーザーID比較
        $ruid = posix_getuid();
        $euid = posix_geteuid();
        
        if ($ruid !== $euid) {
            $escalated = true;
            $real_user = posix_getpwuid($ruid);
            $eff_user = posix_getpwuid($euid);
            
            $warnings[] = [
                'type' => 'UID変更',
                'severity' => $euid === 0 ? 'high' : 'medium',
                'message' => "実UID({$real_user['name']})と実効UID({$eff_user['name']})が異なります"
            ];
        }
        
        // グループID比較
        $rgid = posix_getgid();
        $egid = posix_getegid();
        
        if ($rgid !== $egid) {
            $escalated = true;
            $real_group = posix_getgrgid($rgid);
            $eff_group = posix_getgrgid($egid);
            
            $warnings[] = [
                'type' => 'GID変更',
                'severity' => 'medium',
                'message' => "実GID({$real_group['name']})と実効GID({$eff_group['name']})が異なります"
            ];
        }
        
        // 結果表示
        if ($escalated) {
            echo "⚠ 権限昇格が検出されました\n\n";
            
            foreach ($warnings as $warning) {
                $mark = match($warning['severity']) {
                    'high' => '🔴',
                    'medium' => '🟡',
                    'low' => '🟢',
                    default => 'ℹ'
                };
                
                echo "{$mark} [{$warning['type']}] {$warning['message']}\n";
            }
            
            echo "\n原因の可能性:\n";
            echo "  - setuid/setgidビットが設定されたプログラム\n";
            echo "  - sudoでの実行\n";
            echo "  - posix_seteuid/posix_setegid関数の使用\n";
        } else {
            echo "✓ 権限昇格は検出されませんでした\n";
        }
        
        return [
            'escalated' => $escalated,
            'warnings' => $warnings
        ];
    }
    
    /**
     * セキュリティリスク評価
     */
    public static function assessSecurityRisk() {
        $result = self::detect();
        
        if (!$result['escalated']) {
            return;
        }
        
        echo "\n=== セキュリティリスク評価 ===\n\n";
        
        $euid = posix_geteuid();
        
        if ($euid === 0) {
            echo "🔴 高リスク: root権限で実行中\n";
            echo "   - 最小限の権限で実行することを推奨します\n";
            echo "   - 処理後は権限を降格してください\n";
        } else {
            echo "🟡 中リスク: 権限が昇格されています\n";
            echo "   - 必要な処理後は元の権限に戻してください\n";
        }
    }
}

// 使用例
PrivilegeEscalationDetector::detect();
echo "\n";
PrivilegeEscalationDetector::assessSecurityRisk();
?>

例4: グループメンバーシップの確認

<?php
class GroupMembershipChecker {
    /**
     * 指定したグループのメンバーかチェック
     */
    public static function isMemberOf($group_name) {
        // グループIDを取得
        $group_info = posix_getgrnam($group_name);
        
        if (!$group_info) {
            return [
                'member' => false,
                'reason' => 'グループが存在しません'
            ];
        }
        
        $target_gid = $group_info['gid'];
        
        // 実効グループIDをチェック
        $egid = posix_getegid();
        
        if ($egid === $target_gid) {
            return [
                'member' => true,
                'type' => 'effective',
                'reason' => '実効グループとして所属'
            ];
        }
        
        // 補助グループをチェック
        $groups = posix_getgroups();
        
        if (in_array($target_gid, $groups)) {
            return [
                'member' => true,
                'type' => 'supplementary',
                'reason' => '補助グループとして所属'
            ];
        }
        
        return [
            'member' => false,
            'reason' => 'グループに所属していません'
        ];
    }
    
    /**
     * 所属グループの詳細を表示
     */
    public static function displayMemberships() {
        echo "=== グループメンバーシップ ===\n\n";
        
        // 実効グループ
        $egid = posix_getegid();
        $eff_group = posix_getgrgid($egid);
        
        echo "実効グループ:\n";
        echo "  {$eff_group['name']} (GID: {$egid})\n";
        
        // すべてのグループ
        $groups = posix_getgroups();
        
        echo "\nすべての所属グループ:\n";
        foreach ($groups as $gid) {
            $group = posix_getgrgid($gid);
            $is_effective = ($gid === $egid) ? ' [実効]' : '';
            echo "  - {$group['name']} (GID: {$gid}){$is_effective}\n";
        }
        
        // グループのメンバー一覧
        echo "\n実効グループのメンバー:\n";
        if (!empty($eff_group['members'])) {
            foreach ($eff_group['members'] as $member) {
                echo "  - {$member}\n";
            }
        } else {
            echo "  (メンバー情報なし)\n";
        }
    }
    
    /**
     * 特定のグループ権限が必要な処理を実行
     */
    public static function requireGroup($group_name, callable $callback) {
        $check = self::isMemberOf($group_name);
        
        if (!$check['member']) {
            throw new RuntimeException(
                "グループ '{$group_name}' のメンバーである必要があります。\n" .
                "理由: {$check['reason']}"
            );
        }
        
        echo "✓ グループ権限確認済み: {$group_name} ({$check['type']})\n";
        
        return $callback();
    }
}

// 使用例1: グループメンバーシップ確認
$result = GroupMembershipChecker::isMemberOf('www-data');
echo "www-dataグループメンバー: " . ($result['member'] ? 'はい' : 'いいえ') . "\n";
echo "理由: {$result['reason']}\n\n";

// 使用例2: 所属グループ表示
GroupMembershipChecker::displayMemberships();

// 使用例3: グループ権限が必要な処理
try {
    GroupMembershipChecker::requireGroup('www-data', function() {
        echo "Webサーバーの設定ファイルにアクセスしています...\n";
        return true;
    });
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例5: グループベースのアクセス制御

<?php
class GroupBasedAccessControl {
    private $allowed_groups = [];
    
    /**
     * 許可するグループを追加
     */
    public function allowGroup($group_name) {
        $group = posix_getgrnam($group_name);
        
        if (!$group) {
            throw new InvalidArgumentException("グループが存在しません: {$group_name}");
        }
        
        $this->allowed_groups[$group['gid']] = $group['name'];
        return $this;
    }
    
    /**
     * アクセス可否をチェック
     */
    public function checkAccess() {
        if (empty($this->allowed_groups)) {
            return [
                'allowed' => true,
                'reason' => '制限なし'
            ];
        }
        
        $egid = posix_getegid();
        $groups = posix_getgroups();
        $all_groups = array_unique(array_merge([$egid], $groups));
        
        // 許可されたグループに所属しているかチェック
        foreach ($all_groups as $gid) {
            if (isset($this->allowed_groups[$gid])) {
                return [
                    'allowed' => true,
                    'reason' => "グループ '{$this->allowed_groups[$gid]}' のメンバー",
                    'gid' => $gid
                ];
            }
        }
        
        return [
            'allowed' => false,
            'reason' => '許可されたグループに所属していません',
            'required_groups' => array_values($this->allowed_groups)
        ];
    }
    
    /**
     * アクセスを要求(許可されていなければ例外)
     */
    public function requireAccess() {
        $result = $this->checkAccess();
        
        if (!$result['allowed']) {
            $groups = implode(', ', $result['required_groups']);
            throw new RuntimeException(
                "アクセスが拒否されました。\n" .
                "必要なグループ: {$groups}\n" .
                "理由: {$result['reason']}"
            );
        }
        
        return $result;
    }
    
    /**
     * 保護された操作を実行
     */
    public function executeProtected(callable $callback) {
        $access = $this->requireAccess();
        
        echo "✓ アクセス許可: {$access['reason']}\n";
        
        return $callback();
    }
}

// 使用例
$acl = new GroupBasedAccessControl();
$acl->allowGroup('www-data')
    ->allowGroup('admin')
    ->allowGroup('developers');

try {
    $result = $acl->executeProtected(function() {
        echo "保護されたリソースにアクセスしています...\n";
        return "処理完了";
    });
    
    echo "結果: {$result}\n";
    
} catch (RuntimeException $e) {
    echo "アクセスエラー:\n";
    echo $e->getMessage() . "\n";
}
?>

実効グループIDの変更

posix_setegidの使用

<?php
// 注意: この操作には特権が必要です

function changeEffectiveGroup($new_gid) {
    $old_egid = posix_getegid();
    $old_group = posix_getgrgid($old_egid);
    
    echo "現在の実効GID: {$old_egid} ({$old_group['name']})\n";
    
    if (posix_setegid($new_gid)) {
        $new_egid = posix_getegid();
        $new_group = posix_getgrgid($new_egid);
        
        echo "実効GIDを変更しました: {$new_egid} ({$new_group['name']})\n";
        return true;
    } else {
        $errno = posix_get_last_error();
        $errmsg = posix_strerror($errno);
        
        echo "実効GIDの変更に失敗: {$errmsg}\n";
        return false;
    }
}

// 使用例(通常は失敗します - 特権が必要)
// changeEffectiveGroup(33); // www-dataのGID
?>

セキュリティ上の注意点

権限チェックのベストプラクティス

<?php
class SecureGroupCheck {
    /**
     * 安全なグループ権限チェック
     */
    public static function secureCheck($required_group) {
        // 1. グループの存在確認
        $group = posix_getgrnam($required_group);
        if (!$group) {
            throw new InvalidArgumentException("グループが存在しません: {$required_group}");
        }
        
        // 2. 実効グループIDをチェック
        $egid = posix_getegid();
        
        if ($egid === $group['gid']) {
            return true;
        }
        
        // 3. 補助グループもチェック
        $groups = posix_getgroups();
        
        if (in_array($group['gid'], $groups)) {
            return true;
        }
        
        // 4. ログに記録
        error_log(sprintf(
            "Unauthorized group access attempt: required=%s, egid=%d",
            $required_group,
            $egid
        ));
        
        return false;
    }
}

// 使用例
if (SecureGroupCheck::secureCheck('admin')) {
    echo "管理者権限あり\n";
} else {
    echo "管理者権限なし\n";
}
?>

まとめ

posix_getegidは、現在のプロセスの実効グループIDを取得する関数です。

主な特徴:

  • ✅ 実効グループID(EGID)を返す
  • ✅ ファイルアクセス権限に直接影響
  • ✅ グループベースのアクセス制御に使用
  • ✅ セキュリティチェックに不可欠

実グループIDとの違い:

  • 実グループID (posix_getgid) – プロセスを起動したユーザーの本来のグループ
  • 実効グループID (posix_getegid) – 実際の権限チェックに使用されるグループ

使用場面:

  • ファイルアクセス権限のチェック
  • グループベースのアクセス制御
  • 権限昇格の検出
  • セキュリティ監査

関連関数:

  • posix_getgid() – 実グループIDを取得
  • posix_getgroups() – 補助グループIDを取得
  • posix_setegid() – 実効グループIDを設定
  • posix_getgrgid() – GIDからグループ情報を取得
  • posix_getgrnam() – グループ名からグループ情報を取得

セキュリティのポイント:

  • 実効グループIDは権限チェックに使用される
  • setgidプログラムでは実グループIDと異なる可能性
  • 補助グループも合わせて確認する
  • 権限昇格の検出は重要

この関数を理解して、より安全なグループベースのアクセス制御を実装しましょう!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!

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