Skip to content

Security: Multiple Authorization Bypass via Livewire Method Calls #140

@lighthousekeeper1212

Description

@lighthousekeeper1212

Summary

5 authorization vulnerabilities found where Livewire methods can be called directly to bypass UI-only authorization checks. The core issue: several Livewire component methods perform actions on resources identified by client-supplied IDs without server-side authorization verification.

Finding 1: Kanban/Scrum Board IDOR (HIGH)

File: app/Helpers/KanbanScrumHelper.php:157-166

recordUpdated() accepts a ticket ID via Livewire event and updates status/order without authorization:

public function recordUpdated(int $record, int $newIndex, int $newStatus): void
{
    $ticket = Ticket::find($record);
    if ($ticket) {
        $ticket->order = $newIndex;
        $ticket->status_id = $newStatus;
        $ticket->save();
    }
}

While getRecords() properly scopes tickets to user's projects, recordUpdated() bypasses this entirely. Any authenticated user can change any ticket's status/order across all projects.

Finding 2: Comment Edit/Delete IDOR (HIGH)

File: app/Filament/Resources/TicketResource/Pages/ViewTicket.php:200-234

editComment() and doDeleteComment() accept comment IDs without authorization:

public function doDeleteComment(int $commentId): void
{
    TicketComment::where('id', $commentId)->delete();
}

The Blade template (line 274) shows buttons only when $this->isAdministrator() || $comment->user_id === auth()->user()->id, but this is UI-only. Livewire methods can be called directly.

Finding 3: Delete Policies Missing Ownership Check (MEDIUM)

Files: app/Policies/TicketPolicy.php:84, ProjectPolicy.php:78, SprintPolicy.php:78

Delete methods only check Spatie permission, not ownership:

public function delete(User $user, Ticket $ticket) {
    return $user->can('Delete ticket');
    // Missing: && ($ticket->project->owner_id === $user->id || ...)
}

Compare with update() in the same file which correctly checks ownership + project membership.

Finding 4: TimesheetResource Missing Authorization (MEDIUM)

File: app/Filament/Resources/TimesheetResource.php

No TicketHourPolicy exists. ListTimesheet doesn't override getTableQuery() to scope results. All users' timesheet entries visible/editable. Every other list page properly scopes data.

Finding 5: Board/RoadMap Client-Supplied Project IDs (LOW)

Files: app/Filament/Pages/Board.php:70, RoadMap.php:86

search() and filter() accept project IDs from form state without ownership check. Mitigated by downstream Kanban/Scrum mount() authorization for board access, but RoadMap may leak project metadata.

Remediation

  1. Add authorization checks to all Livewire methods:
public function recordUpdated(int $record, int $newIndex, int $newStatus): void
{
    $ticket = Ticket::find($record);
    if ($ticket && (
        $ticket->project->owner_id === auth()->id() ||
        $ticket->project->users->contains('id', auth()->id())
    )) {
        // ... update
    }
}
  1. Add ownership checks to delete policies matching the update policy pattern
  2. Create TicketHourPolicy and scope TimesheetResource queries
  3. Server-side validate all Livewire form state (never trust client data)

Found via automated security research by Lighthouse

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions