coolify/tests/Feature/TeamInvitationPrivilegeEscalationTest.php

177 lines
5.6 KiB
PHP
Raw Permalink Normal View History

fix: critical privilege escalation in team invitation system This commit addresses a critical security vulnerability where low-privileged users (members) could invite high-privileged users (admins/owners) to teams, allowing them to escalate their own privileges through password reset. Root Causes Fixed: 1. TeamPolicy authorization checks were commented out, allowing all team members to manage invitations instead of just admins/owners 2. Missing role elevation checks in InviteLink component allowed members to invite users with higher privileges Security Fixes: 1. app/Policies/TeamPolicy.php - Uncommented and enforced authorization checks for: * update() - Only admins/owners can update team settings * delete() - Only admins/owners can delete teams * manageMembers() - Only admins/owners can manage team members * viewAdmin() - Only admins/owners can view admin panel * manageInvitations() - Only admins/owners can manage invitations 2. app/Livewire/Team/InviteLink.php - Added explicit role elevation checks to prevent: * Members from inviting admins or owners * Admins from inviting owners (defense-in-depth) - Validates that inviter has sufficient privileges for target role Test Coverage: 1. tests/Feature/TeamPolicyTest.php - 24 comprehensive tests covering all policy methods - Tests for owner, admin, member, and non-member access - Specific tests for the privilege escalation vulnerability 2. tests/Feature/TeamInvitationPrivilegeEscalationTest.php - 11 tests covering all role elevation scenarios - Tests member → admin/owner escalation (blocked) - Tests admin → owner escalation (blocked) - Tests valid invitation paths for each role Impact: - Prevents privilege escalation attacks - Protects all Coolify instances from unauthorized access - Enforces proper role hierarchy in team management References: - Identified by Aikido AI whitebox pentest service - CVE: Pending assignment - Severity: Critical 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 09:42:25 +00:00
<?php
use App\Livewire\Team\InviteLink;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
// Create a team with owner, admin, and member
$this->team = Team::factory()->create();
$this->owner = User::factory()->create();
$this->admin = User::factory()->create();
$this->member = User::factory()->create();
$this->team->members()->attach($this->owner->id, ['role' => 'owner']);
$this->team->members()->attach($this->admin->id, ['role' => 'admin']);
$this->team->members()->attach($this->member->id, ['role' => 'member']);
});
describe('privilege escalation prevention', function () {
test('member cannot invite admin (SECURITY FIX)', function () {
// Login as member
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
// Attempt to invite someone as admin
Livewire::test(InviteLink::class)
->set('email', 'newadmin@example.com')
->set('role', 'admin')
->call('viaLink')
->assertDispatched('error');
});
test('member cannot invite owner (SECURITY FIX)', function () {
// Login as member
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
// Attempt to invite someone as owner
Livewire::test(InviteLink::class)
->set('email', 'newowner@example.com')
->set('role', 'owner')
->call('viaLink')
->assertDispatched('error');
});
test('admin cannot invite owner', function () {
// Login as admin
$this->actingAs($this->admin);
session(['currentTeam' => $this->team]);
// Attempt to invite someone as owner
Livewire::test(InviteLink::class)
->set('email', 'newowner@example.com')
->set('role', 'owner')
->call('viaLink')
->assertDispatched('error');
});
test('admin can invite member', function () {
// Login as admin
$this->actingAs($this->admin);
session(['currentTeam' => $this->team]);
// Invite someone as member
Livewire::test(InviteLink::class)
->set('email', 'newmember@example.com')
->set('role', 'member')
->call('viaLink')
->assertDispatched('success');
// Verify invitation was created
$this->assertDatabaseHas('team_invitations', [
'email' => 'newmember@example.com',
'role' => 'member',
'team_id' => $this->team->id,
]);
});
test('admin can invite admin', function () {
// Login as admin
$this->actingAs($this->admin);
session(['currentTeam' => $this->team]);
// Invite someone as admin
Livewire::test(InviteLink::class)
->set('email', 'newadmin@example.com')
->set('role', 'admin')
->call('viaLink')
->assertDispatched('success');
// Verify invitation was created
$this->assertDatabaseHas('team_invitations', [
'email' => 'newadmin@example.com',
'role' => 'admin',
'team_id' => $this->team->id,
]);
});
test('owner can invite member', function () {
// Login as owner
$this->actingAs($this->owner);
session(['currentTeam' => $this->team]);
// Invite someone as member
Livewire::test(InviteLink::class)
->set('email', 'newmember@example.com')
->set('role', 'member')
->call('viaLink')
->assertDispatched('success');
// Verify invitation was created
$this->assertDatabaseHas('team_invitations', [
'email' => 'newmember@example.com',
'role' => 'member',
'team_id' => $this->team->id,
]);
});
test('owner can invite admin', function () {
// Login as owner
$this->actingAs($this->owner);
session(['currentTeam' => $this->team]);
// Invite someone as admin
Livewire::test(InviteLink::class)
->set('email', 'newadmin@example.com')
->set('role', 'admin')
->call('viaLink')
->assertDispatched('success');
// Verify invitation was created
$this->assertDatabaseHas('team_invitations', [
'email' => 'newadmin@example.com',
'role' => 'admin',
'team_id' => $this->team->id,
]);
});
test('owner can invite owner', function () {
// Login as owner
$this->actingAs($this->owner);
session(['currentTeam' => $this->team]);
// Invite someone as owner
Livewire::test(InviteLink::class)
->set('email', 'newowner@example.com')
->set('role', 'owner')
->call('viaLink')
->assertDispatched('success');
// Verify invitation was created
$this->assertDatabaseHas('team_invitations', [
'email' => 'newowner@example.com',
'role' => 'owner',
'team_id' => $this->team->id,
]);
});
test('member cannot bypass policy by calling viaEmail', function () {
// Login as member
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
// Attempt to invite someone as admin via email
Livewire::test(InviteLink::class)
->set('email', 'newadmin@example.com')
->set('role', 'admin')
->call('viaEmail')
->assertDispatched('error');
});
});